1<?php
2/* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4/**
5 * Class ilBadgeHandler
6 *
7 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com>
8 * @version $Id:$
9 *
10 * @package ServicesBadge
11 */
12class ilBadgeHandler
13{
14    /**
15     * @var ilDB
16     */
17    protected $db;
18
19    /**
20     * @var ilTree
21     */
22    protected $tree;
23
24    /**
25     * @var ilLanguage
26     */
27    protected $lng;
28
29    protected $settings; // [ilSetting]
30
31    protected static $instance; // [ilBadgeHandler]
32
33    /**
34     * Constructor
35     *
36     * @return self
37     */
38    protected function __construct()
39    {
40        global $DIC;
41
42        $this->db = $DIC->database();
43        if (isset($DIC["tree"])) {
44            $this->tree = $DIC->repositoryTree();
45        }
46        $this->settings = new ilSetting("bdga");
47    }
48
49    /**
50     * Constructor
51     *
52     * @return self
53     */
54    public static function getInstance()
55    {
56        if (!self::$instance) {
57            self::$instance = new self();
58        }
59        return self::$instance;
60    }
61
62
63    //
64    // setter/getter
65    //
66
67    public function isActive()
68    {
69        return $this->settings->get("active", false);
70    }
71
72    public function setActive($a_value)
73    {
74        $this->settings->set("active", (bool) $a_value);
75    }
76
77    public function isObiActive()
78    {
79        // see bug #20124
80        return false;
81
82        return $this->settings->get("obi_active", false);
83    }
84
85    public function setObiActive($a_value)
86    {
87        $this->settings->set("obi_active", (bool) $a_value);
88    }
89
90    public function getObiOrganistation()
91    {
92        return $this->settings->get("obi_organisation", null);
93    }
94
95    public function setObiOrganisation($a_value)
96    {
97        $this->settings->set("obi_organisation", trim($a_value));
98    }
99
100    public function getObiContact()
101    {
102        return $this->settings->get("obi_contact", null);
103    }
104
105    public function setObiContact($a_value)
106    {
107        $this->settings->set("obi_contact", trim($a_value));
108    }
109
110    public function getObiSalt()
111    {
112        return $this->settings->get("obi_salt", null);
113    }
114
115    public function setObiSalt($a_value)
116    {
117        $this->settings->set("obi_salt", trim($a_value));
118    }
119
120    public function getComponents()
121    {
122        $components = $this->settings->get("components", null);
123        if ($components) {
124            return unserialize($components);
125        }
126        return array();
127    }
128
129    public function setComponents(array $a_components = null)
130    {
131        if (is_array($a_components) &&
132            !sizeof($a_components)) {
133            $a_components = null;
134        }
135        $this->settings->set("components", $a_components !== null
136            ? serialize(array_unique($a_components))
137            : null);
138    }
139
140
141    //
142    // component handling
143    //
144
145    protected function getComponent($a_id)
146    {
147        $ilDB = $this->db;
148
149        // see ilCtrl
150        $set = $ilDB->query("SELECT * FROM il_component" .
151            " WHERE id = " . $ilDB->quote($a_id, "text"));
152        $rec = $ilDB->fetchAssoc($set);
153        if ($rec["type"]) {
154            return $rec;
155        }
156    }
157
158    /**
159     * Get provider instance
160     *
161     * @param string $a_component_id
162     * @return ilBadgeProvider
163     */
164    public function getProviderInstance($a_component_id)
165    {
166        $comp = $this->getComponent($a_component_id);
167        if ($comp) {
168            $class = "il" . $comp["name"] . "BadgeProvider";
169            $file = $comp["type"] . "/" . $comp["name"] . "/classes/class." . $class . ".php";
170            if (file_exists($file)) {
171                include_once $file;
172                $obj = new $class;
173                if ($obj instanceof ilBadgeProvider) {
174                    return $obj;
175                }
176            }
177        }
178    }
179
180    public function getComponentCaption($a_component_id)
181    {
182        $comp = $this->getComponent($a_component_id);
183        if ($comp) {
184            return $comp["type"] . "/" . $comp["name"];
185        }
186    }
187
188    //
189    // types
190    //
191
192    public function getUniqueTypeId($a_component_id, ilBadgeType $a_badge)
193    {
194        return $a_component_id . "/" . $a_badge->getId();
195    }
196
197    /**
198     * Get type instance by unique id (component, type)
199     * @param string $a_id
200     * @return ilBadgeType
201     */
202    public function getTypeInstanceByUniqueId($a_id)
203    {
204        $parts = explode("/", $a_id);
205        $comp_id = $parts[0];
206        $type_id = $parts[1];
207        $provider = $this->getProviderInstance($comp_id);
208        if ($provider) {
209            foreach ($provider->getBadgeTypes() as $type) {
210                if ($type->getId() == $type_id) {
211                    return $type;
212                }
213            }
214        }
215    }
216
217    public function getInactiveTypes()
218    {
219        $types = $this->settings->get("inactive_types", null);
220        if ($types) {
221            return unserialize($types);
222        }
223        return array();
224    }
225
226    public function setInactiveTypes(array $a_types = null)
227    {
228        if (is_array($a_types) &&
229            !sizeof($a_types)) {
230            $a_types = null;
231        }
232        $this->settings->set("inactive_types", $a_types !== null
233            ? serialize(array_unique($a_types))
234            : null);
235    }
236
237    /**
238     * Get badges types
239     *
240     * @return ilBadgeType[]
241     */
242    public function getAvailableTypes()
243    {
244        $res = array();
245
246        $inactive = $this->getInactiveTypes();
247        foreach ($this->getComponents() as $component_id) {
248            $provider = $this->getProviderInstance($component_id);
249            if ($provider) {
250                foreach ($provider->getBadgeTypes() as $type) {
251                    $id = $this->getUniqueTypeId($component_id, $type);
252                    if (!in_array($id, $inactive)) {
253                        $res[$id] = $type;
254                    }
255                }
256            }
257        }
258
259        return $res;
260    }
261
262    /**
263     * Get valid badges types for object type
264     *
265     * @param string $a_object_type
266     * @return ilBadgeType[]
267     */
268    public function getAvailableTypesForObjType($a_object_type)
269    {
270        $res = array();
271
272        foreach ($this->getAvailableTypes() as $id => $type) {
273            if (in_array($a_object_type, $type->getValidObjectTypes())) {
274                $res[$id] = $type;
275            }
276        }
277
278        return $res;
279    }
280
281    /**
282     * Get available manual badges for object id
283     *
284     * @param int $a_parent_obj_id
285     * @param string $a_parent_obj_type
286     * @return array id,title
287     */
288    public function getAvailableManualBadges($a_parent_obj_id, $a_parent_obj_type = null)
289    {
290        $res = array();
291
292        if (!$a_parent_obj_type) {
293            $a_parent_obj_type = ilObject::_lookupType($a_parent_obj_id);
294        }
295
296        include_once "./Services/Badge/classes/class.ilBadge.php";
297        $badges = ilBadge::getInstancesByParentId($a_parent_obj_id);
298        foreach (ilBadgeHandler::getInstance()->getAvailableTypesForObjType($a_parent_obj_type) as $type_id => $type) {
299            if (!$type instanceof ilBadgeAuto) {
300                foreach ($badges as $badge) {
301                    if ($badge->getTypeId() == $type_id &&
302                        $badge->isActive()) {
303                        $res[$badge->getId()] = $badge->getTitle();
304                    }
305                }
306            }
307        }
308
309        asort($res);
310        return $res;
311    }
312
313
314
315    //
316    // service/module definition
317    //
318
319    /**
320     * Import component definition
321     *
322     * @param string $a_component_id
323     */
324    public static function updateFromXML($a_component_id)
325    {
326        $handler = self::getInstance();
327        $components = $handler->getComponents();
328        $components[] = $a_component_id;
329        $handler->setComponents($components);
330    }
331
332    /**
333     * Remove component definition
334     *
335     * @param string $a_component_id
336     */
337    public static function clearFromXML($a_component_id)
338    {
339        $handler = self::getInstance();
340        $components = $handler->getComponents();
341        foreach ($components as $idx => $component) {
342            if ($component == $a_component_id) {
343                unset($components[$idx]);
344            }
345        }
346        $handler->setComponents($components);
347    }
348
349
350    //
351    // helper
352    //
353
354    public function isObjectActive($a_obj_id, $a_obj_type = null)
355    {
356        if (!$this->isActive()) {
357            return false;
358        }
359
360        if (!$a_obj_type) {
361            $a_obj_type = ilObject::_lookupType($a_obj_id);
362        }
363
364        if ($a_obj_type != "bdga") {
365            include_once 'Services/Container/classes/class.ilContainer.php';
366            include_once 'Services/Object/classes/class.ilObjectServiceSettingsGUI.php';
367            if (!ilContainer::_lookupContainerSetting(
368                $a_obj_id,
369                ilObjectServiceSettingsGUI::BADGES,
370                false
371            )) {
372                return false;
373            }
374        }
375
376        return true;
377    }
378
379    public function triggerEvaluation($a_type_id, $a_user_id, array $a_params = null)
380    {
381        if (!$this->isActive() ||
382            in_array($a_type_id, $this->getInactiveTypes())) {
383            return;
384        }
385
386        $type = $this->getTypeInstanceByUniqueId($a_type_id);
387        if (!$type ||
388            !$type instanceof ilBadgeAuto) {
389            return;
390        }
391
392        include_once "Services/Badge/classes/class.ilBadge.php";
393        include_once "Services/Badge/classes/class.ilBadgeAssignment.php";
394        $new_badges = array();
395        foreach (ilBadge::getInstancesByType($a_type_id) as $badge) {
396            if ($badge->isActive()) {
397                // already assigned?
398                if (!ilBadgeAssignment::exists($badge->getId(), $a_user_id)) {
399                    if ((bool) $type->evaluate($a_user_id, (array) $a_params, (array) $badge->getConfiguration())) {
400                        $ass = new ilBadgeAssignment($badge->getId(), $a_user_id);
401                        $ass->store();
402
403                        $new_badges[$a_user_id][] = $badge->getId();
404                    }
405                }
406            }
407        }
408
409        $this->sendNotification($new_badges);
410    }
411
412    public function getUserIds($a_parent_ref_id, $a_parent_obj_id = null, $a_parent_type = null)
413    {
414        $tree = $this->tree;
415
416        if (!$a_parent_obj_id) {
417            $a_parent_obj_id = ilObject::_lookupObjectId($a_parent_ref_id);
418        }
419        if (!$a_parent_type) {
420            $a_parent_type = ilObject::_lookupType($a_parent_obj_id);
421        }
422
423        // try to get participants from (parent) course/group
424        switch ($a_parent_type) {
425            case "crs":
426                include_once "Modules/Course/classes/class.ilCourseParticipants.php";
427                $member_obj = ilCourseParticipants::_getInstanceByObjId($a_parent_obj_id);
428                return $member_obj->getMembers();
429
430            case "grp":
431                include_once "Modules/Group/classes/class.ilGroupParticipants.php";
432                $member_obj = ilGroupParticipants::_getInstanceByObjId($a_parent_obj_id);
433                return $member_obj->getMembers();
434
435            default:
436                // walk path to find course or group object and use members of that object
437                $path = $tree->getPathId($a_parent_ref_id);
438                array_pop($path);
439                foreach (array_reverse($path) as $path_ref_id) {
440                    $type = ilObject::_lookupType($path_ref_id, true);
441                    if ($type == "crs" || $type == "grp") {
442                        return $this->getParticipantsForObject($path_ref_id, null, $type);
443                    }
444                }
445                break;
446        }
447    }
448
449
450    //
451    // PATH HANDLING (PUBLISHING)
452    //
453
454    protected function getBasePath()
455    {
456        return ilUtil::getWebspaceDir() . "/pub_badges/";
457    }
458
459    public function getInstancePath(ilBadgeAssignment $a_ass)
460    {
461        $hash = md5($a_ass->getBadgeId() . "_" . $a_ass->getUserId());
462
463        $path = $this->getBasePath() . "instances/" .
464            $a_ass->getBadgeId() . "/" .
465            floor($a_ass->getUserId() / 1000) . "/";
466
467        ilUtil::makeDirParents($path);
468
469        $path .= $hash . ".json";
470
471        return $path;
472    }
473
474    public function countStaticBadgeInstances(ilBadge $a_badge)
475    {
476        $path = $this->getBasePath() . "instances/" . $a_badge->getId();
477        $cnt = 0;
478        if (is_dir($path)) {
479            $this->countStaticBadgeInstancesHelper($cnt, $path);
480        }
481        return $cnt;
482    }
483
484    protected function countStaticBadgeInstancesHelper(&$a_cnt, $a_path)
485    {
486        foreach (glob($a_path . "/*") as $item) {
487            if (is_dir($item)) {
488                $this->countStaticBadgeInstancesHelper($a_cnt, $item);
489            } elseif (substr($item, -5) == ".json") {
490                $a_cnt++;
491            }
492        }
493    }
494
495    public function getBadgePath(ilBadge $a_badge)
496    {
497        $hash = md5($a_badge->getId());
498
499        $path = $this->getBasePath() . "badges/" .
500            floor($a_badge->getId() / 100) . "/" .
501            $hash . "/";
502
503        ilUtil::makeDirParents($path);
504
505        return $path;
506    }
507
508    protected function prepareIssuerJson($a_url)
509    {
510        $json = new stdClass();
511        $json->{"@context"} = "https://w3id.org/openbadges/v1";
512        $json->type = "Issuer";
513        $json->id = $a_url;
514        $json->name = $this->getObiOrganistation();
515        $json->url = ILIAS_HTTP_PATH . "/";
516        $json->email = $this->getObiContact();
517
518        return $json;
519    }
520
521    public function getIssuerStaticUrl()
522    {
523        $path = $this->getBasePath() . "issuer/";
524        ilUtil::makeDirParents($path);
525        $path .= "issuer.json";
526
527        $url = ILIAS_HTTP_PATH . substr($path, 1);
528
529        if (!file_exists($path)) {
530            $json = json_encode($this->prepareIssuerJson($url));
531            file_put_contents($path, $json);
532        }
533
534        return $url;
535    }
536
537    public function rebuildIssuerStaticUrl()
538    {
539        $path = $this->getBasePath() . "issuer/issuer.json";
540        if (file_exists($path)) {
541            unlink($path);
542        }
543        $this->getIssuerStaticUrl();
544    }
545
546
547    //
548    // notification
549    //
550
551    public function sendNotification(array $a_user_map, $a_parent_ref_id = null)
552    {
553        $badges = array();
554
555        include_once "Services/Badge/classes/class.ilBadge.php";
556        include_once "Services/Badge/classes/class.ilBadgeAssignment.php";
557        include_once "Services/Notification/classes/class.ilSystemNotification.php";
558        include_once "Services/Link/classes/class.ilLink.php";
559
560        foreach ($a_user_map as $user_id => $badge_ids) {
561            $user_badges = array();
562
563            foreach ($badge_ids as $badge_id) {
564                // making extra sure
565                if (!ilBadgeAssignment::exists($badge_id, $user_id)) {
566                    continue;
567                }
568
569                if (!array_key_exists($badge_id, $badges)) {
570                    $badges[$badge_id] = new ilBadge($badge_id);
571                }
572
573                $badge = $badges[$badge_id];
574
575                $user_badges[] = $badge->getTitle();
576            }
577
578            if (sizeof($user_badges)) {
579                // compose and send mail
580
581                $ntf = new ilSystemNotification(false);
582                $ntf->setLangModules(array("badge"));
583
584                $ntf->setRefId($a_parent_ref_id);
585                $ntf->setGotoLangId("badge_notification_parent_goto");
586
587                // user specific language
588                $lng = $ntf->getUserLanguage($user_id);
589
590                $ntf->setIntroductionLangId("badge_notification_body");
591
592                $ntf->addAdditionalInfo("badge_notification_badges", implode("\n", $user_badges), true);
593
594                $url = ilLink::_getLink($user_id, "usr", array(), "_bdg");
595                $ntf->addAdditionalInfo("badge_notification_badges_goto", $url);
596
597                $ntf->setReasonLangId("badge_notification_reason");
598
599                // force email
600                $mail = new ilMail(ANONYMOUS_USER_ID);
601                $mail->enableSOAP(false);
602                $mail->sendMail(
603                    ilObjUser::_lookupEmail($user_id),
604                    null,
605                    null,
606                    $lng->txt("badge_notification_subject"),
607                    $ntf->composeAndGetMessage($user_id, null, "read", true),
608                    null,
609                    array("system")
610                );
611
612
613                // osd
614                // bug #24562
615                if (ilContext::hasHTML()) {
616                    $osd_params = array("badge_list" => "<br />" . implode("<br />", $user_badges));
617
618                    require_once "Services/Notifications/classes/class.ilNotificationConfig.php";
619                    $notification = new ilNotificationConfig("osd_main");
620                    $notification->setTitleVar("badge_notification_subject", array(), "badge");
621                    $notification->setShortDescriptionVar("badge_notification_osd", $osd_params, "badge");
622                    $notification->setLongDescriptionVar("", $osd_params, "");
623                    $notification->setAutoDisable(false);
624                    $notification->setLink($url);
625                    $notification->setIconPath(ilUtil::getImagePath('icon_bdga.svg'));
626                    $notification->setValidForSeconds(ilNotificationConfig::TTL_SHORT);
627                    $notification->setVisibleForSeconds(ilNotificationConfig::DEFAULT_TTS);
628                    $notification->notifyByUsers(array($user_id));
629                }
630            }
631        }
632    }
633}
634