Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
namespace core_badges;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
require_once($CFG->dirroot.'/lib/badgeslib.php');
22
 
23
use context_system;
24
use context_course;
25
use context_user;
26
use moodle_exception;
27
use moodle_url;
28
use core_text;
29
use award_criteria;
30
use core_php_time_limit;
31
use html_writer;
32
use stdClass;
33
 
34
/**
35
 * Class that represents badge.
36
 *
1441 ariadna 37
 * @package    core_badges
1 efrain 38
 * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
39
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1441 ariadna 40
 * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
1 efrain 41
 */
42
class badge {
43
    /** @var int Badge id */
44
    public $id;
45
 
46
    /** @var string Badge name */
47
    public $name;
48
 
49
    /** @var string Badge description */
50
    public $description;
51
 
52
    /** @var integer Timestamp this badge was created */
53
    public $timecreated;
54
 
55
    /** @var integer Timestamp this badge was modified */
56
    public $timemodified;
57
 
58
    /** @var int The user who created this badge */
59
    public $usercreated;
60
 
61
    /** @var int The user who modified this badge */
62
    public $usermodified;
63
 
64
    /** @var string The name of the issuer of this badge */
65
    public $issuername;
66
 
67
    /** @var string The url of the issuer of this badge */
68
    public $issuerurl;
69
 
70
    /** @var string The email of the issuer of this badge */
71
    public $issuercontact;
72
 
73
    /** @var integer Timestamp this badge will expire */
74
    public $expiredate;
75
 
76
    /** @var integer Duration this badge is valid for */
77
    public $expireperiod;
78
 
79
    /** @var integer Site or course badge */
80
    public $type;
81
 
82
    /** @var integer The course this badge belongs to */
83
    public $courseid;
84
 
85
    /** @var string The message this badge includes. */
86
    public $message;
87
 
88
    /** @var string The subject of the message for this badge */
89
    public $messagesubject;
90
 
91
    /** @var int Is this badge image baked. */
92
    public $attachment;
93
 
94
    /** @var int Send a message when this badge is awarded. */
95
    public $notification;
96
 
97
    /** @var int Lifecycle status for this badge. */
98
    public $status = 0;
99
 
100
    /** @var int Timestamp to next run cron for this badge. */
101
    public $nextcron;
102
 
103
    /** @var int What backpack api version to use for this badge. */
104
    public $version;
105
 
106
    /** @var string What language is this badge written in. */
107
    public $language;
108
 
109
    /** @var string The caption of the image for this badge. */
110
    public $imagecaption;
111
 
112
    /** @var array Badge criteria */
113
    public $criteria = array();
114
 
115
    /** @var int|null Total users which have the award. Called from badges_get_badges() */
116
    public $awards;
117
 
118
    /** @var string|null The name of badge status. Called from badges_get_badges() */
119
    public $statstring;
120
 
121
    /** @var int|null The date the badges were issued. Called from badges_get_badges() */
122
    public $dateissued;
123
 
124
    /** @var string|null Unique hash. Called from badges_get_badges() */
125
    public $uniquehash;
126
 
127
    /** @var string|null Message format. Called from file_prepare_standard_editor() */
128
    public $messageformat;
129
 
130
    /** @var array Message editor. Called from file_prepare_standard_editor() */
131
    public $message_editor = [];
132
 
133
    /**
134
     * Constructs with badge details.
135
     *
136
     * @param int $badgeid badge ID.
137
     */
138
    public function __construct($badgeid) {
139
        global $DB;
140
        $this->id = $badgeid;
141
 
142
        $data = $DB->get_record('badge', array('id' => $badgeid));
143
 
144
        if (empty($data)) {
145
            throw new moodle_exception('error:nosuchbadge', 'badges', '', $badgeid);
146
        }
147
 
148
        foreach ((array)$data as $field => $value) {
149
            if (property_exists($this, $field)) {
150
                $this->{$field} = $value;
151
            }
152
        }
153
 
154
        $this->criteria = self::get_criteria();
155
    }
156
 
157
    /**
158
     * Use to get context instance of a badge.
159
     *
160
     * @return \context|void instance.
161
     */
162
    public function get_context() {
163
        if ($this->type == BADGE_TYPE_SITE) {
164
            return context_system::instance();
165
        } else if ($this->type == BADGE_TYPE_COURSE) {
166
            return context_course::instance($this->courseid);
167
        } else {
168
            debugging('Something is wrong...');
169
        }
170
    }
171
 
172
    /**
173
     * Return array of aggregation methods
174
     *
175
     * @return array
176
     */
177
    public static function get_aggregation_methods() {
178
        return array(
179
                BADGE_CRITERIA_AGGREGATION_ALL => get_string('all', 'badges'),
180
                BADGE_CRITERIA_AGGREGATION_ANY => get_string('any', 'badges'),
181
        );
182
    }
183
 
184
    /**
185
     * Return array of accepted criteria types for this badge
186
     *
187
     * @return array
188
     */
189
    public function get_accepted_criteria() {
190
        global $CFG;
191
        $criteriatypes = array();
192
 
193
        if ($this->type == BADGE_TYPE_COURSE) {
194
            $criteriatypes = array(
195
                    BADGE_CRITERIA_TYPE_OVERALL,
196
                    BADGE_CRITERIA_TYPE_MANUAL,
197
                    BADGE_CRITERIA_TYPE_COURSE,
198
                    BADGE_CRITERIA_TYPE_BADGE,
199
                    BADGE_CRITERIA_TYPE_ACTIVITY,
200
                    BADGE_CRITERIA_TYPE_COMPETENCY
201
            );
202
        } else if ($this->type == BADGE_TYPE_SITE) {
203
            $criteriatypes = array(
204
                    BADGE_CRITERIA_TYPE_OVERALL,
205
                    BADGE_CRITERIA_TYPE_MANUAL,
206
                    BADGE_CRITERIA_TYPE_COURSESET,
207
                    BADGE_CRITERIA_TYPE_BADGE,
208
                    BADGE_CRITERIA_TYPE_PROFILE,
209
                    BADGE_CRITERIA_TYPE_COHORT,
210
                    BADGE_CRITERIA_TYPE_COMPETENCY
211
            );
212
        }
213
        $alltypes = badges_list_criteria();
214
        foreach ($criteriatypes as $index => $type) {
215
            if (!isset($alltypes[$type])) {
216
                unset($criteriatypes[$index]);
217
            }
218
        }
219
 
220
        return $criteriatypes;
221
    }
222
 
223
    /**
224
     * Save/update badge information in 'badge' table only.
225
     * Cannot be used for updating awards and criteria settings.
226
     *
227
     * @return boolean Returns true on success.
228
     */
229
    public function save() {
230
        global $DB;
231
 
232
        $fordb = new stdClass();
233
        foreach (get_object_vars($this) as $k => $v) {
234
            $fordb->{$k} = $v;
235
        }
236
        // TODO: We need to making it more simple.
237
        // Since the variables are not exist in the badge table,
238
        // unsetting them is a must to avoid errors.
239
        unset($fordb->criteria);
240
        unset($fordb->awards);
241
        unset($fordb->statstring);
242
        unset($fordb->dateissued);
243
        unset($fordb->uniquehash);
244
        unset($fordb->messageformat);
245
        unset($fordb->message_editor);
246
 
247
        $fordb->timemodified = time();
248
        if ($DB->update_record_raw('badge', $fordb)) {
249
            // Trigger event, badge updated.
250
            $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
251
            $event = \core\event\badge_updated::create($eventparams);
252
            $event->trigger();
253
            return true;
254
        } else {
255
            throw new moodle_exception('error:save', 'badges');
256
            return false;
257
        }
258
    }
259
 
260
    /**
261
     * Creates and saves a clone of badge with all its properties.
262
     * Clone is not active by default and has 'Copy of' attached to its name.
263
     *
264
     * @return int ID of new badge.
265
     */
266
    public function make_clone() {
267
        global $DB, $USER, $PAGE;
268
 
269
        $fordb = new stdClass();
270
        foreach (get_object_vars($this) as $k => $v) {
271
            $fordb->{$k} = $v;
272
        }
273
 
274
        $fordb->name = get_string('copyof', 'badges', $this->name);
275
        $fordb->status = BADGE_STATUS_INACTIVE;
276
        $fordb->usercreated = $USER->id;
277
        $fordb->usermodified = $USER->id;
278
        $fordb->timecreated = time();
279
        $fordb->timemodified = time();
280
        $tags = $this->get_badge_tags();
281
        unset($fordb->id);
282
 
283
        if ($fordb->notification > 1) {
284
            $fordb->nextcron = badges_calculate_message_schedule($fordb->notification);
285
        }
286
 
287
        $criteria = $fordb->criteria;
288
        unset($fordb->criteria);
289
 
290
        if ($new = $DB->insert_record('badge', $fordb, true)) {
291
            $newbadge = new badge($new);
292
            // Copy badge tags.
293
            \core_tag_tag::set_item_tags('core_badges', 'badge', $newbadge->id, $this->get_context(), $tags);
294
 
295
            // Copy badge image.
296
            $fs = get_file_storage();
297
            if ($file = $fs->get_file($this->get_context()->id, 'badges', 'badgeimage', $this->id, '/', 'f3.png')) {
298
                if ($imagefile = $file->copy_content_to_temp()) {
299
                    badges_process_badge_image($newbadge, $imagefile);
300
                }
301
            }
302
 
303
            // Copy badge criteria.
304
            foreach ($this->criteria as $crit) {
305
                $crit->make_clone($new);
306
            }
307
 
308
            // Trigger event, badge duplicated.
309
            $eventparams = array('objectid' => $new, 'context' => $PAGE->context);
310
            $event = \core\event\badge_duplicated::create($eventparams);
311
            $event->trigger();
312
 
313
            return $new;
314
        } else {
315
            throw new moodle_exception('error:clone', 'badges');
316
            return false;
317
        }
318
    }
319
 
320
    /**
321
     * Checks if badges is active.
322
     * Used in badge award.
323
     *
324
     * @return boolean A status indicating badge is active
325
     */
326
    public function is_active() {
327
        if (($this->status == BADGE_STATUS_ACTIVE) ||
328
            ($this->status == BADGE_STATUS_ACTIVE_LOCKED)) {
329
            return true;
330
        }
331
        return false;
332
    }
333
 
334
    /**
335
     * Use to get the name of badge status.
336
     *
337
     * @return string
338
     */
339
    public function get_status_name() {
340
        return get_string('badgestatus_' . $this->status, 'badges');
341
    }
342
 
343
    /**
344
     * Use to set badge status.
345
     * Only active badges can be earned/awarded/issued.
346
     *
347
     * @param int $status Status from BADGE_STATUS constants
348
     */
349
    public function set_status($status = 0) {
350
        $this->status = $status;
351
        $this->save();
352
        if ($status == BADGE_STATUS_ACTIVE) {
353
            // Trigger event, badge enabled.
354
            $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
355
            $event = \core\event\badge_enabled::create($eventparams);
356
            $event->trigger();
357
        } else if ($status == BADGE_STATUS_INACTIVE) {
358
            // Trigger event, badge disabled.
359
            $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
360
            $event = \core\event\badge_disabled::create($eventparams);
361
            $event->trigger();
362
        }
363
    }
364
 
365
    /**
366
     * Checks if badges is locked.
367
     * Used in badge award and editing.
368
     *
369
     * @return boolean A status indicating badge is locked
370
     */
371
    public function is_locked() {
372
        if (($this->status == BADGE_STATUS_ACTIVE_LOCKED) ||
373
                ($this->status == BADGE_STATUS_INACTIVE_LOCKED)) {
374
            return true;
375
        }
376
        return false;
377
    }
378
 
379
    /**
380
     * Checks if badge has been awarded to users.
381
     * Used in badge editing.
382
     *
383
     * @return boolean A status indicating badge has been awarded at least once
384
     */
385
    public function has_awards() {
386
        global $DB;
387
        $awarded = $DB->record_exists_sql('SELECT b.uniquehash
388
                    FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
389
                    WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
390
 
391
        return $awarded;
392
    }
393
 
394
    /**
395
     * Gets list of users who have earned an instance of this badge.
396
     *
397
     * @return array An array of objects with information about badge awards.
398
     */
399
    public function get_awards() {
400
        global $DB;
401
 
402
        $awards = $DB->get_records_sql(
403
                'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname
404
                    FROM {badge_issued} b INNER JOIN {user} u
405
                        ON b.userid = u.id
406
                    WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
407
 
408
        return $awards;
409
    }
410
 
411
    /**
412
     * Indicates whether badge has already been issued to a user.
413
     *
414
     * @param int $userid User to check
415
     * @return boolean
416
     */
417
    public function is_issued($userid) {
418
        global $DB;
419
        return $DB->record_exists('badge_issued', array('badgeid' => $this->id, 'userid' => $userid));
420
    }
421
 
422
    /**
423
     * Issue a badge to user.
424
     *
425
     * @param int $userid User who earned the badge
426
     * @param boolean $nobake Not baking actual badges (for testing purposes)
427
     */
428
    public function issue($userid, $nobake = false) {
429
        global $DB, $CFG;
430
 
431
        $now = time();
432
        $issued = new stdClass();
433
        $issued->badgeid = $this->id;
434
        $issued->userid = $userid;
435
        $issued->uniquehash = sha1(rand() . $userid . $this->id . $now);
436
        $issued->dateissued = $now;
437
 
438
        if ($this->can_expire()) {
439
            $issued->dateexpire = $this->calculate_expiry($now);
440
        } else {
441
            $issued->dateexpire = null;
442
        }
443
 
444
        // Take into account user badges privacy settings.
445
        // If none set, badges default visibility is set to public.
446
        $issued->visible = get_user_preferences('badgeprivacysetting', 1, $userid);
447
 
448
        $result = $DB->insert_record('badge_issued', $issued, true);
449
 
450
        if ($result) {
451
            // Trigger badge awarded event.
452
            $eventdata = array (
453
                'context' => $this->get_context(),
454
                'objectid' => $this->id,
455
                'relateduserid' => $userid,
456
                'other' => array('dateexpire' => $issued->dateexpire, 'badgeissuedid' => $result)
457
            );
458
            \core\event\badge_awarded::create($eventdata)->trigger();
459
 
460
            // Lock the badge, so that its criteria could not be changed any more.
461
            if ($this->status == BADGE_STATUS_ACTIVE) {
462
                $this->set_status(BADGE_STATUS_ACTIVE_LOCKED);
463
            }
464
 
465
            // Update details in criteria_met table.
466
            $compl = $this->get_criteria_completions($userid);
467
            foreach ($compl as $c) {
468
                $obj = new stdClass();
469
                $obj->id = $c->id;
470
                $obj->issuedid = $result;
471
                $DB->update_record('badge_criteria_met', $obj, true);
472
            }
473
 
474
            if (!$nobake) {
475
                // Bake a badge image.
476
                $pathhash = badges_bake($issued->uniquehash, $this->id, $userid, true);
477
 
478
                // Notify recipients and badge creators.
479
                badges_notify_badge_award($this, $userid, $issued->uniquehash, $pathhash);
480
            }
481
        }
482
    }
483
 
484
    /**
485
     * Reviews all badge criteria and checks if badge can be instantly awarded.
486
     *
487
     * @return int Number of awards
488
     */
489
    public function review_all_criteria() {
490
        global $DB, $CFG;
491
        $awards = 0;
492
 
493
        // Raise timelimit as this could take a while for big web sites.
494
        core_php_time_limit::raise();
495
        raise_memory_limit(MEMORY_HUGE);
496
 
497
        foreach ($this->criteria as $crit) {
498
            // Overall criterion is decided when other criteria are reviewed.
499
            if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) {
500
                continue;
501
            }
502
 
503
            list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql();
504
            // For site level badges, get all active site users who can earn this badge and haven't got it yet.
505
            if ($this->type == BADGE_TYPE_SITE) {
506
                $sql = "SELECT DISTINCT u.id, bi.badgeid
507
                        FROM {user} u
508
                        {$extrajoin}
509
                        LEFT JOIN {badge_issued} bi
510
                            ON u.id = bi.userid AND bi.badgeid = :badgeid
511
                        WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0 " . $extrawhere;
512
                $params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams);
513
                $toearn = $DB->get_fieldset_sql($sql, $params);
514
            } else {
515
                // For course level badges, get all users who already earned the badge in this course.
516
                // Then find the ones who are enrolled in the course and don't have a badge yet.
517
                $earned = $DB->get_fieldset_select(
518
                    'badge_issued',
519
                    'userid AS id',
520
                    'badgeid = :badgeid',
521
                    array('badgeid' => $this->id)
522
                );
523
 
524
                $wheresql = '';
525
                $earnedparams = array();
526
                if (!empty($earned)) {
527
                    list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false);
528
                    $wheresql = ' WHERE u.id ' . $earnedsql;
529
                }
530
                list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true);
531
                $sql = "SELECT DISTINCT u.id
532
                        FROM {user} u
533
                        {$extrajoin}
534
                        JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere;
535
                $params = array_merge($enrolledparams, $earnedparams, $extraparams);
536
                $toearn = $DB->get_fieldset_sql($sql, $params);
537
            }
538
 
539
            foreach ($toearn as $uid) {
540
                $reviewoverall = false;
541
                if ($crit->review($uid, true)) {
542
                    $crit->mark_complete($uid);
543
                    if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
544
                        $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
545
                        $this->issue($uid);
546
                        $awards++;
547
                    } else {
548
                        $reviewoverall = true;
549
                    }
550
                } else {
551
                    // Will be reviewed some other time.
552
                    $reviewoverall = false;
553
                }
554
                // Review overall if it is required.
555
                if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
556
                    $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
557
                    $this->issue($uid);
558
                    $awards++;
559
                }
560
            }
561
        }
562
 
563
        return $awards;
564
    }
565
 
566
    /**
567
     * Gets an array of completed criteria from 'badge_criteria_met' table.
568
     *
569
     * @param int $userid Completions for a user
570
     * @return array Records of criteria completions
571
     */
572
    public function get_criteria_completions($userid) {
573
        global $DB;
574
        $completions = array();
575
        $sql = "SELECT bcm.id, bcm.critid
576
                FROM {badge_criteria_met} bcm
577
                    INNER JOIN {badge_criteria} bc ON bcm.critid = bc.id
578
                WHERE bc.badgeid = :badgeid AND bcm.userid = :userid ";
579
        $completions = $DB->get_records_sql($sql, array('badgeid' => $this->id, 'userid' => $userid));
580
 
581
        return $completions;
582
    }
583
 
584
    /**
585
     * Checks if badges has award criteria set up.
586
     *
587
     * @return boolean A status indicating badge has at least one criterion
588
     */
589
    public function has_criteria() {
590
        if (count($this->criteria) > 0) {
591
            return true;
592
        }
593
        return false;
594
    }
595
 
596
    /**
597
     * Returns badge award criteria
598
     *
599
     * @return array An array of badge criteria
600
     */
601
    public function get_criteria() {
602
        global $DB;
603
        $criteria = array();
604
 
605
        if ($records = (array)$DB->get_records('badge_criteria', array('badgeid' => $this->id))) {
606
            foreach ($records as $record) {
607
                $criteria[$record->criteriatype] = award_criteria::build((array)$record);
608
            }
609
        }
610
 
611
        return $criteria;
612
    }
613
 
614
    /**
615
     * Get aggregation method for badge criteria
616
     *
617
     * @param int $criteriatype If none supplied, get overall aggregation method (optional)
618
     * @return int One of BADGE_CRITERIA_AGGREGATION_ALL or BADGE_CRITERIA_AGGREGATION_ANY
619
     */
620
    public function get_aggregation_method($criteriatype = 0) {
621
        global $DB;
622
        $params = array('badgeid' => $this->id, 'criteriatype' => $criteriatype);
623
        $aggregation = $DB->get_field('badge_criteria', 'method', $params, IGNORE_MULTIPLE);
624
 
625
        if (!$aggregation) {
626
            return BADGE_CRITERIA_AGGREGATION_ALL;
627
        }
628
 
629
        return $aggregation;
630
    }
631
 
632
    /**
633
     * Checks if badge has expiry period or date set up.
634
     *
635
     * @return boolean A status indicating badge can expire
636
     */
637
    public function can_expire() {
638
        if ($this->expireperiod || $this->expiredate) {
639
            return true;
640
        }
641
        return false;
642
    }
643
 
644
    /**
645
     * Calculates badge expiry date based on either expirydate or expiryperiod.
646
     *
647
     * @param int $timestamp Time of badge issue
648
     * @return int A timestamp
649
     */
650
    public function calculate_expiry($timestamp) {
651
        $expiry = null;
652
 
653
        if (isset($this->expiredate)) {
654
            $expiry = $this->expiredate;
655
        } else if (isset($this->expireperiod)) {
656
            $expiry = $timestamp + $this->expireperiod;
657
        }
658
 
659
        return $expiry;
660
    }
661
 
662
    /**
663
     * Checks if badge has manual award criteria set.
664
     *
665
     * @return boolean A status indicating badge can be awarded manually
666
     */
667
    public function has_manual_award_criteria() {
668
        foreach ($this->criteria as $criterion) {
669
            if ($criterion->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) {
670
                return true;
671
            }
672
        }
673
        return false;
674
    }
675
 
676
    /**
677
     * Fully deletes the badge or marks it as archived.
678
     *
679
     * @param boolean $archive Achive a badge without actual deleting of any data.
680
     */
681
    public function delete($archive = true) {
682
        global $DB;
683
 
684
        if ($archive) {
685
            $this->status = BADGE_STATUS_ARCHIVED;
686
            $this->save();
687
 
688
            // Trigger event, badge archived.
689
            $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
690
            $event = \core\event\badge_archived::create($eventparams);
691
            $event->trigger();
692
            return;
693
        }
694
 
695
        $fs = get_file_storage();
696
 
697
        // Remove all issued badge image files and badge awards.
698
        // Cannot bulk remove area files here because they are issued in user context.
699
        $awards = $this->get_awards();
700
        foreach ($awards as $award) {
701
            $usercontext = context_user::instance($award->userid);
702
            $fs->delete_area_files($usercontext->id, 'badges', 'userbadge', $this->id);
703
        }
704
        $DB->delete_records('badge_issued', array('badgeid' => $this->id));
705
 
706
        // Remove all badge criteria.
707
        $criteria = $this->get_criteria();
708
        foreach ($criteria as $criterion) {
709
            $criterion->delete();
710
        }
711
 
712
        // Delete badge images.
713
        $badgecontext = $this->get_context();
714
        $fs->delete_area_files($badgecontext->id, 'badges', 'badgeimage', $this->id);
715
 
716
        // Delete endorsements, competencies and related badges.
717
        $DB->delete_records('badge_endorsement', array('badgeid' => $this->id));
718
        $relatedsql = 'badgeid = :badgeid OR relatedbadgeid = :relatedbadgeid';
719
        $relatedparams = array(
720
            'badgeid' => $this->id,
721
            'relatedbadgeid' => $this->id
722
        );
723
        $DB->delete_records_select('badge_related', $relatedsql, $relatedparams);
724
        $DB->delete_records('badge_alignment', array('badgeid' => $this->id));
725
 
726
        // Delete all tags.
727
        \core_tag_tag::remove_all_item_tags('core_badges', 'badge', $this->id);
728
 
729
        // Finally, remove badge itself.
730
        $DB->delete_records('badge', array('id' => $this->id));
731
 
732
        // Trigger event, badge deleted.
733
        $eventparams = array('objectid' => $this->id,
734
            'context' => $this->get_context(),
735
            'other' => array('badgetype' => $this->type, 'courseid' => $this->courseid)
736
            );
737
        $event = \core\event\badge_deleted::create($eventparams);
738
        $event->trigger();
739
    }
740
 
741
    /**
742
     * Add multiple related badges.
743
     *
744
     * @param array $relatedids Id of badges.
745
     */
746
    public function add_related_badges($relatedids) {
747
        global $DB;
748
        $relatedbadges = array();
749
        foreach ($relatedids as $relatedid) {
750
            $relatedbadge = new stdClass();
751
            $relatedbadge->badgeid = $this->id;
752
            $relatedbadge->relatedbadgeid = $relatedid;
753
            $relatedbadges[] = $relatedbadge;
754
        }
755
        $DB->insert_records('badge_related', $relatedbadges);
756
    }
757
 
758
    /**
759
     * Delete an related badge.
760
     *
761
     * @param int $relatedid Id related badge.
762
     * @return boolean A status for delete an related badge.
763
     */
764
    public function delete_related_badge($relatedid) {
765
        global $DB;
766
        $sql = "(badgeid = :badgeid AND relatedbadgeid = :relatedid) OR " .
767
               "(badgeid = :relatedid2 AND relatedbadgeid = :badgeid2)";
768
        $params = ['badgeid' => $this->id, 'badgeid2' => $this->id, 'relatedid' => $relatedid, 'relatedid2' => $relatedid];
769
        return $DB->delete_records_select('badge_related', $sql, $params);
770
    }
771
 
772
    /**
773
     * Checks if badge has related badges.
774
     *
775
     * @return boolean A status related badge.
776
     */
777
    public function has_related() {
778
        global $DB;
779
        $sql = "SELECT DISTINCT b.id
780
                    FROM {badge_related} br
781
                    JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id)
782
                   WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3";
783
        return $DB->record_exists_sql($sql, ['badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id]);
784
    }
785
 
786
    /**
787
     * Get related badges of badge.
788
     *
789
     * @param boolean $activeonly Do not get the inactive badges when is true.
790
     * @return array Related badges information.
791
     */
792
    public function get_related_badges($activeonly = false) {
793
        global $DB;
794
 
795
        $params = array('badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id);
796
        $query = "SELECT DISTINCT b.id, b.name, b.version, b.language, b.type
797
                    FROM {badge_related} br
798
                    JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id)
799
                   WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3";
800
        if ($activeonly) {
801
            $query .= " AND b.status <> :status";
802
            $params['status'] = BADGE_STATUS_INACTIVE;
803
        }
804
        $relatedbadges = $DB->get_records_sql($query, $params);
805
        return $relatedbadges;
806
    }
807
 
808
    /**
809
     * Insert/update alignment information of badge.
810
     *
811
     * @param stdClass $alignment Data of a alignment.
812
     * @param int $alignmentid ID alignment.
813
     * @return bool|int A status/ID when insert or update data.
814
     */
815
    public function save_alignment($alignment, $alignmentid = 0) {
816
        global $DB;
817
 
818
        $record = $DB->record_exists('badge_alignment', array('id' => $alignmentid));
819
        if ($record) {
820
            $alignment->id = $alignmentid;
821
            return $DB->update_record('badge_alignment', $alignment);
822
        } else {
823
            return $DB->insert_record('badge_alignment', $alignment, true);
824
        }
825
    }
826
 
827
    /**
828
     * Delete a alignment of badge.
829
     *
830
     * @param int $alignmentid ID alignment.
831
     * @return boolean A status for delete a alignment.
832
     */
833
    public function delete_alignment($alignmentid) {
834
        global $DB;
835
        return $DB->delete_records('badge_alignment', array('badgeid' => $this->id, 'id' => $alignmentid));
836
    }
837
 
838
    /**
839
     * Get alignments of badge.
840
     *
841
     * @return array List content alignments.
842
     */
843
    public function get_alignments() {
844
        global $DB;
845
        return $DB->get_records('badge_alignment', array('badgeid' => $this->id));
846
    }
847
 
848
    /**
849
     * Insert/update Endorsement information of badge.
850
     *
851
     * @param stdClass $endorsement Data of an endorsement.
852
     * @return bool|int A status/ID when insert or update data.
853
     */
854
    public function save_endorsement($endorsement) {
855
        global $DB;
856
        $record = $DB->get_record('badge_endorsement', array('badgeid' => $this->id));
857
        if ($record) {
858
            $endorsement->id = $record->id;
859
            return $DB->update_record('badge_endorsement', $endorsement);
860
        } else {
861
            return $DB->insert_record('badge_endorsement', $endorsement, true);
862
        }
863
    }
864
 
865
    /**
866
     * Get endorsement of badge.
867
     *
868
     * @return array|stdClass Endorsement information.
869
     */
870
    public function get_endorsement() {
871
        global $DB;
872
        return $DB->get_record('badge_endorsement', array('badgeid' => $this->id));
873
    }
874
 
875
    /**
876
     * Markdown language support for criteria.
877
     *
878
     * @return string $output Markdown content to output.
879
     */
880
    public function markdown_badge_criteria() {
881
        $agg = $this->get_aggregation_methods();
882
        if (empty($this->criteria)) {
883
            return get_string('nocriteria', 'badges');
884
        }
885
        $overalldescr = '';
886
        $overall = $this->criteria[BADGE_CRITERIA_TYPE_OVERALL];
887
        if (!empty($overall->description)) {
888
                $overalldescr = format_text($overall->description, $overall->descriptionformat,
889
                    array('context' => $this->get_context())) . '\n';
890
        }
891
        // Get the condition string.
892
        if (count($this->criteria) == 2) {
893
            $condition = get_string('criteria_descr', 'badges');
894
        } else {
895
            $condition = get_string('criteria_descr_' . BADGE_CRITERIA_TYPE_OVERALL, 'badges',
896
                core_text::strtoupper($agg[$this->get_aggregation_method()]));
897
        }
898
        unset($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
899
        $items = array();
900
        // If only one criterion left, make sure its description goe to the top.
901
        if (count($this->criteria) == 1) {
902
            $c = reset($this->criteria);
903
            if (!empty($c->description)) {
904
                $overalldescr = $c->description . '\n';
905
            }
906
            if (count($c->params) == 1) {
907
                $items[] = ' * ' . get_string('criteria_descr_single_' . $c->criteriatype, 'badges') .
908
                    $c->get_details();
909
            } else {
910
                $items[] = '* ' . get_string('criteria_descr_' . $c->criteriatype, 'badges',
911
                        core_text::strtoupper($agg[$this->get_aggregation_method($c->criteriatype)])) .
912
                    $c->get_details();
913
            }
914
        } else {
915
            foreach ($this->criteria as $type => $c) {
916
                $criteriadescr = '';
917
                if (!empty($c->description)) {
918
                    $criteriadescr = $c->description;
919
                }
920
                if (count($c->params) == 1) {
921
                    $items[] = ' * ' . get_string('criteria_descr_single_' . $type, 'badges') .
922
                        $c->get_details() . $criteriadescr;
923
                } else {
924
                    $items[] = '* ' . get_string('criteria_descr_' . $type, 'badges',
925
                            core_text::strtoupper($agg[$this->get_aggregation_method($type)])) .
926
                        $c->get_details() . $criteriadescr;
927
                }
928
            }
929
        }
930
        return strip_tags($overalldescr . $condition . html_writer::alist($items, array(), 'ul'));
931
    }
932
 
933
    /**
934
     * Define issuer information by format Open Badges specification version 2.
935
     *
936
     * @param int $obversion OB version to use.
937
     * @return array Issuer informations of the badge.
938
     */
939
    public function get_badge_issuer(?int $obversion = null) {
1441 ariadna 940
        return [
941
            'name' => $this->issuername,
942
            'url' => $this->issuerurl,
943
            'email' => $this->issuercontact,
944
            '@context' => OPEN_BADGES_V2_CONTEXT,
945
            'id' => (new moodle_url('/badges/issuer_json.php', ['id' => $this->id]))->out(false),
946
            'type' => OPEN_BADGES_V2_TYPE_ISSUER,
947
        ];
1 efrain 948
    }
949
 
950
    /**
951
     * Get tags of badge.
952
     *
953
     * @return array Badge tags.
954
     */
955
    public function get_badge_tags(): array {
956
        return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->id));
957
    }
1441 ariadna 958
 
959
    /**
960
     * Create a badge, to store it in the database.
961
     *
962
     * @param stdClass $data Data to create a badge.
963
     * @param int|null $courseid The course where the badge will be added.
964
     * @return badge The badge object created.
965
     */
966
    public static function create_badge(stdClass $data, ?int $courseid = null): badge {
967
        global $DB, $USER, $CFG;
968
 
969
        $now = time();
970
 
971
        $fordb = new stdClass();
972
        $fordb->id = null;
973
        $fordb->courseid = $courseid;
974
        $fordb->type = $courseid ? BADGE_TYPE_COURSE : BADGE_TYPE_SITE;
975
        $fordb->name = trim($data->name);
976
        $fordb->version = $data->version;
977
        $fordb->language = $data->language;
978
        $fordb->description = $data->description;
979
        $fordb->imagecaption = $data->imagecaption;
980
        $fordb->timecreated = $now;
981
        $fordb->timemodified = $now;
982
        $fordb->usercreated = $USER->id;
983
        $fordb->usermodified = $USER->id;
984
        $fordb->issuername = $data->issuername;
985
        $fordb->issuerurl = $data->issuerurl;
986
        $fordb->issuercontact = $data->issuercontact;
987
 
988
        if (!property_exists($data, 'expiry')) {
989
            $data->expiry = 0;
990
        }
991
        $fordb->expiredate = ($data->expiry == 1) ? $data->expiredate : null;
992
        $fordb->expireperiod = ($data->expiry == 2) ? $data->expireperiod : null;
993
        $fordb->messagesubject = get_string('messagesubject', 'badges');
994
        $fordb->message = get_string('messagebody', 'badges',
995
                html_writer::link($CFG->wwwroot . '/badges/mybadges.php', get_string('managebadges', 'badges')));
996
        $fordb->attachment = 1;
997
        $fordb->notification = BADGE_MESSAGE_NEVER;
998
        $fordb->status = BADGE_STATUS_INACTIVE;
999
 
1000
        $badgeid = $DB->insert_record('badge', $fordb, true);
1001
 
1002
        if ($courseid) {
1003
            $course = get_course($courseid);
1004
            $context = context_course::instance($course->id);
1005
        } else {
1006
            $context = context_system::instance();
1007
        }
1008
 
1009
        // Trigger event, badge created.
1010
        $eventparams = [
1011
            'objectid' => $badgeid,
1012
            'context' => $context,
1013
        ];
1014
        $event = \core\event\badge_created::create($eventparams);
1015
        $event->trigger();
1016
 
1017
        $badge = new badge($badgeid);
1018
        if (property_exists($data, 'tags')) {
1019
            \core_tag_tag::set_item_tags('core_badges', 'badge', $badgeid, $context, $data->tags);
1020
        }
1021
 
1022
        return $badge;
1023
    }
1024
 
1025
    /**
1026
     * Update badge data.
1027
     *
1028
     * @param stdClass $data Data to update a badge.
1029
     * @return bool A status for update a badge.
1030
     */
1031
    public function update(stdClass $data): bool {
1032
        global $USER;
1033
 
1034
        $this->name = trim($data->name);
1035
        $this->version = trim($data->version);
1036
        $this->language = $data->language;
1037
        $this->description = $data->description;
1038
        $this->imagecaption = $data->imagecaption;
1039
        $this->usermodified = $USER->id;
1040
        $this->issuername = $data->issuername;
1041
        $this->issuerurl = $data->issuerurl;
1042
        $this->issuercontact = $data->issuercontact;
1043
        $this->expiredate = ($data->expiry == 1) ? $data->expiredate : null;
1044
        $this->expireperiod = ($data->expiry == 2) ? $data->expireperiod : null;
1045
 
1046
        // Need to unset message_editor options to avoid errors on form edit.
1047
        unset($this->messageformat);
1048
        unset($this->message_editor);
1049
 
1050
        if (!$this->save()) {
1051
            return false;
1052
        }
1053
 
1054
        \core_tag_tag::set_item_tags('core_badges', 'badge', $this->id, $this->get_context(), $data->tags);
1055
 
1056
        return true;
1057
    }
1058
 
1059
    /**
1060
     * Update the message of badge.
1061
     *
1062
     * @param stdClass $data Data to update a badge message.
1063
     * @return bool A status for update a badge message.
1064
     */
1065
    public function update_message(stdClass $data): bool {
1066
        // Calculate next message cron if form data is different from original badge data.
1067
        if ($data->notification != $this->notification) {
1068
            if ($data->notification > BADGE_MESSAGE_ALWAYS) {
1069
                $this->nextcron = badges_calculate_message_schedule($data->notification);
1070
            } else {
1071
                $this->nextcron = null;
1072
            }
1073
        }
1074
 
1075
        $this->message = clean_text($data->message_editor['text'], FORMAT_HTML);
1076
        $this->messagesubject = $data->messagesubject;
1077
        $this->notification = $data->notification;
1078
        $this->attachment = $data->attachment;
1079
 
1080
        unset($this->messageformat);
1081
        unset($this->message_editor);
1082
        return $this->save();
1083
    }
1 efrain 1084
}