1<?php
2
3/* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
4
5include_once './webservice/soap/classes/class.ilSoapAdministration.php';
6
7/**
8 * This class handles all DB changes necessary for fraunhofer
9 *
10 * @author Stefan Meyer <smeyer.ilias@gmx.de>
11 * @version $Id$
12 *
13 */
14class ilSoapLearningProgressAdministration extends ilSoapAdministration
15{
16    protected static $DELETE_PROGRESS_FILTER_TYPES = array('sahs', 'tst');
17
18    const PROGRESS_FILTER_ALL = 0;
19    const PROGRESS_FILTER_IN_PROGRESS = 1;
20    const PROGRESS_FILTER_COMPLETED = 2;
21    const PROGRESS_FILTER_FAILED = 3;
22    const PROGRESS_FILTER_NOT_ATTEMPTED = 4;
23
24    const SOAP_LP_ERROR_AUTHENTICATION = 50;
25    const SOAP_LP_ERROR_INVALID_FILTER = 52;
26    const SOAP_LP_ERROR_INVALID_REF_ID = 54;
27    const SOAP_LP_ERROR_LP_NOT_AVAILABLE = 56;
28    const SOAP_LP_ERROR_NO_PERMISSION = 58;
29    const SOAP_LP_ERROR_LP_NOT_ENABLED = 60;
30
31    protected static $PROGRESS_INFO_TYPES = array(
32        self::PROGRESS_FILTER_ALL,
33        self::PROGRESS_FILTER_IN_PROGRESS,
34        self::PROGRESS_FILTER_COMPLETED,
35        self::PROGRESS_FILTER_FAILED,
36        self::PROGRESS_FILTER_NOT_ATTEMPTED
37    );
38
39
40
41    const USER_FILTER_ALL = -1;
42
43    /**
44     * Delete progress of users and objects
45     * Implemented for
46     */
47    public function deleteProgress($sid, $ref_ids, $usr_ids, $type_filter, $progress_filter)
48    {
49        $this->initAuth($sid);
50        $this->initIlias();
51
52        if (!is_array($usr_ids)) {
53            $usr_ids = (array) $usr_ids;
54        }
55        if (!is_array($type_filter)) {
56            $type_filter = (array) $type_filter;
57        }
58
59        // Check session
60        if (!$this->__checkSession($sid)) {
61            return $this->__raiseError($this->__getMessage(), $this->__getMessageCode());
62        }
63
64        // Check filter
65        if (array_diff((array) $type_filter, self::$DELETE_PROGRESS_FILTER_TYPES)) {
66            return $this->__raiseError('Invalid filter type given', 'Client');
67        }
68
69        include_once 'Services/User/classes/class.ilObjUser.php';
70        if (!in_array(self::USER_FILTER_ALL, $usr_ids) and !ilObjUser::userExists($usr_ids)) {
71            return $this->__raiseError('Invalid user ids given', 'Client');
72        }
73
74        $valid_refs = array();
75        foreach ((array) $ref_ids as $ref_id) {
76            $obj_id = ilObject::_lookupObjId($ref_id);
77            $type = ilObject::_lookupType($obj_id);
78
79            // All containers
80            if ($GLOBALS['DIC']['objDefinition']->isContainer($type)) {
81                $all_sub_objs = array();
82                foreach (($type_filter) as $type_filter_item) {
83                    $sub_objs = $GLOBALS['DIC']['tree']->getSubTree(
84                        $GLOBALS['DIC']['tree']->getNodeData($ref_id),
85                        false,
86                        $type_filter_item
87                    );
88                    $all_sub_objs = array_merge($all_sub_objs, $sub_objs);
89                }
90
91                foreach ($all_sub_objs as $child_ref) {
92                    $child_type = ilObject::_lookupType(ilObject::_lookupObjId($child_ref));
93                    if (!$GLOBALS['DIC']['ilAccess']->checkAccess('write', '', $child_ref)) {
94                        return $this->__raiseError('Permission denied for : ' . $ref_id . ' -> type ' . $type, 'Client');
95                    }
96                    $valid_refs[] = $child_ref;
97                }
98            } elseif (in_array($type, $type_filter)) {
99                if (!$GLOBALS['DIC']['ilAccess']->checkAccess('write', '', $ref_id)) {
100                    return $this->__raiseError('Permission denied for : ' . $ref_id . ' -> type ' . $type, 'Client');
101                }
102                $valid_refs[] = $ref_id;
103            } else {
104                return $this->__raiseError('Invalid object type given for : ' . $ref_id . ' -> type ' . $type, 'Client');
105            }
106        }
107
108        // Delete tracking data
109        foreach ($valid_refs as $ref_id) {
110            include_once './Services/Object/classes/class.ilObjectFactory.php';
111            $obj = ilObjectFactory::getInstanceByRefId($ref_id, false);
112
113            if (!$obj instanceof ilObject) {
114                return $this->__raiseError('Invalid reference id given : ' . $ref_id . ' -> type ' . $type, 'Client');
115            }
116
117            // filter users
118            $valid_users = $this->applyProgressFilter($obj->getId(), (array) $usr_ids, (array) $progress_filter);
119
120            switch ($obj->getType()) {
121                case 'sahs':
122                    include_once './Modules/ScormAicc/classes/class.ilObjSAHSLearningModule.php';
123                    $subtype = ilObjSAHSLearningModule::_lookupSubType($obj->getId());
124
125                    switch ($subtype) {
126                        case 'scorm':
127                            $this->deleteScormTracking($obj->getId(), (array) $valid_users);
128                            break;
129
130                        case 'scorm2004':
131                            $this->deleteScorm2004Tracking($obj->getId(), (array) $valid_users);
132                            break;
133                    }
134                    break;
135
136                case 'tst':
137
138                    /** @var $obj ilObjTest */
139                    $obj->removeTestResultsFromSoapLpAdministration(array_values((array) $valid_users));
140                    break;
141            }
142
143            // Refresh status
144            include_once './Services/Tracking/classes/class.ilLPStatusWrapper.php';
145            ilLPStatusWrapper::_resetInfoCaches($obj->getId());
146            ilLPStatusWrapper::_refreshStatus($obj->getId(), $valid_users);
147        }
148        return true;
149    }
150
151    /**
152     * @param string $sid
153     * @param int $a_ref_id
154     * @param int[] $a_progress_filter
155     * @return soap_fault|SoapFault|string
156     */
157    public function getProgressInfo($sid, $a_ref_id, $a_progress_filter)
158    {
159        global $DIC;
160
161        $this->initAuth($sid);
162        $this->initIlias();
163
164        $ilAccess = $DIC->access();
165
166        // Check session
167        if (!$this->__checkSession($sid)) {
168            return $this->__raiseError(
169                'Error ' . self::SOAP_LP_ERROR_AUTHENTICATION . ':' . $this->__getMessage(),
170                self::SOAP_LP_ERROR_AUTHENTICATION
171            );
172        }
173
174        // Check filter
175        if (array_diff((array) $a_progress_filter, self::$PROGRESS_INFO_TYPES)) {
176            return $this->__raiseError(
177                'Error ' . self::SOAP_LP_ERROR_INVALID_FILTER . ': Invalid filter type given',
178                self::SOAP_LP_ERROR_INVALID_FILTER
179            );
180        }
181        // Check LP enabled
182        include_once("Services/Tracking/classes/class.ilObjUserTracking.php");
183        if (!ilObjUserTracking::_enabledLearningProgress()) {
184            return $this->__raiseError(
185                'Error ' . self::SOAP_LP_ERROR_LP_NOT_ENABLED . ': Learning progress not enabled in ILIAS',
186                self::SOAP_LP_ERROR_LP_NOT_ENABLED
187            );
188        }
189
190        include_once './Services/Object/classes/class.ilObjectFactory.php';
191        $obj = ilObjectFactory::getInstanceByRefId($a_ref_id, false);
192        if (!$obj instanceof ilObject) {
193            return $this->__raiseError(
194                'Error ' . self::SOAP_LP_ERROR_INVALID_REF_ID . ': Invalid reference id ' . $a_ref_id . ' given',
195                self::SOAP_LP_ERROR_INVALID_REF_ID
196            );
197        }
198
199        // check lp available
200        include_once './Services/Tracking/classes/class.ilLPObjSettings.php';
201        $mode = ilLPObjSettings::_lookupDBMode($obj->getId());
202        if ($mode == ilLPObjSettings::LP_MODE_UNDEFINED) {
203            return $this->__raiseError(
204                'Error ' . self::SOAP_LP_ERROR_LP_NOT_AVAILABLE . ': Learning progress not available for objects of type ' .
205                $obj->getType(),
206                self::SOAP_LP_ERROR_LP_NOT_AVAILABLE
207            );
208        }
209
210        // check rbac
211        /**
212         * @var ilAccess
213         */
214        if (!$ilAccess->checkRbacOrPositionPermissionAccess('read_learning_progress', 'read_learning_progress', $a_ref_id)) {
215            return $this->__raiseError(
216                'Error ' . self::SOAP_LP_ERROR_NO_PERMISSION . ': No Permission to access learning progress in this object',
217                self::SOAP_LP_ERROR_NO_PERMISSION
218            );
219        }
220
221        include_once './Services/Xml/classes/class.ilXmlWriter.php';
222        $writer = new ilXmlWriter();
223        $writer->xmlStartTag(
224            'LearningProgressInfo',
225            array(
226                    'ref_id' => $obj->getRefId(),
227                    'type' => $obj->getType()
228                )
229        );
230
231        $writer->xmlStartTag('LearningProgressSummary');
232
233        include_once './Services/Tracking/classes/class.ilLPStatusWrapper.php';
234        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_COMPLETED, $a_progress_filter)) {
235            $completed = ilLPStatusWrapper::_getCompleted($obj->getId());
236            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
237                'read_learning_progress',
238                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
239                $a_ref_id,
240                $completed
241            );
242            $completed = count($completed);
243
244            $writer->xmlElement(
245                'Status',
246                array(
247                        'type'  => self::PROGRESS_FILTER_COMPLETED,
248                        'num'	=> (int) $completed
249                    )
250            );
251        }
252        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_IN_PROGRESS, $a_progress_filter)) {
253            $completed = ilLPStatusWrapper::_getInProgress($obj->getId());
254            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
255                'read_learning_progress',
256                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
257                $a_ref_id,
258                $completed
259            );
260            $completed = count($completed);
261
262            $writer->xmlElement(
263                'Status',
264                array(
265                        'type'  => self::PROGRESS_FILTER_IN_PROGRESS,
266                        'num'	=> (int) $completed
267                    )
268            );
269        }
270        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_FAILED, $a_progress_filter)) {
271            $completed = ilLPStatusWrapper::_getFailed($obj->getId());
272            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
273                'read_learning_progress',
274                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
275                $a_ref_id,
276                $completed
277            );
278            $completed = count($completed);
279
280            $writer->xmlElement(
281                'Status',
282                array(
283                        'type'  => self::PROGRESS_FILTER_FAILED,
284                        'num'	=> (int) $completed
285                    )
286            );
287        }
288        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_NOT_ATTEMPTED, $a_progress_filter)) {
289            $completed = ilLPStatusWrapper::_getNotAttempted($obj->getId());
290            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
291                'read_learning_progress',
292                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
293                $a_ref_id,
294                $completed
295            );
296            $completed = count($completed);
297
298            $writer->xmlElement(
299                'Status',
300                array(
301                        'type'  => self::PROGRESS_FILTER_NOT_ATTEMPTED,
302                        'num'	=> (int) $completed
303                    )
304            );
305        }
306        $writer->xmlEndTag('LearningProgressSummary');
307
308
309        $writer->xmlStartTag('UserProgress');
310        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_COMPLETED, $a_progress_filter)) {
311            $completed = ilLPStatusWrapper::_getCompleted($obj->getId());
312            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
313                'read_learning_progress',
314                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
315                $a_ref_id,
316                $completed
317            );
318
319            $this->addUserProgress($writer, $completed, self::PROGRESS_FILTER_COMPLETED);
320        }
321        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_IN_PROGRESS, $a_progress_filter)) {
322            $completed = ilLPStatusWrapper::_getInProgress($obj->getId());
323            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
324                'read_learning_progress',
325                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
326                $a_ref_id,
327                $completed
328            );
329            $this->addUserProgress($writer, $completed, self::PROGRESS_FILTER_IN_PROGRESS);
330        }
331        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_FAILED, $a_progress_filter)) {
332            $completed = ilLPStatusWrapper::_getFailed($obj->getId());
333            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
334                'read_learning_progress',
335                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
336                $a_ref_id,
337                $completed
338            );
339            $this->addUserProgress($writer, $completed, self::PROGRESS_FILTER_FAILED);
340        }
341        if (in_array(self::PROGRESS_FILTER_ALL, $a_progress_filter) or in_array(self::PROGRESS_FILTER_NOT_ATTEMPTED, $a_progress_filter)) {
342            $completed = ilLPStatusWrapper::_getNotAttempted($obj->getId());
343            $completed = $GLOBALS['DIC']->access()->filterUserIdsByRbacOrPositionOfCurrentUser(
344                'read_learning_progress',
345                ilOrgUnitOperation::OP_READ_LEARNING_PROGRESS,
346                $a_ref_id,
347                $completed
348            );
349
350            $this->addUserProgress($writer, $completed, self::PROGRESS_FILTER_NOT_ATTEMPTED);
351        }
352        $writer->xmlEndTag('UserProgress');
353        $writer->xmlEndTag('LearningProgressInfo');
354
355        return $writer->xmlDumpMem();
356    }
357
358    protected function addUserProgress(ilXmlWriter $writer, $users, $a_type)
359    {
360        foreach ($users  as $user_id) {
361            $writer->xmlStartTag(
362                'User',
363                array(
364                        'id' => $user_id,
365                        'status' => $a_type
366                    )
367            );
368
369            $info = ilObjUser::_lookupName($user_id);
370            $writer->xmlElement('Login', array(), (string) $info['login']);
371            $writer->xmlElement('Firstname', array(), (string) $info['firstname']);
372            $writer->xmlElement('Lastname', array(), (string) $info['lastname']);
373            $writer->xmlEndTag('User');
374        }
375    }
376
377
378    /**
379     * Apply progress filter
380     * @param int $obj_id
381     * @param array $usr_ids
382     * @param array $filter
383     *
384     * @return array $filtered_users
385     */
386    protected function applyProgressFilter($obj_id, array $usr_ids, array $filter)
387    {
388        include_once './Services/Tracking/classes/class.ilLPStatusWrapper.php';
389
390
391        $all_users = array();
392        if (in_array(self::USER_FILTER_ALL, $usr_ids)) {
393            $all_users = array_unique(
394                array_merge(
395                        ilLPStatusWrapper::_getInProgress($obj_id),
396                        ilLPStatusWrapper::_getCompleted($obj_id),
397                        ilLPStatusWrapper::_getFailed($obj_id)
398                    )
399            );
400        } else {
401            $all_users = $usr_ids;
402        }
403
404        if (!$filter or in_array(self::PROGRESS_FILTER_ALL, $filter)) {
405            $GLOBALS['DIC']['log']->write(__METHOD__ . ': Deleting all progress data');
406            return $all_users;
407        }
408
409        $filter_users = array();
410        if (in_array(self::PROGRESS_FILTER_IN_PROGRESS, $filter)) {
411            $GLOBALS['DIC']['log']->write(__METHOD__ . ': Filtering  in progress.');
412            $filter_users = array_merge($filter, ilLPStatusWrapper::_getInProgress($obj_id));
413        }
414        if (in_array(self::PROGRESS_FILTER_COMPLETED, $filter)) {
415            $GLOBALS['DIC']['log']->write(__METHOD__ . ': Filtering  completed.');
416            $filter_users = array_merge($filter, ilLPStatusWrapper::_getCompleted($obj_id));
417        }
418        if (in_array(self::PROGRESS_FILTER_FAILED, $filter)) {
419            $GLOBALS['DIC']['log']->write(__METHOD__ . ': Filtering  failed.');
420            $filter_users = array_merge($filter, ilLPStatusWrapper::_getFailed($obj_id));
421        }
422
423        // Build intersection
424        return array_intersect($all_users, $filter_users);
425    }
426
427    /**
428     * Delete SCORM Tracking
429     * @global type $ilDB
430     * @param type $a_obj_id
431     * @param type $a_usr_ids
432     * @return boolean
433     */
434    protected function deleteScormTracking($a_obj_id, $a_usr_ids)
435    {
436        global $DIC;
437
438        $ilDB = $DIC['ilDB'];
439
440        $query = 'DELETE FROM scorm_tracking ' .
441            'WHERE ' . $ilDB->in('user_id', $a_usr_ids, false, 'integer') . ' ' .
442            'AND obj_id = ' . $ilDB->quote($a_obj_id, 'integer') . ' ';
443        $res = $ilDB->manipulate($query);
444        return true;
445    }
446
447    /**
448     * Delete scorm 2004 tracking
449     * @param type $a_obj_id
450     * @param type $a_usr_ids
451     */
452    protected function deleteScorm2004Tracking($a_obj_id, $a_usr_ids)
453    {
454        global $DIC;
455
456        $ilDB = $DIC['ilDB'];
457
458        $query = 'SELECT cp_node_id FROM cp_node ' .
459            'WHERE nodename = ' . $ilDB->quote('item', 'text') . ' ' .
460            'AND cp_node.slm_id = ' . $ilDB->quote($a_obj_id, 'integer');
461        $res = $ilDB->query($query);
462
463        $scos = array();
464        while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
465            $scos[] = $row->cp_node_id;
466        }
467
468        $query = 'DELETE FROM cmi_node ' .
469                'WHERE ' . $ilDB->in('user_id', (array) $a_usr_ids, false, 'integer') . ' ' .
470                'AND ' . $ilDB->in('cp_node_id', $scos, false, 'integer');
471        $ilDB->manipulate($query);
472    }
473
474    /**
475     * Get learning progress changes
476     */
477    public function getLearningProgressChanges($sid, $timestamp, $include_ref_ids, $type_filter)
478    {
479        $this->initAuth($sid);
480        $this->initIlias();
481
482        if (!$this->__checkSession($sid)) {
483            return $this->__raiseError($this->__getMessage(), $this->__getMessageCode());
484        }
485        global $DIC;
486
487        $rbacsystem = $DIC['rbacsystem'];
488        $tree = $DIC['tree'];
489        $ilLog = $DIC['ilLog'];
490
491        // check administrator
492        $types = "";
493        if (is_array($type_filter)) {
494            $types = implode(",", $type_filter);
495        }
496
497        // output lp changes as xml
498        try {
499            include_once './Services/Tracking/classes/class.ilLPXmlWriter.php';
500            $writer = new ilLPXmlWriter(true);
501            $writer->setTimestamp($timestamp);
502            $writer->setIncludeRefIds($include_ref_ids);
503            $writer->setTypeFilter($type_filter);
504            $writer->write();
505
506            return $writer->xmlDumpMem(true);
507        } catch (UnexpectedValueException $e) {
508            return $this->__raiseError($e->getMessage(), 'Client');
509        }
510    }
511}
512