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