1<?php
2
3
4/**
5 * Class ilLTIAppEventListener
6 */
7class ilLTIAppEventListener implements \ilAppEventListener
8{
9    /**
10     * @var \ilLTIAppEventListener
11     */
12    private static $instance = null;
13
14    /**
15     * @var \ilLogger
16     */
17    private $logger = null;
18
19    /**
20     * @var ilLTIDataConnector|null
21     */
22    private $connector = null;
23
24
25    /**
26     * ilLTIAppEventListener constructor.
27     */
28    protected function __construct()
29    {
30        global $DIC;
31
32        $this->logger = $DIC->logger()->lti();
33        $this->connector = new ilLTIDataConnector();
34    }
35
36    /**
37     * @return \ilLTIAppEventListener
38     */
39    protected static function getInstance()
40    {
41        if (!self::$instance instanceof \ilLTIAppEventListener) {
42            self::$instance = new self();
43        }
44        return self::$instance;
45    }
46
47
48    /**
49     * Handle update status
50     */
51    protected function handleUpdateStatus($a_obj_id, $a_usr_id, $a_status, $a_percentage)
52    {
53        $this->logger->debug('Handle update status');
54        $auth_mode = ilObjUser::_lookupAuthMode($a_usr_id);
55        if (!$this->isLTIAuthMode($auth_mode)) {
56            $this->logger->debug('Ignoring update for non-LTI-user.');
57            return false;
58        }
59        $ext_account = ilObjUser::_lookupExternalAccount($a_usr_id);
60        list($lti, $consumer) = explode('_', $auth_mode);
61
62        // iterate through all references
63        $refs = ilObject::_getAllReferences($a_obj_id);
64        foreach ((array) $refs as $ref_id) {
65            $resources = $this->connector->lookupResourcesForUserObjectRelation(
66                $ref_id,
67                $ext_account,
68                $consumer
69            );
70
71            $this->logger->debug('Resources for update:');
72            $this->logger->dump($resources, ilLogLevel::DEBUG);
73
74            foreach ($resources as $resource) {
75                $this->tryOutcomeService($resource, $ext_account, $a_status, $a_percentage);
76            }
77        }
78    }
79
80
81    /**
82     * @param ilDateTime $since
83     * @throws ilDateTimeException
84     */
85    protected function doCronUpdate(ilDateTime $since)
86    {
87        $this->logger->debug('Starting cron update for lti outcome service');
88
89        $resources = $this->connector->lookupResourcesForAllUsersSinceDate($since);
90        foreach ($resources as $consumer_ext_account => $user_resources) {
91            list($consumer, $ext_account) = explode('__', $consumer_ext_account, 2);
92
93            $login = ilObjUser::_checkExternalAuthAccount('lti_' . $consumer, $ext_account);
94            if (!$login) {
95                $this->logger->info('No user found for lti_' . $consumer . ' -> ' . $ext_account);
96                continue;
97            }
98            $usr_id = ilObjUser::_lookupId($login);
99            foreach ($user_resources as $resource_info) {
100                $this->logger->debug('Found resource: ' . $resource_info);
101                list($resource_id, $resource_ref_id) = explode('__', $resource_info);
102
103                // lookup lp status
104                $status = ilLPStatus::_lookupStatus(
105                    ilObject::_lookupObjId($resource_ref_id),
106                    $usr_id
107                );
108                $percentage = ilLPStatus::_lookupPercentage(
109                    ilObject::_lookupObjId($resource_ref_id),
110                    $usr_id
111                );
112                $this->tryOutcomeService($resource_id, $ext_account, $status, $percentage);
113            }
114        }
115    }
116
117    /**
118     * @param $a_usr_id
119     * @return bool
120     */
121    protected function isLTIAuthMode($auth_mode)
122    {
123        return strpos($auth_mode, 'lti_') === 0;
124    }
125
126
127    /**
128     * try outcome service
129     */
130    protected function tryOutcomeService($resource, $ext_account, $a_status, $a_percentage)
131    {
132        $resource_link = \IMSGlobal\LTI\ToolProvider\ResourceLink::fromRecordId($resource, $this->connector);
133        if (!$resource_link->hasOutcomesService()) {
134            $this->logger->debug('No outcome service available for resource id: ' . $resource);
135            return false;
136        }
137        $this->logger->debug('Trying outcome service with status ' . $a_status . ' and percentage ' . $a_percentage);
138        $user = \IMSGlobal\LTI\ToolProvider\User::fromResourceLink($resource_link, $ext_account);
139
140        if ($a_status == ilLPStatus::LP_STATUS_COMPLETED_NUM) {
141            $score = 1;
142        } elseif (
143            $a_status == ilLPStatus::LP_STATUS_FAILED_NUM ||
144            $a_status == ilLPStatus::LP_STATUS_NOT_ATTEMPTED_NUM
145        ) {
146            $score = 0;
147        } elseif (!$a_percentage) {
148            $score = 0;
149        } else {
150            $score = (int) $a_percentage / 100;
151        }
152
153        $this->logger->debug('Sending score: ' . (string) $score);
154
155        $outcome = new \IMSGlobal\LTI\ToolProvider\Outcome($score);
156
157        $resource_link->doOutcomesService(
158            \IMSGlobal\LTI\ToolProvider\ResourceLink::EXT_WRITE,
159            $outcome,
160            $user
161        );
162    }
163
164
165    /**
166     * @inheritdoc
167     */
168    public static function handleEvent($a_component, $a_event, $a_parameter)
169    {
170        global $DIC;
171
172        $logger = $DIC->logger()->lti()->debug('Handling event: ' . $a_event . ' from ' . $a_component);
173
174        switch ($a_component) {
175            case 'Services/Tracking':
176                if ($a_event == 'updateStatus') {
177                    $listener = self::getInstance();
178                    $listener->handleUpdateStatus(
179                        $a_parameter['obj_id'],
180                        $a_parameter['usr_id'],
181                        $a_parameter['status'],
182                        $a_parameter['percentage']
183                    );
184                }
185                break;
186        }
187    }
188
189    /**
190     * @param ilDateTime $since
191     * @return bool
192     * @throws ilDateTimeException
193     */
194    public static function handleCronUpdate(ilDateTime $since)
195    {
196        $listener = self::getInstance();
197        $listener->doCronUpdate($since);
198        return true;
199    }
200
201
202    public static function handleOutcomeWithoutLP($a_obj_id, $a_usr_id, $a_percentage)
203    {
204        global $DIC;
205        $score = 0;
206
207        $auth_mode = ilObjUser::_lookupAuthMode($a_usr_id);
208        if (strpos($auth_mode, 'lti_') === false) {
209            $DIC->logger()->lti()->debug('Ignoring outcome for non-LTI-user.');
210            return false;
211        }
212        //check if LearningPress enabled
213        $olp = ilObjectLP::getInstance($a_obj_id);
214        if (ilLPObjSettings::LP_MODE_DEACTIVATED != $olp->getCurrentMode())
215        {
216            $DIC->logger()->lti()->debug('Ignoring outcome if LP is activated.');
217            return false;
218        }
219
220        if ($a_percentage && $a_percentage > 0) {
221            $score = round($a_percentage/100, 4);
222        }
223
224        $connector = new ilLTIDataConnector();
225        $ext_account = ilObjUser::_lookupExternalAccount($a_usr_id);
226        list($lti, $consumer) = explode('_', $auth_mode);
227
228        // iterate through all references
229        $refs = ilObject::_getAllReferences($a_obj_id);
230        foreach ((array) $refs as $ref_id) {
231            $resources = $connector->lookupResourcesForUserObjectRelation(
232                $ref_id,
233                $ext_account,
234                $consumer
235            );
236
237            $DIC->logger()->lti()->debug('Resources for update: '.$resources);
238
239            foreach ($resources as $resource) {
240                // $this->tryOutcomeService($resource, $ext_account, $a_status, $a_percentage);
241                $resource_link = \IMSGlobal\LTI\ToolProvider\ResourceLink::fromRecordId($resource, $connector);
242                if ($resource_link->hasOutcomesService()) {
243                    $user = \IMSGlobal\LTI\ToolProvider\User::fromResourceLink($resource_link, $ext_account);
244                    $DIC->logger()->lti()->debug('Sending score: ' . (string) $score);
245                    $outcome = new \IMSGlobal\LTI\ToolProvider\Outcome($score);
246
247                    $resource_link->doOutcomesService(
248                        \IMSGlobal\LTI\ToolProvider\ResourceLink::EXT_WRITE,
249                        $outcome,
250                        $user
251                    );
252                }
253            }
254        }
255    }
256
257}
258