Rest::getAuditItems()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 21
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\ExtraFieldValues;
7
use Chamilo\CoreBundle\Entity\Session;
8
use Chamilo\CourseBundle\Entity\CLpCategory;
9
use Chamilo\CourseBundle\Entity\CNotebook;
10
use Chamilo\CourseBundle\Entity\Repository\CNotebookRepository;
11
use Chamilo\UserBundle\Entity\User;
12
use Symfony\Component\HttpFoundation\ParameterBag;
13
use Symfony\Component\HttpFoundation\Request as HttpRequest;
14
15
/**
16
 * Class RestApi.
17
 */
18
class Rest extends WebService
19
{
20
    public const SERVICE_NAME = 'MsgREST';
21
    public const EXTRA_FIELD_GCM_REGISTRATION = 'gcm_registration_id';
22
23
    public const GET_AUTH = 'authenticate';
24
    public const SAVE_GCM_ID = 'gcm_id';
25
    public const LOGOUT = 'logout';
26
27
    public const GET_USER_MESSAGES = 'user_messages';
28
    public const GET_USER_MESSAGES_RECEIVED = 'user_messages_received';
29
    public const DELETE_USER_MESSAGE = 'delete_user_message';
30
    public const GET_USER_MESSAGES_SENT = 'user_messages_sent';
31
    public const GET_COUNT_NEW_MESSAGES = 'get_count_new_messages';
32
    public const SET_MESSAGE_READ = 'set_message_read';
33
    public const POST_USER_MESSAGE_READ = 'user_message_read';
34
    public const POST_USER_MESSAGE_UNREAD = 'user_message_unread';
35
    public const SAVE_USER_MESSAGE = 'save_user_message';
36
    public const GET_MESSAGE_USERS = 'message_users';
37
    public const VIEW_MESSAGE = 'view_message';
38
39
    public const GET_USER_COURSES = 'user_courses';
40
    public const GET_USER_COURSES_BY_DATES = 'user_courses_by_dates';
41
    public const GET_USER_SESSIONS = 'user_sessions';
42
43
    public const VIEW_PROFILE = 'view_user_profile';
44
    public const GET_PROFILE = 'user_profile';
45
    public const GET_PROFILES_BY_EXTRA_FIELD = 'users_profiles_by_extra_field';
46
47
    public const VIEW_MY_COURSES = 'view_my_courses';
48
    public const VIEW_COURSE_HOME = 'view_course_home';
49
    public const GET_COURSE_INFO = 'course_info';
50
    public const GET_COURSE_DESCRIPTIONS = 'course_descriptions';
51
    public const GET_COURSE_DOCUMENTS = 'course_documents';
52
    public const GET_COURSE_ANNOUNCEMENTS = 'course_announcements';
53
    public const GET_COURSE_ANNOUNCEMENT = 'course_announcement';
54
    public const GET_COURSE_AGENDA = 'course_agenda';
55
    public const GET_COURSE_NOTEBOOKS = 'course_notebooks';
56
    public const GET_COURSE_FORUM_CATEGORIES = 'course_forumcategories';
57
    public const GET_COURSE_FORUM = 'course_forum';
58
    public const GET_COURSE_FORUM_THREAD = 'course_forumthread';
59
    public const GET_COURSE_LEARNPATHS = 'course_learnpaths';
60
    public const GET_COURSE_LEARNPATH = 'course_learnpath';
61
    public const GET_COURSE_LP_PROGRESS = 'course_lp_progress';
62
    public const GET_COURSE_LINKS = 'course_links';
63
    public const GET_COURSE_WORKS = 'course_works';
64
    public const GET_COURSE_EXERCISES = 'course_exercises';
65
    public const GET_COURSES_DETAILS_BY_EXTRA_FIELD = 'courses_details_by_extra_field';
66
    public const GET_COURSE_BY_CODE = 'course_details_by_code';
67
68
    public const SAVE_COURSE_NOTEBOOK = 'save_course_notebook';
69
70
    public const SAVE_FORUM_POST = 'save_forum_post';
71
    public const SAVE_FORUM_THREAD = 'save_forum_thread';
72
    public const SET_THREAD_NOTIFY = 'set_thread_notify';
73
    public const DOWNLOAD_FORUM_ATTACHMENT = 'download_forum_attachment';
74
75
    public const GET_WORK_LIST = 'get_work_list';
76
    public const GET_WORK_STUDENTS_WITHOUT_PUBLICATIONS = 'get_work_students_without_publications';
77
    public const GET_WORK_USERS = 'get_work_users';
78
    public const GET_WORK_STUDENT_LIST = 'get_work_student_list';
79
    public const PUT_WORK_STUDENT_ITEM_VISIBILITY = 'put_course_work_visibility';
80
    public const DELETE_WORK_STUDENT_ITEM = 'delete_work_student_item';
81
    public const DELETE_WORK_CORRECTIONS = 'delete_work_corrections';
82
    public const DOWNLOAD_WORK_FOLDER = 'download_work_folder';
83
    public const DOWNLOAD_WORK_COMMENT_ATTACHMENT = 'download_work_comment_attachment';
84
    public const DOWNLOAD_WORK = 'download_work';
85
86
    public const VIEW_DOCUMENT_IN_FRAME = 'view_document_in_frame';
87
88
    public const VIEW_QUIZ_TOOL = 'view_quiz_tool';
89
90
    public const VIEW_SURVEY_TOOL = 'view_survey_tool';
91
92
    public const CREATE_CAMPUS = 'add_campus';
93
    public const EDIT_CAMPUS = 'edit_campus';
94
    public const DELETE_CAMPUS = 'delete_campus';
95
96
    public const GET_USERS = 'get_users';
97
    public const GET_USER_INFO_FROM_USERNAME = 'get_user_info_from_username';
98
    public const USERNAME_EXIST = 'username_exist';
99
    public const SAVE_USER = 'save_user';
100
    public const SAVE_USER_GET_APIKEY = 'save_user_get_apikey';
101
    public const SAVE_USER_JSON = 'save_user_json';
102
    public const UPDATE_USER_FROM_USERNAME = 'update_user_from_username';
103
    public const UPDATE_USER_APIKEY = 'update_user_apikey';
104
    public const DELETE_USER = 'delete_user';
105
    public const GET_USERS_API_KEYS = 'get_users_api_keys';
106
    public const GET_USER_API_KEY = 'get_user_api_key';
107
    public const GET_USER_LAST_CONNEXION = 'get_user_last_connexion';
108
    public const GET_USER_TOTAL_CONNEXION_TIME = 'get_user_total_connexion_time';
109
    public const GET_USER_PROGRESS_AND_TIME_IN_SESSION = 'get_user_progress_and_time_in_session';
110
    public const GET_USER_SUB_GROUP = 'get_user_sub_group';
111
112
    public const GET_COURSES = 'get_courses';
113
    public const GET_COURSES_FROM_EXTRA_FIELD = 'get_courses_from_extra_field';
114
    public const SAVE_COURSE = 'save_course';
115
    public const DELETE_COURSE = 'delete_course';
116
    public const GET_SESSION_FROM_EXTRA_FIELD = 'get_session_from_extra_field';
117
    public const GET_SESSION_INFO_FROM_EXTRA_FIELD = 'get_session_info_from_extra_field';
118
    public const SAVE_SESSION = 'save_session';
119
    public const CREATE_SESSION_FROM_MODEL = 'create_session_from_model';
120
    public const UPDATE_SESSION = 'update_session';
121
    public const GET_SESSIONS = 'get_sessions';
122
123
    public const SUBSCRIBE_USER_TO_COURSE = 'subscribe_user_to_course';
124
    public const SUBSCRIBE_USER_TO_COURSE_PASSWORD = 'subscribe_user_to_course_password';
125
    public const UNSUBSCRIBE_USER_FROM_COURSE = 'unsubscribe_user_from_course';
126
    public const GET_USERS_SUBSCRIBED_TO_COURSE = 'get_users_subscribed_to_course';
127
128
    public const ADD_COURSES_SESSION = 'add_courses_session';
129
    public const ADD_USERS_SESSION = 'add_users_session';
130
    public const SUBSCRIBE_USER_TO_SESSION_FROM_USERNAME = 'subscribe_user_to_session_from_username';
131
    public const SUBSCRIBE_USERS_TO_SESSION = 'subscribe_users_to_session';
132
    public const UNSUBSCRIBE_USERS_FROM_SESSION = 'unsubscribe_users_from_session';
133
    public const GET_USERS_SUBSCRIBED_TO_SESSION = 'get_users_subscribed_to_session';
134
135
    public const GET_COURSE_QUIZ_MDL_COMPAT = 'get_course_quiz_mdl_compat';
136
137
    public const UPDATE_USER_PAUSE_TRAINING = 'update_user_pause_training';
138
139
    public const CHECK_CONDITIONAL_LOGIN = 'check_conditional_login';
140
    public const GET_LEGAL_CONDITIONS = 'get_legal_conditions';
141
    public const UPDATE_CONDITION_ACCEPTED = 'update_condition_accepted';
142
    public const GET_TEST_UPDATES_LIST = 'get_test_updates_list';
143
    public const GET_TEST_AVERAGE_RESULTS_LIST = 'get_test_average_results_list';
144
145
    public const GET_GROUPS = 'get_groups';
146
    public const GROUP_EXISTS = 'group_exists';
147
    public const ADD_GROUP = 'add_group';
148
    public const DELETE_GROUP = 'delete_group';
149
    public const GET_GROUP_SUB_USERS = 'get_group_sub_users';
150
    public const GET_GROUP_SUB_COURSES = 'get_group_sub_courses';
151
    public const GET_GROUP_SUB_SESSIONS = 'get_group_sub_sessions';
152
    public const ADD_GROUP_SUB_USER = 'add_group_sub_user';
153
    public const ADD_GROUP_SUB_COURSE = 'add_group_sub_course';
154
    public const ADD_GROUP_SUB_SESSION = 'add_group_sub_session';
155
    public const DELETE_GROUP_SUB_USER = 'delete_group_sub_user';
156
    public const DELETE_GROUP_SUB_COURSE = 'delete_group_sub_course';
157
    public const DELETE_GROUP_SUB_SESSION = 'delete_group_sub_session';
158
    public const GET_AUDIT_ITEMS = 'get_audit_items';
159
    public const SUBSCRIBE_COURSE_TO_SESSION_FROM_EXTRA_FIELD = 'subscribe_course_to_session_from_extra_field';
160
    public const SUBSCRIBE_USER_TO_SESSION_FROM_EXTRA_FIELD = 'subscribe_user_to_session_from_extra_field';
161
    public const UPDATE_SESSION_FROM_EXTRA_FIELD = 'update_session_from_extra_field';
162
163
    /**
164
     * @var Session
165
     */
166
    private $session;
167
168
    /**
169
     * @var Course
170
     */
171
    private $course;
172
173
    /**
174
     * Rest constructor.
175
     *
176
     * @param string $username
177
     * @param string $apiKey
178
     */
179
    public function __construct($username, $apiKey)
180
    {
181
        parent::__construct($username, $apiKey);
182
    }
183
184
    /**
185
     * Get user's username or another field if so configured through $_configuration['webservice_return_user_field'].
186
     *
187
     * @param int $userId
188
     */
189
    private function __getConfiguredUsernameById(int $userId = null): string
190
    {
191
        if (empty($userId)) {
192
            return '';
193
        }
194
        $userField = api_get_configuration_value('webservice_return_user_field');
195
        if (empty($userField)) {
196
            return api_get_user_info($userId)['username'];
197
        }
198
199
        $fieldValue = new ExtraFieldValue('user');
200
        $extraInfo = $fieldValue->get_values_by_handler_and_field_variable($userId, $userField);
201
        if (!empty($extraInfo)) {
202
            return $extraInfo['value'];
203
        } else {
204
            return api_get_user_info($userId)['username'];
205
        }
206
    }
207
208
    /**
209
     * @param string $username
210
     * @param string $apiKeyToValidate
211
     *
212
     * @throws Exception
213
     *
214
     * @return Rest
215
     */
216
    public static function validate($username, $apiKeyToValidate)
217
    {
218
        $apiKey = self::findUserApiKey($username, self::SERVICE_NAME);
219
220
        if ($apiKey != $apiKeyToValidate) {
221
            throw new Exception(get_lang('InvalidApiKey'));
222
        }
223
224
        return new self($username, $apiKey);
225
    }
226
227
    /**
228
     * Create the gcm_registration_id extra field for users.
229
     */
230
    public static function init()
231
    {
232
        $extraField = new ExtraField('user');
233
        $fieldInfo = $extraField->get_handler_field_info_by_field_variable(self::EXTRA_FIELD_GCM_REGISTRATION);
234
235
        if (empty($fieldInfo)) {
236
            $extraField->save(
237
                [
238
                    'variable' => self::EXTRA_FIELD_GCM_REGISTRATION,
239
                    'field_type' => ExtraField::FIELD_TYPE_TEXT,
240
                    'display_text' => self::EXTRA_FIELD_GCM_REGISTRATION,
241
                ]
242
            );
243
        }
244
    }
245
246
    public static function decodeParams(string $encoded): array
247
    {
248
        return json_decode($encoded);
249
    }
250
251
    /**
252
     * Set the current course.
253
     *
254
     * @param int $id
255
     *
256
     * @throws Exception
257
     */
258
    public function setCourse($id)
259
    {
260
        global $_course;
261
262
        if (!$id) {
263
            $this->course = null;
264
265
            ChamiloSession::erase('_real_cid');
266
            ChamiloSession::erase('_cid');
267
            ChamiloSession::erase('_course');
268
269
            return;
270
        }
271
272
        $em = Database::getManager();
273
        /** @var Course $course */
274
        $course = $em->find('ChamiloCoreBundle:Course', $id);
275
276
        if (!$course) {
0 ignored issues
show
introduced by
$course is of type Chamilo\CoreBundle\Entity\Course, thus it always evaluated to true.
Loading history...
277
            throw new Exception(get_lang('NoCourse'));
278
        }
279
280
        $this->course = $course;
281
282
        $courseInfo = api_get_course_info($course->getCode());
283
        $_course = $courseInfo;
284
285
        ChamiloSession::write('_real_cid', $course->getId());
286
        ChamiloSession::write('_cid', $course->getCode());
287
        ChamiloSession::write('_course', $courseInfo);
288
    }
289
290
    /**
291
     * Set the current session.
292
     *
293
     * @param int $id
294
     *
295
     * @throws Exception
296
     */
297
    public function setSession($id)
298
    {
299
        if (!$id) {
300
            $this->session = null;
301
302
            ChamiloSession::erase('session_name');
303
            ChamiloSession::erase('id_session');
304
305
            return;
306
        }
307
308
        $em = Database::getManager();
309
        /** @var Session $session */
310
        $session = $em->find('ChamiloCoreBundle:Session', $id);
311
312
        if (!$session) {
0 ignored issues
show
introduced by
$session is of type Chamilo\CoreBundle\Entity\Session, thus it always evaluated to true.
Loading history...
313
            throw new Exception(get_lang('NoSession'));
314
        }
315
316
        $this->session = $session;
317
318
        ChamiloSession::write('session_name', $session->getName());
319
        ChamiloSession::write('id_session', $session->getId());
320
    }
321
322
    /**
323
     * @param string $registrationId
324
     *
325
     * @return bool
326
     */
327
    public function setGcmId($registrationId)
328
    {
329
        $registrationId = Security::remove_XSS($registrationId);
330
        $extraFieldValue = new ExtraFieldValue('user');
331
332
        return $extraFieldValue->save(
333
            [
334
                'variable' => self::EXTRA_FIELD_GCM_REGISTRATION,
335
                'value' => $registrationId,
336
                'item_id' => $this->user->getId(),
337
            ]
338
        );
339
    }
340
341
    /**
342
     * @param int $lastMessageId
343
     *
344
     * @return array
345
     */
346
    public function getUserMessages($lastMessageId = 0)
347
    {
348
        $lastMessages = MessageManager::getMessagesFromLastReceivedMessage($this->user->getId(), $lastMessageId);
349
        $messages = [];
350
351
        foreach ($lastMessages as $message) {
352
            $hasAttachments = MessageManager::hasAttachments($message['id']);
353
354
            $messages[] = [
355
                'id' => $message['id'],
356
                'title' => $message['title'],
357
                'sender' => [
358
                    'id' => $message['user_id'],
359
                    'lastname' => $message['lastname'],
360
                    'firstname' => $message['firstname'],
361
                    'completeName' => api_get_person_name($message['firstname'], $message['lastname']),
362
                ],
363
                'sendDate' => $message['send_date'],
364
                'content' => $message['content'],
365
                'hasAttachments' => $hasAttachments,
366
                'url' => api_get_path(WEB_CODE_PATH).'messages/view_message.php?'
367
                    .http_build_query(['type' => 1, 'id' => $message['id']]),
368
            ];
369
        }
370
371
        return $messages;
372
    }
373
374
    /**
375
     * @return array
376
     */
377
    public function getUserReceivedMessages()
378
    {
379
        $lastMessages = MessageManager::getReceivedMessages($this->user->getId(), 0);
380
        $messages = [];
381
382
        $webPath = api_get_path(WEB_PATH);
383
384
        foreach ($lastMessages as $message) {
385
            $hasAttachments = MessageManager::hasAttachments($message['id']);
386
            $attachmentList = [];
387
            if ($hasAttachments) {
388
                $attachmentList = MessageManager::getAttachmentList($message['id']);
389
            }
390
            $messages[] = [
391
                'id' => $message['id'],
392
                'title' => $message['title'],
393
                'msgStatus' => $message['msg_status'],
394
                'sender' => [
395
                    'id' => $message['user_id'],
396
                    'lastname' => $message['lastname'],
397
                    'firstname' => $message['firstname'],
398
                    'completeName' => api_get_person_name($message['firstname'], $message['lastname']),
399
                    'pictureUri' => $message['pictureUri'],
400
                ],
401
                'sendDate' => $message['send_date'],
402
                'content' => str_replace('src="/"', $webPath, $message['content']),
403
                'hasAttachments' => $hasAttachments,
404
                'attachmentList' => $attachmentList,
405
                'url' => '',
406
            ];
407
        }
408
409
        return $messages;
410
    }
411
412
    /**
413
     * @return array
414
     */
415
    public function getUserSentMessages()
416
    {
417
        $lastMessages = MessageManager::getSentMessages($this->user->getId(), 0);
418
        $messages = [];
419
420
        foreach ($lastMessages as $message) {
421
            $hasAttachments = MessageManager::hasAttachments($message['id']);
422
423
            $messages[] = [
424
                'id' => $message['id'],
425
                'title' => $message['title'],
426
                'msgStatus' => $message['msg_status'],
427
                'receiver' => [
428
                    'id' => $message['user_id'],
429
                    'lastname' => $message['lastname'],
430
                    'firstname' => $message['firstname'],
431
                    'completeName' => api_get_person_name($message['firstname'], $message['lastname']),
432
                    'pictureUri' => $message['pictureUri'],
433
                ],
434
                'sendDate' => $message['send_date'],
435
                'content' => $message['content'],
436
                'hasAttachments' => $hasAttachments,
437
                'url' => '',
438
            ];
439
        }
440
441
        return $messages;
442
    }
443
444
    /**
445
     * Get the user courses.
446
     *
447
     * @throws Exception
448
     */
449
    public function getUserCourses($userId = 0): array
450
    {
451
        if (!empty($userId)) {
452
            if (!api_is_platform_admin() && $userId != $this->user->getId()) {
453
                self::throwNotAllowedException();
454
            }
455
        }
456
457
        if (empty($userId)) {
458
            $userId = $this->user->getId();
459
        }
460
461
        Event::courseLogout(
462
            [
463
                'uid' => $userId,
464
                'cid' => api_get_course_id(),
465
                'sid' => api_get_session_id(),
466
            ]
467
        );
468
469
        $courses = CourseManager::get_courses_list_by_user_id($userId);
470
        $data = [];
471
472
        $webCodePath = api_get_path(WEB_CODE_PATH).'webservices/api/v2.php?';
473
474
        foreach ($courses as $courseInfo) {
475
            /** @var Course $course */
476
            $course = Database::getManager()->find('ChamiloCoreBundle:Course', $courseInfo['real_id']);
477
            $teachers = CourseManager::getTeacherListFromCourseCodeToString($course->getCode());
478
            $picturePath = CourseManager::getPicturePath($course, true)
479
                ?: Display::return_icon('session_default.png', null, null, null, null, true);
480
481
            $data[] = [
482
                'id' => $course->getId(),
483
                'title' => $course->getTitle(),
484
                'code' => $course->getCode(),
485
                'directory' => $course->getDirectory(),
486
                'urlPicture' => $picturePath,
487
                'teachers' => $teachers,
488
                'isSpecial' => !empty($courseInfo['special_course']),
489
                'url' => $webCodePath.http_build_query(
490
                    [
491
                        'action' => self::VIEW_COURSE_HOME,
492
                        'api_key' => $this->apiKey,
493
                        'username' => $this->user->getUsername(),
494
                        'course' => $course->getId(),
495
                    ]
496
                ),
497
            ];
498
        }
499
500
        return $data;
501
    }
502
503
    /**
504
     * @throws Exception
505
     */
506
    public function getCourseByCode(string $q, int $sessionId = 0): array
507
    {
508
        if (!api_is_teacher() && !api_is_platform_admin()) {
509
            self::throwNotAllowedException();
510
        }
511
512
        if (strlen($q) < 3) {
513
            throw new Exception(get_lang('TooShort'));
514
        }
515
516
        $courseList = CourseManager::searchCourse($q, $sessionId);
517
518
        return array_map(
519
            fn($course) => api_get_course_info_by_id($course['id']),
520
            $courseList
521
        );
522
    }
523
524
    /**
525
     * @throws Exception
526
     *
527
     * @return array
528
     */
529
    public function getCourseInfo()
530
    {
531
        $teachers = CourseManager::getTeacherListFromCourseCodeToString($this->course->getCode());
532
        $tools = CourseHome::get_tools_category(
533
            TOOL_STUDENT_VIEW,
534
            $this->course->getId(),
535
            $this->session ? $this->session->getId() : 0
536
        );
537
538
        return [
539
            'id' => $this->course->getId(),
540
            'title' => $this->course->getTitle(),
541
            'code' => $this->course->getCode(),
542
            'directory' => $this->course->getDirectory(),
543
            'urlPicture' => CourseManager::getPicturePath($this->course, true),
544
            'teachers' => $teachers,
545
            'tools' => array_map(
546
                function ($tool) {
547
                    return ['type' => $tool['name']];
548
                },
549
                $tools
550
            ),
551
        ];
552
    }
553
554
    /**
555
     * Get the course descriptions.
556
     *
557
     * @param array $fields A list of extra fields to include in the answer. Searches for the field in the including course
558
     *
559
     * @throws Exception
560
     */
561
    public function getCourseDescriptions($fields = []): array
562
    {
563
        Event::event_access_tool(TOOL_COURSE_DESCRIPTION);
564
565
        // Check the extra fields criteria (whether to add extra field information or not)
566
        $fieldSource = [];
567
        if (count($fields) > 0) {
568
            // For each field, check where to get it from (quiz or course)
569
            $courseExtraField = new ExtraField('course');
570
            foreach ($fields as $fieldName) {
571
                // The field does not exist on the exercise, so use it from the course
572
                $courseFieldExists = $courseExtraField->get_handler_field_info_by_field_variable($fieldName);
573
                if ($courseFieldExists === false) {
574
                    continue;
575
                }
576
                $fieldSource[$fieldName] = ['item_type' => 'course', 'id' => $courseFieldExists['id']];
577
            }
578
        }
579
580
        $courseId = $this->course->getId();
581
        $descriptions = CourseDescription::get_descriptions($courseId);
582
        $results = [];
583
584
        $webPath = api_get_path(WEB_PATH);
585
586
        /** @var CourseDescription $description */
587
        foreach ($descriptions as $description) {
588
            $descriptionDetails = [
589
                'id' => $description->get_description_type(),
590
                'title' => $description->get_title(),
591
                'content' => str_replace('src="/', 'src="'.$webPath, $description->get_content()),
592
            ];
593
            if (count($fieldSource) > 0) {
594
                $extraFieldValuesTable = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
595
                $fieldsSearchString = "SELECT field_id, value FROM $extraFieldValuesTable WHERE item_id = %d AND field_id = %d";
596
                foreach ($fieldSource as $fieldName => $fieldDetails) {
597
                    $itemId = $courseId;
598
                    $result = Database::query(sprintf($fieldsSearchString, $itemId, $fieldDetails['id']));
599
                    if (Database::num_rows($result) > 0) {
600
                        $row = Database::fetch_assoc($result);
601
                        $descriptionDetails['extra_'.$fieldName] = $row['value'];
602
                    } else {
603
                        $descriptionDetails['extra_'.$fieldName] = '';
604
                    }
605
                }
606
            }
607
            $results[] = $descriptionDetails;
608
        }
609
610
        return $results;
611
    }
612
613
    /**
614
     * @param int $directoryId
615
     *
616
     * @throws Exception
617
     *
618
     * @return array
619
     */
620
    public function getCourseDocuments($directoryId = 0)
621
    {
622
        Event::event_access_tool(TOOL_DOCUMENT);
623
624
        /** @var string $path */
625
        $path = '/';
626
        $sessionId = $this->session ? $this->session->getId() : 0;
627
628
        if ($directoryId) {
629
            $directory = DocumentManager::get_document_data_by_id(
630
                $directoryId,
631
                $this->course->getCode(),
632
                false,
633
                $sessionId
634
            );
635
636
            if (!$directory) {
637
                throw new Exception('NoDataAvailable');
638
            }
639
640
            $path = $directory['path'];
641
        }
642
643
        $courseInfo = api_get_course_info_by_id($this->course->getId());
644
        $documents = DocumentManager::getAllDocumentData(
645
            $courseInfo,
646
            $path,
647
            0,
648
            null,
649
            false,
650
            false,
651
            $sessionId
652
        );
653
        $results = [];
654
655
        if (!empty($documents)) {
656
            $webPath = api_get_path(WEB_CODE_PATH).'document/document.php?';
657
658
            /** @var array $document */
659
            foreach ($documents as $document) {
660
                if ($document['visibility'] != '1') {
661
                    continue;
662
                }
663
664
                $icon = $document['filetype'] == 'file'
665
                    ? choose_image($document['path'])
666
                    : chooseFolderIcon($document['path']);
667
668
                $results[] = [
669
                    'id' => $document['id'],
670
                    'type' => $document['filetype'],
671
                    'title' => $document['title'],
672
                    'path' => $document['path'],
673
                    'url' => $webPath.http_build_query(
674
                        [
675
                            'username' => $this->user->getUsername(),
676
                            'api_key' => $this->apiKey,
677
                            'cidReq' => $this->course->getCode(),
678
                            'id_session' => $sessionId,
679
                            'gidReq' => 0,
680
                            'gradebook' => 0,
681
                            'origin' => '',
682
                            'action' => 'download',
683
                            'id' => $document['id'],
684
                        ]
685
                    ),
686
                    'icon' => $icon,
687
                    'size' => format_file_size($document['size']),
688
                ];
689
            }
690
        }
691
692
        return $results;
693
    }
694
695
    /**
696
     * @throws Exception
697
     *
698
     * @return array
699
     */
700
    public function getCourseAnnouncements()
701
    {
702
        Event::event_access_tool(TOOL_ANNOUNCEMENT);
703
704
        $sessionId = $this->session ? $this->session->getId() : 0;
705
706
        $announcements = AnnouncementManager::getAnnouncements(
707
            null,
708
            null,
709
            false,
710
            null,
711
            null,
712
            null,
713
            null,
714
            null,
715
            0,
716
            $this->user->getId(),
717
            $this->course->getId(),
718
            $sessionId
719
        );
720
721
        $announcements = array_map(
722
            function ($announcement) {
723
                return [
724
                    'id' => (int) $announcement['id'],
725
                    'title' => strip_tags($announcement['title']),
726
                    'creatorName' => strip_tags($announcement['username']),
727
                    'date' => strip_tags($announcement['insert_date']),
728
                ];
729
            },
730
            $announcements
731
        );
732
733
        return $announcements;
734
    }
735
736
    /**
737
     * @param int $announcementId
738
     *
739
     * @throws Exception
740
     *
741
     * @return array
742
     */
743
    public function getCourseAnnouncement($announcementId)
744
    {
745
        Event::event_access_tool(TOOL_ANNOUNCEMENT);
746
747
        $sessionId = $this->session ? $this->session->getId() : 0;
748
        $announcement = AnnouncementManager::getAnnouncementInfoById(
749
            $announcementId,
750
            $this->course->getId(),
751
            $this->user->getId()
752
        );
753
754
        if (!$announcement) {
755
            throw new Exception(get_lang('NoAnnouncement'));
756
        }
757
758
        return [
759
            'id' => $announcement['announcement']->getIid(),
760
            'title' => $announcement['announcement']->getTitle(),
761
            'creatorName' => UserManager::formatUserFullName($announcement['item_property']->getInsertUser()),
762
            'date' => api_convert_and_format_date(
763
                $announcement['item_property']->getInsertDate(),
764
                DATE_TIME_FORMAT_LONG_24H
765
            ),
766
            'content' => AnnouncementManager::parseContent(
767
                $this->user->getId(),
768
                $announcement['announcement']->getContent(),
769
                $this->course->getCode(),
770
                $sessionId
771
            ),
772
        ];
773
    }
774
775
    /**
776
     * @throws Exception
777
     *
778
     * @return array
779
     */
780
    public function getCourseAgenda()
781
    {
782
        Event::event_access_tool(TOOL_CALENDAR_EVENT);
783
784
        $sessionId = $this->session ? $this->session->getId() : 0;
785
786
        $agenda = new Agenda(
787
            'course',
788
            $this->user->getId(),
789
            $this->course->getId(),
790
            $sessionId
791
        );
792
        $result = $agenda->parseAgendaFilter(null);
793
794
        $start = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
795
        $start->modify('first day of this month');
796
        $start->setTime(0, 0, 0);
797
        $end = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
798
        $end->modify('last day of this month');
799
        $end->setTime(23, 59, 59);
800
801
        $groupId = current($result['groups']);
802
        $userId = current($result['users']);
803
804
        $events = $agenda->getEvents(
805
            $start->getTimestamp(),
806
            $end->getTimestamp(),
807
            $this->course->getId(),
808
            $groupId,
809
            $userId,
810
            'array'
811
        );
812
813
        if (!is_array($events)) {
814
            return [];
815
        }
816
817
        $webPath = api_get_path(WEB_PATH);
818
819
        return array_map(
820
            function ($event) use ($webPath) {
821
                return [
822
                    'id' => (int) $event['unique_id'],
823
                    'title' => $event['title'],
824
                    'content' => str_replace('src="/', 'src="'.$webPath, $event['description']),
825
                    'startDate' => $event['start_date_localtime'],
826
                    'endDate' => $event['end_date_localtime'],
827
                    'isAllDay' => $event['allDay'] ? true : false,
828
                ];
829
            },
830
            $events
831
        );
832
    }
833
834
    /**
835
     * @throws Exception
836
     *
837
     * @return array
838
     */
839
    public function getCourseNotebooks()
840
    {
841
        Event::event_access_tool(TOOL_NOTEBOOK);
842
843
        $em = Database::getManager();
844
        /** @var CNotebookRepository $notebooksRepo */
845
        $notebooksRepo = $em->getRepository('ChamiloCourseBundle:CNotebook');
846
        $notebooks = $notebooksRepo->findByUser($this->user, $this->course, $this->session);
847
848
        return array_map(
849
            function (CNotebook $notebook) {
850
                return [
851
                    'id' => $notebook->getIid(),
852
                    'title' => $notebook->getTitle(),
853
                    'description' => $notebook->getDescription(),
854
                    'creationDate' => api_format_date(
855
                        $notebook->getCreationDate()->getTimestamp()
856
                    ),
857
                    'updateDate' => api_format_date(
858
                        $notebook->getUpdateDate()->getTimestamp()
859
                    ),
860
                ];
861
            },
862
            $notebooks
863
        );
864
    }
865
866
    /**
867
     * @throws Exception
868
     *
869
     * @return array
870
     */
871
    public function getCourseForumCategories()
872
    {
873
        Event::event_access_tool(TOOL_FORUM);
874
875
        $sessionId = $this->session ? $this->session->getId() : 0;
876
        $webCoursePath = api_get_path(WEB_COURSE_PATH).$this->course->getDirectory().'/upload/forum/images/';
877
878
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
879
880
        $categoriesFullData = get_forum_categories('', $this->course->getId(), $sessionId);
881
        $categories = [];
882
        $includeGroupsForums = api_get_setting('display_groups_forum_in_general_tool') === 'true';
883
        $forumsFullData = get_forums('', $this->course->getCode(), $includeGroupsForums, $sessionId);
884
        $forums = [];
885
886
        foreach ($forumsFullData as $forumId => $forumInfo) {
887
            $forum = [
888
                'id' => (int) $forumInfo['iid'],
889
                'catId' => (int) $forumInfo['forum_category'],
890
                'title' => $forumInfo['forum_title'],
891
                'description' => $forumInfo['forum_comment'],
892
                'image' => $forumInfo['forum_image'] ? ($webCoursePath.$forumInfo['forum_image']) : '',
893
                'numberOfThreads' => isset($forumInfo['number_of_threads']) ? intval(
894
                    $forumInfo['number_of_threads']
895
                ) : 0,
896
                'lastPost' => null,
897
            ];
898
899
            $lastPostInfo = get_last_post_information($forumId, false, $this->course->getId(), $sessionId);
900
901
            if ($lastPostInfo) {
902
                $forum['lastPost'] = [
903
                    'date' => api_convert_and_format_date($lastPostInfo['last_post_date']),
904
                    'user' => api_get_person_name(
905
                        $lastPostInfo['last_poster_firstname'],
906
                        $lastPostInfo['last_poster_lastname']
907
                    ),
908
                ];
909
            }
910
911
            $forums[] = $forum;
912
        }
913
914
        foreach ($categoriesFullData as $category) {
915
            $categoryForums = array_filter(
916
                $forums,
917
                function (array $forum) use ($category) {
918
                    if ($forum['catId'] != $category['cat_id']) {
919
                        return false;
920
                    }
921
922
                    return true;
923
                }
924
            );
925
926
            $categories[] = [
927
                'id' => (int) $category['iid'],
928
                'title' => $category['cat_title'],
929
                'catId' => (int) $category['cat_id'],
930
                'description' => $category['cat_comment'],
931
                'forums' => $categoryForums,
932
                'courseId' => $this->course->getId(),
933
            ];
934
        }
935
936
        return $categories;
937
    }
938
939
    /**
940
     * @param int $forumId
941
     *
942
     * @throws Exception
943
     *
944
     * @return array
945
     */
946
    public function getCourseForum($forumId)
947
    {
948
        Event::event_access_tool(TOOL_FORUM);
949
950
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
951
952
        $sessionId = $this->session ? $this->session->getId() : 0;
953
        $forumInfo = get_forums($forumId, $this->course->getCode(), true, $sessionId);
954
955
        if (!isset($forumInfo['iid'])) {
956
            throw new Exception(get_lang('NoForum'));
957
        }
958
959
        $webCoursePath = api_get_path(WEB_COURSE_PATH).$this->course->getDirectory().'/upload/forum/images/';
960
        $forum = [
961
            'id' => $forumInfo['iid'],
962
            'title' => $forumInfo['forum_title'],
963
            'description' => $forumInfo['forum_comment'],
964
            'image' => $forumInfo['forum_image'] ? ($webCoursePath.$forumInfo['forum_image']) : '',
965
            'threads' => [],
966
        ];
967
968
        $threads = get_threads($forumInfo['iid'], $this->course->getId(), $sessionId);
969
970
        foreach ($threads as $thread) {
971
            $forum['threads'][] = [
972
                'id' => $thread['iid'],
973
                'title' => $thread['thread_title'],
974
                'lastEditDate' => api_convert_and_format_date($thread['lastedit_date'], DATE_TIME_FORMAT_LONG_24H),
975
                'numberOfReplies' => $thread['thread_replies'],
976
                'numberOfViews' => $thread['thread_views'],
977
                'author' => api_get_person_name($thread['firstname'], $thread['lastname']),
978
            ];
979
        }
980
981
        return $forum;
982
    }
983
984
    /**
985
     * @param int $forumId
986
     * @param int $threadId
987
     *
988
     * @return array
989
     */
990
    public function getCourseForumThread($forumId, $threadId)
991
    {
992
        Event::event_access_tool(TOOL_FORUM);
993
994
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
995
996
        $sessionId = $this->session ? $this->session->getId() : 0;
997
        $threadInfo = get_thread_information($forumId, $threadId, $sessionId);
998
999
        $thread = [
1000
            'id' => intval($threadInfo['iid']),
1001
            'cId' => intval($threadInfo['c_id']),
1002
            'title' => $threadInfo['thread_title'],
1003
            'forumId' => intval($threadInfo['forum_id']),
1004
            'posts' => [],
1005
        ];
1006
1007
        $forumInfo = get_forums($threadInfo['forum_id'], $this->course->getCode(), true, $sessionId);
1008
        $postsInfo = getPosts($forumInfo, $threadInfo['iid'], 'ASC');
1009
1010
        foreach ($postsInfo as $postInfo) {
1011
            $thread['posts'][] = [
1012
                'id' => $postInfo['iid'],
1013
                'title' => $postInfo['post_title'],
1014
                'text' => $postInfo['post_text'],
1015
                'author' => api_get_person_name($postInfo['firstname'], $postInfo['lastname']),
1016
                'date' => api_convert_and_format_date($postInfo['post_date'], DATE_TIME_FORMAT_LONG_24H),
1017
                'parentId' => $postInfo['post_parent_id'],
1018
                'attachments' => getAttachedFiles(
1019
                    $forumId,
1020
                    $threadId,
1021
                    $postInfo['iid'],
1022
                    0,
1023
                    $this->course->getId()
1024
                ),
1025
            ];
1026
        }
1027
1028
        return $thread;
1029
    }
1030
1031
    public function getCourseLinks(): array
1032
    {
1033
        Event::event_access_tool(TOOL_LINK);
1034
1035
        $courseId = $this->course->getId();
1036
        $sessionId = $this->session ? $this->session->getId() : 0;
1037
1038
        $webCodePath = api_get_path(WEB_CODE_PATH);
1039
        $cidReq = api_get_cidreq();
1040
1041
        $categories = array_merge(
1042
            [
1043
                [
1044
                    'iid' => 0,
1045
                    'c_id' => $courseId,
1046
                    'id' => 0,
1047
                    'category_title' => get_lang('NoCategory'),
1048
                    'description' => '',
1049
                    'display_order' => 0,
1050
                    'session_id' => $sessionId,
1051
                    'visibility' => 1,
1052
                ],
1053
            ],
1054
            Link::getLinkCategories($courseId, $sessionId)
1055
        );
1056
1057
        $categories = array_filter(
1058
            $categories,
1059
            function (array $category) {
1060
                return $category['visibility'] != 0;
1061
            }
1062
        );
1063
1064
        return array_map(
1065
            function (array $category) use ($webCodePath, $cidReq, $courseId, $sessionId) {
1066
                $links = array_filter(
1067
                    Link::getLinksPerCategory($category['iid'], $courseId, $sessionId),
1068
                    function (array $link) {
1069
                        return $link['visibility'] != 0;
1070
                    }
1071
                );
1072
1073
                $links = array_map(
1074
                    function (array $link) use ($webCodePath, $cidReq) {
1075
                        return [
1076
                            'id' => (int) $link['id'],
1077
                            'title' => Security::remove_XSS($link['title']),
1078
                            'description' => Security::remove_XSS($link['description']),
1079
                            'visibility' => (int) $link['visibility'],
1080
                            'url' => $webCodePath."link/link_goto.php?$cidReq&link_id=".$link['id'],
1081
                        ];
1082
                    },
1083
                    $links
1084
                );
1085
1086
                return [
1087
                    'id' => (int) $category['iid'],
1088
                    'title' => Security::remove_XSS($category['category_title']),
1089
                    'description' => Security::remove_XSS($category['description']),
1090
                    'visibility' => (int) $category['visibility'],
1091
                    'links' => $links,
1092
                ];
1093
            },
1094
            $categories
1095
        );
1096
    }
1097
1098
    /**
1099
     * It gets the courses and visible tests of a user by dates.
1100
     *
1101
     * @throws Exception
1102
     */
1103
    public function getUserCoursesByDates(int $userId, string $startDate, string $endDate): array
1104
    {
1105
        self::protectAdminEndpoint();
1106
        $userCourses = CourseManager::get_courses_list_by_user_id($userId);
1107
        $courses = [];
1108
        if (!empty($userCourses)) {
1109
            foreach ($userCourses as $course) {
1110
                $courseCode = $course['code'];
1111
                $courseId = $course['real_id'];
1112
                $exercises = Exercise::exerciseGrid(
1113
                    0,
1114
                    '',
1115
                    0,
1116
                    $courseId,
1117
                    0,
1118
                    true,
1119
                    0,
1120
                    0,
1121
                    0,
1122
                    null,
1123
                    false,
1124
                    false
1125
                );
1126
                $trackExercises = Tracking::getUserTrackExerciseByDates(
1127
                    $userId,
1128
                    $courseId,
1129
                    $startDate,
1130
                    $endDate
1131
                );
1132
                $takenExercises = [];
1133
                if (!empty($trackExercises)) {
1134
                    $totalSore = 0;
1135
                    foreach ($trackExercises as $track) {
1136
                        $takenExercises[] = $track['title'];
1137
                        $totalSore += $track['score'];
1138
                    }
1139
                    $avgScore = round($totalSore / count($trackExercises));
1140
                    $takenExercises['avg_score'] = $avgScore;
1141
                }
1142
                $courses[] = [
1143
                    'course_code' => $courseCode,
1144
                    'course_title' => $course['title'],
1145
                    'visible_tests' => $exercises,
1146
                    'taken_tests' => $takenExercises,
1147
                ];
1148
            }
1149
        }
1150
1151
        return $courses;
1152
    }
1153
1154
    /**
1155
     * Get the list of courses from extra field included count of visible exercises.
1156
     *
1157
     * @throws Exception
1158
     */
1159
    public function getCoursesByExtraField(string $fieldName, string $fieldValue): array
1160
    {
1161
        self::protectAdminEndpoint();
1162
        $extraField = new ExtraField('course');
1163
        $extraFieldInfo = $extraField->get_handler_field_info_by_field_variable($fieldName);
1164
1165
        if (empty($extraFieldInfo)) {
1166
            throw new Exception("$fieldName not found");
1167
        }
1168
1169
        $extraFieldValue = new ExtraFieldValue('course');
1170
        $items = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
1171
            $fieldName,
1172
            $fieldValue,
1173
            false,
1174
            false,
1175
            true
1176
        );
1177
1178
        $courses = [];
1179
        foreach ($items as $item) {
1180
            $courseId = $item['item_id'];
1181
            $courses[$courseId] = api_get_course_info_by_id($courseId);
1182
            $exercises = Exercise::exerciseGrid(
1183
                0,
1184
                '',
1185
                0,
1186
                $courseId,
1187
                0,
1188
                true,
1189
                0,
1190
                0,
1191
                0,
1192
                null,
1193
                false,
1194
                false
1195
            );
1196
            $courses[$courseId]['count_visible_tests'] = count($exercises);
1197
        }
1198
1199
        return $courses;
1200
    }
1201
1202
    /**
1203
     * Get the list of users from extra field.
1204
     *
1205
     * @param string $fieldName  The name of the extra_field (as in extra_field.variable) we want to filter on.
1206
     * @param string $fieldValue The value of the extra_field we want to filter on. If a user doesn't have the given extra_field set to that value, it will not be returned.
1207
     * @param int    $active     Additional filter. If 1, only return active users. Otherwise, return them all.
1208
     *
1209
     * @throws Exception
1210
     */
1211
    public function getUsersProfilesByExtraField(string $fieldName, string $fieldValue, int $active = 0): array
1212
    {
1213
        self::protectAdminEndpoint();
1214
        $users = [];
1215
        $extraValues = UserManager::get_extra_user_data_by_value(
1216
            $fieldName,
1217
            $fieldValue
1218
        );
1219
        if (!empty($extraValues)) {
1220
            foreach ($extraValues as $value) {
1221
                $userId = (int) $value;
1222
                $user = api_get_user_entity($userId);
1223
                if ($active && !$user->getActive()) {
1224
                    // If this user is not active, and we only asked for active users, skip to next user.
1225
                    continue;
1226
                }
1227
                $pictureInfo = UserManager::get_user_picture_path_by_id($user->getId(), 'web');
1228
                $users[$userId] = [
1229
                    'pictureUri' => $pictureInfo['dir'].$pictureInfo['file'],
1230
                    'id' => $userId,
1231
                    'status' => $user->getStatus(),
1232
                    'fullName' => UserManager::formatUserFullName($user),
1233
                    'username' => $user->getUsername(),
1234
                    'officialCode' => $user->getOfficialCode(),
1235
                    'phone' => $user->getPhone(),
1236
                    //'extra' => [],
1237
                ];
1238
            }
1239
        }
1240
1241
        return $users;
1242
    }
1243
1244
    /**
1245
     * Get one's own profile.
1246
     */
1247
    public function getUserProfile(): array
1248
    {
1249
        $pictureInfo = UserManager::get_user_picture_path_by_id($this->user->getId(), 'web');
1250
1251
        $result = [
1252
            'pictureUri' => $pictureInfo['dir'].$pictureInfo['file'],
1253
            'id' => $this->user->getId(),
1254
            'status' => $this->user->getStatus(),
1255
            'fullName' => UserManager::formatUserFullName($this->user),
1256
            'username' => $this->user->getUsername(),
1257
            'officialCode' => $this->user->getOfficialCode(),
1258
            'phone' => $this->user->getPhone(),
1259
        ];
1260
1261
        $extraInfo = (new ExtraFieldValue('user'))->getAllValuesForAnItem($this->user->getId(), true);
1262
1263
        $result['extra'] = ExtraFieldValue::formatValues($extraInfo);
1264
1265
        return $result;
1266
    }
1267
1268
    /**
1269
     * Get one's own (avg) progress in learning paths.
1270
     */
1271
    public function getCourseLpProgress(): array
1272
    {
1273
        $sessionId = $this->session ? $this->session->getId() : 0;
1274
        $userId = $this->user->getId();
1275
1276
        /*$sessionId = $this->session ? $this->session->getId() : 0;
1277
        $courseId = $this->course->getId();*/
1278
1279
        $result = Tracking::getCourseLpProgress($userId, $sessionId);
1280
1281
        return [$result];
1282
    }
1283
1284
    /**
1285
     * @throws Exception
1286
     */
1287
    public function getCourseLearnPaths(): array
1288
    {
1289
        Event::event_access_tool(TOOL_LEARNPATH);
1290
1291
        $sessionId = $this->session ? $this->session->getId() : 0;
1292
        $categoriesTempList = learnpath::getCategories($this->course->getId());
1293
1294
        $categoryNone = new CLpCategory();
1295
        $categoryNone->setId(0);
1296
        $categoryNone->setName(get_lang('WithOutCategory'));
1297
        $categoryNone->setPosition(0);
1298
1299
        $categories = array_merge([$categoryNone], $categoriesTempList);
1300
        $categoryData = [];
1301
1302
        /** @var CLpCategory $category */
1303
        foreach ($categories as $category) {
1304
            $learnPathList = new LearnpathList(
1305
                $this->user->getId(),
1306
                api_get_course_info($this->course->getCode()),
1307
                $sessionId,
1308
                null,
1309
                false,
1310
                $category->getId()
1311
            );
1312
1313
            $flatLpList = $learnPathList->get_flat_list();
1314
1315
            if (empty($flatLpList)) {
1316
                continue;
1317
            }
1318
1319
            $listData = [];
1320
1321
            foreach ($flatLpList as $lpId => $lpDetails) {
1322
                if ($lpDetails['lp_visibility'] == 0) {
1323
                    continue;
1324
                }
1325
1326
                if (!learnpath::is_lp_visible_for_student(
1327
                    $lpId,
1328
                    $this->user->getId(),
1329
                    api_get_course_info($this->course->getCode()),
1330
                    $sessionId
1331
                )) {
1332
                    continue;
1333
                }
1334
1335
                $timeLimits = false;
1336
1337
                // This is an old LP (from a migration 1.8.7) so we do nothing
1338
                if (empty($lpDetails['created_on']) && empty($lpDetails['modified_on'])) {
1339
                    $timeLimits = false;
1340
                }
1341
1342
                // Checking if expired_on is ON
1343
                if (!empty($lpDetails['expired_on'])) {
1344
                    $timeLimits = true;
1345
                }
1346
1347
                if ($timeLimits) {
1348
                    if (!empty($lpDetails['publicated_on']) && !empty($lpDetails['expired_on'])) {
1349
                        $startTime = api_strtotime($lpDetails['publicated_on'], 'UTC');
1350
                        $endTime = api_strtotime($lpDetails['expired_on'], 'UTC');
1351
                        $now = time();
1352
                        $isActiveTime = false;
1353
1354
                        if ($now > $startTime && $endTime > $now) {
1355
                            $isActiveTime = true;
1356
                        }
1357
1358
                        if (!$isActiveTime) {
1359
                            continue;
1360
                        }
1361
                    }
1362
                }
1363
1364
                $progress = learnpath::getProgress($lpId, $this->user->getId(), $this->course->getId(), $sessionId);
1365
1366
                $listData[] = [
1367
                    'id' => $lpId,
1368
                    'title' => Security::remove_XSS($lpDetails['lp_name']),
1369
                    'progress' => $progress,
1370
                    'url' => api_get_path(WEB_CODE_PATH).'webservices/api/v2.php?'.http_build_query(
1371
                        [
1372
                            'hash' => $this->encodeParams(
1373
                                [
1374
                                    'action' => 'course_learnpath',
1375
                                    'lp_id' => $lpId,
1376
                                    'course' => $this->course->getId(),
1377
                                    'session' => $sessionId,
1378
                                ]
1379
                            ),
1380
                        ]
1381
                    ),
1382
                ];
1383
            }
1384
1385
            if (empty($listData)) {
1386
                continue;
1387
            }
1388
1389
            $categoryData[] = [
1390
                'id' => $category->getId(),
1391
                'name' => $category->getName(),
1392
                'learnpaths' => $listData,
1393
            ];
1394
        }
1395
1396
        return $categoryData;
1397
    }
1398
1399
    /**
1400
     * Start login for a user. Then make a redirect to show the learnpath.
1401
     */
1402
    public function showLearningPath(int $lpId)
1403
    {
1404
        $loggedUser['user_id'] = $this->user->getId();
1405
        $loggedUser['status'] = $this->user->getStatus();
1406
        $loggedUser['uidReset'] = true;
1407
        $sessionId = $this->session ? $this->session->getId() : 0;
1408
1409
        ChamiloSession::write('_user', $loggedUser);
1410
        Login::init_user($this->user->getId(), true);
1411
1412
        $url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.http_build_query(
1413
            [
1414
                'cidReq' => $this->course->getCode(),
1415
                'id_session' => $sessionId,
1416
                'gidReq' => 0,
1417
                'gradebook' => 0,
1418
                'origin' => '',
1419
                'action' => 'view',
1420
                'lp_id' => (int) $lpId,
1421
                'isStudentView' => 'true',
1422
            ]
1423
        );
1424
1425
        header("Location: $url");
1426
        exit;
1427
    }
1428
1429
    /**
1430
     * @param int $forumId
1431
     *
1432
     * @return array
1433
     */
1434
    public function saveForumPost(array $postValues, $forumId)
1435
    {
1436
        Event::event_access_tool(TOOL_FORUM);
1437
1438
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
1439
1440
        $forum = get_forums($forumId, $this->course->getCode());
1441
        store_reply($forum, $postValues, $this->course->getId(), $this->user->getId());
1442
1443
        return [
1444
            'registered' => true,
1445
        ];
1446
    }
1447
1448
    /**
1449
     * Get the list of sessions for current user.
1450
     *
1451
     * @return array the sessions list
1452
     */
1453
    public function getUserSessions()
1454
    {
1455
        $data = [];
1456
        $sessionsByCategory = UserManager::get_sessions_by_category($this->user->getId(), false);
1457
1458
        $webCodePath = api_get_path(WEB_CODE_PATH).'webservices/api/v2.php?';
1459
1460
        foreach ($sessionsByCategory as $category) {
1461
            $categorySessions = [];
1462
1463
            foreach ($category['sessions'] as $sessions) {
1464
                $sessionCourses = [];
1465
1466
                foreach ($sessions['courses'] as $course) {
1467
                    $courseInfo = api_get_course_info_by_id($course['real_id']);
1468
                    $teachers = SessionManager::getCoachesByCourseSessionToString(
1469
                        $sessions['session_id'],
1470
                        $course['real_id']
1471
                    );
1472
1473
                    $sessionCourses[] = [
1474
                        'id' => $courseInfo['real_id'],
1475
                        'title' => $courseInfo['title'],
1476
                        'code' => $courseInfo['code'],
1477
                        'directory' => $courseInfo['directory'],
1478
                        'pictureUrl' => $courseInfo['course_image_large'],
1479
                        'urlPicture' => $courseInfo['course_image_large'],
1480
                        'teachers' => $teachers,
1481
                        'url' => $webCodePath.http_build_query(
1482
                            [
1483
                                'action' => self::VIEW_COURSE_HOME,
1484
                                'api_key' => $this->apiKey,
1485
                                'username' => $this->user->getUsername(),
1486
                                'course' => $courseInfo['real_id'],
1487
                                'session' => $sessions['session_id'],
1488
                            ]
1489
                        ),
1490
                    ];
1491
                }
1492
1493
                $sessionBox = Display::getSessionTitleBox($sessions['session_id']);
1494
1495
                $categorySessions[] = [
1496
                    'name' => $sessionBox['title'],
1497
                    'id' => $sessions['session_id'],
1498
                    'date' => $sessionBox['dates'],
1499
                    'duration' => isset($sessionBox['duration']) ? $sessionBox['duration'] : null,
1500
                    'courses' => $sessionCourses,
1501
                ];
1502
            }
1503
1504
            $data[] = [
1505
                'id' => $category['session_category']['id'],
1506
                'name' => $category['session_category']['name'],
1507
                'sessions' => $categorySessions,
1508
            ];
1509
        }
1510
1511
        return $data;
1512
    }
1513
1514
    public function getUsersSubscribedToCourse()
1515
    {
1516
        $users = CourseManager::get_user_list_from_course_code($this->course->getCode());
1517
1518
        $userList = [];
1519
        foreach ($users as $user) {
1520
            $userList[] = [
1521
                'user_id' => $user['user_id'],
1522
                'username' => $user['username'],
1523
                'firstname' => $user['firstname'],
1524
                'lastname' => $user['lastname'],
1525
                'status_rel' => $user['status_rel'],
1526
            ];
1527
        }
1528
1529
        return $userList;
1530
    }
1531
1532
    /**
1533
     * @param string $subject
1534
     * @param string $text
1535
     *
1536
     * @return array
1537
     */
1538
    public function saveUserMessage($subject, $text, array $receivers, $only_local)
1539
    {
1540
        foreach ($receivers as $userId) {
1541
            MessageManager::send_message(
1542
                $userId,
1543
                $subject,
1544
                $text,
1545
                [],
1546
                [],
1547
                0,
1548
                0,
1549
                0,
1550
                0,
1551
                0,
1552
                false,
1553
                0,
1554
                [],
1555
                false,
1556
                false,
1557
                0,
1558
                [],
1559
                false,
1560
                null,
1561
                $only_local);
1562
        }
1563
1564
        return [
1565
            'sent' => true,
1566
        ];
1567
    }
1568
1569
    /**
1570
     * @param string $search
1571
     *
1572
     * @return array
1573
     */
1574
    public function getMessageUsers($search)
1575
    {
1576
        $repo = UserManager::getRepository();
1577
1578
        $users = $repo->findUsersToSendMessage($this->user->getId(), $search);
1579
        $showEmail = api_get_setting('show_email_addresses') === 'true';
1580
        $data = [];
1581
1582
        /** @var User $user */
1583
        foreach ($users as $user) {
1584
            $userName = UserManager::formatUserFullName($user);
1585
1586
            if ($showEmail) {
1587
                $userName .= " ({$user->getEmail()})";
1588
            }
1589
1590
            $data[] = [
1591
                'id' => $user->getId(),
1592
                'name' => $userName,
1593
            ];
1594
        }
1595
1596
        return $data;
1597
    }
1598
1599
    /**
1600
     * @param string $title
1601
     * @param string $text
1602
     *
1603
     * @return array
1604
     */
1605
    public function saveCourseNotebook($title, $text)
1606
    {
1607
        Event::event_access_tool(TOOL_NOTEBOOK);
1608
1609
        $values = ['note_title' => $title, 'note_comment' => $text];
1610
        $sessionId = $this->session ? $this->session->getId() : 0;
1611
1612
        $noteBookId = NotebookManager::save_note(
1613
            $values,
1614
            $this->user->getId(),
1615
            $this->course->getId(),
1616
            $sessionId
1617
        );
1618
1619
        return [
1620
            'registered' => $noteBookId,
1621
        ];
1622
    }
1623
1624
    /**
1625
     * @param int $forumId
1626
     *
1627
     * @return array
1628
     */
1629
    public function saveForumThread(array $values, $forumId)
1630
    {
1631
        Event::event_access_tool(TOOL_FORUM);
1632
1633
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
1634
1635
        $sessionId = $this->session ? $this->session->getId() : 0;
1636
        $forum = get_forums($forumId, $this->course->getCode(), true, $sessionId);
1637
        $courseInfo = api_get_course_info($this->course->getCode());
1638
        $thread = store_thread($forum, $values, $courseInfo, false, $this->user->getId(), $sessionId);
1639
1640
        return [
1641
            'registered' => $thread->getIid(),
1642
        ];
1643
    }
1644
1645
    /**
1646
     * Returns an array of users with id, firstname, lastname, email and username.
1647
     *
1648
     * @param array $params An array of parameters to filter the results (currently supports 'status', 'id_campus' and 'extra_fields')
1649
     *
1650
     * @throws Exception
1651
     */
1652
    public function getUsersCampus(array $params): array
1653
    {
1654
        self::protectAdminEndpoint();
1655
1656
        if ('*' === $params['status']) {
1657
            $conditions = [];
1658
        } else {
1659
            $conditions = [
1660
                'status' => $params['status'],
1661
            ];
1662
        }
1663
        $idCampus = !empty($params['id_campus']) ?? 1;
1664
        $fields = [];
1665
        if (!empty($params['extra_fields'])) {
1666
            //extra_fields must be sent as a comma-separated list of extra_field variable names
1667
            $fields = explode(',', $params['extra_fields']);
1668
        }
1669
        $users = UserManager::get_user_list($conditions, ['firstname'], false, false, $idCampus);
1670
        $list = [];
1671
        foreach ($users as $item) {
1672
            $listTemp = [
1673
                'id' => $item['user_id'],
1674
                'firstname' => $item['firstname'],
1675
                'lastname' => $item['lastname'],
1676
                'email' => $item['email'],
1677
                'username' => $item['username'],
1678
                'active' => $item['active'],
1679
            ];
1680
            foreach ($fields as $field) {
1681
                $field = trim($field);
1682
                $value = UserManager::get_extra_user_data_by_field($item['user_id'], $field);
1683
                if (!empty($value)) {
1684
                    $listTemp[$field] = $value[$field];
1685
                }
1686
            }
1687
            $list[] = $listTemp;
1688
        }
1689
1690
        return $list;
1691
    }
1692
1693
    /**
1694
     * Returns a list of courses in the given URL. If no URL is provided, we assume we are not in a multi-URL setup and
1695
     * return all the courses.
1696
     */
1697
    public function getCoursesCampus(int $campusId = 0): array
1698
    {
1699
        return CourseManager::get_courses_list(
1700
            0, //offset
1701
            0, //howMany
1702
            1, //$orderby = 1
1703
            'ASC',
1704
            -1, //visibility
1705
            null,
1706
            empty($campusId) ? null : $campusId, //$urlId
1707
            true //AlsoSearchCode
1708
        );
1709
    }
1710
1711
    /**
1712
     * Returns a list of sessions in the given URL. If no URL is provided, we assume we are not in a multi-URL setup and
1713
     * return all the sessions.
1714
     *
1715
     * @param int $campusId Optional
1716
     *
1717
     * @throws Exception
1718
     */
1719
    public function getSessionsCampus(int $campusId = 0, bool $getExtraFields = false): array
1720
    {
1721
        self::protectAdminEndpoint();
1722
1723
        $list = SessionManager::get_sessions_list(
1724
            [],
1725
            [],
1726
            null,
1727
            null,
1728
            $campusId
1729
        );
1730
        $shortList = [];
1731
        foreach ($list as $session) {
1732
            $bundle = [
1733
                'id' => $session['id'],
1734
                'name' => $session['name'],
1735
                'access_start_date' => $session['access_start_date'],
1736
                'access_end_date' => $session['access_end_date'],
1737
            ];
1738
            if ($getExtraFields) {
1739
                $extraFieldValues = new ExtraFieldValue('session');
1740
                $extraFields = $extraFieldValues->getAllValuesByItem($session['id']);
1741
                $bundle['extra_fields'] = $extraFields;
1742
            }
1743
            $shortList[] = $bundle;
1744
        }
1745
1746
        return $shortList;
1747
    }
1748
1749
    /**
1750
     * Returns an array of groups with id, group_type, name, description, visibility.
1751
     *
1752
     * @param array $params An array of parameters to filter the results (currently supports 'type')
1753
     *
1754
     * @throws Exception
1755
     */
1756
    public function getGroups(array $params): array
1757
    {
1758
        self::protectAdminEndpoint();
1759
1760
        if ('*' === $params['type']) {
1761
            $conditions = [];
1762
        } else {
1763
            $conditions = ['where' => ['group_type = ?' => $params['type']]];
1764
        }
1765
        $userGroup = new UserGroup();
1766
        $groups = $userGroup->getDataToExport($conditions);
1767
        $list = [];
1768
        /** @var \Chamilo\UserBundle\Entity\Group $item */
1769
        foreach ($groups as $item) {
1770
            $listTemp = [
1771
                'id' => $item['id'],
1772
                'name' => $item['name'],
1773
                'description' => $item['description'],
1774
                'visibility' => $item['visibility'],
1775
                'type' => $item['group_type'],
1776
            ];
1777
            if (in_array($item['group_type'], [0, 1])) {
1778
                $listTemp['type_name'] = ($item['group_type'] == 0) ? 'class' : 'social';
1779
            }
1780
            if (in_array($item['visibility'], [1, 2])) {
1781
                $listTemp['visibility_name'] = ($item['visibility'] == 1) ? 'open' : 'closed';
1782
            }
1783
            $list[] = $listTemp;
1784
        }
1785
1786
        return $list;
1787
    }
1788
1789
    /**
1790
     * @throws Exception
1791
     */
1792
    public function addSession(array $params): array
1793
    {
1794
        self::protectAdminEndpoint();
1795
1796
        $name = $params['name'];
1797
        $coach_username = (int) $params['coach_username'];
1798
        $startDate = $params['access_start_date'];
1799
        $endDate = $params['access_end_date'];
1800
        $displayStartDate = $startDate;
1801
        $displayEndDate = $endDate;
1802
        $description = $params['description'];
1803
        $idUrlCampus = $params['id_campus'];
1804
        $extraFields = isset($params['extra']) ? $params['extra'] : [];
1805
1806
        $return = SessionManager::create_session(
1807
            $name,
1808
            $startDate,
1809
            $endDate,
1810
            $displayStartDate,
1811
            $displayEndDate,
1812
            null,
1813
            null,
1814
            $coach_username,
1815
            null,
1816
            1,
1817
            false,
1818
            null,
1819
            $description,
1820
            1,
1821
            $extraFields,
1822
            null,
1823
            false,
1824
            $idUrlCampus
1825
        );
1826
1827
        if ($return) {
1828
            $out = [
1829
                'status' => true,
1830
                'message' => get_lang('ANewSessionWasCreated'),
1831
                'id_session' => $return,
1832
            ];
1833
        } else {
1834
            $out = [
1835
                'status' => false,
1836
                'message' => get_lang('ErrorOccurred'),
1837
            ];
1838
        }
1839
1840
        return $out;
1841
    }
1842
1843
    /**
1844
     * @throws Exception
1845
     */
1846
    public function addCourse(ParameterBag $request): array
1847
    {
1848
        self::protectAdminEndpoint();
1849
1850
        $idCampus = $request->getInt('id_campus', 1);
1851
        $title = $request->get('title');
1852
        $wantedCode = $request->get('wanted_code');
1853
        $diskQuota = $request->getInt('disk_quota', 100);
1854
        $visibility = $request->getInt('visibility');
1855
        $removeCampusId = $request->getBoolean('remove_campus_id_from_wanted_code');
1856
        $language = $request->get('language');
1857
1858
        if (!isset(Course::getStatusList()[$visibility] )) {
1859
            throw new Exception(get_lang('VisibilityCannotBeChanged'));
1860
        }
1861
1862
        $params = [];
1863
        $params['title'] = $title;
1864
        $params['wanted_code'] = 'CAMPUS_'.$idCampus.'_'.$wantedCode;
1865
        if (1 === (int) $removeCampusId) {
1866
            $params['wanted_code'] = $wantedCode;
1867
        }
1868
        $params['user_id'] = $this->user->getId();
1869
        $params['visibility'] = $visibility;
1870
        $params['disk_quota'] = $diskQuota;
1871
        $params['course_language'] = $language;
1872
1873
        foreach ($request->all() as $key => $value) {
1874
            if (substr($key, 0, 6) === 'extra_') { //an extra field
1875
                $params[$key] = $value;
1876
            }
1877
        }
1878
1879
        if ('true' === api_get_setting('teacher_can_select_course_template')) {
1880
            $params['course_template'] = $request->getInt('course_template');
1881
        }
1882
1883
        $courseInfo = CourseManager::create_course($params, $params['user_id'], $idCampus);
1884
        $results = [];
1885
        if (!empty($courseInfo)) {
1886
            $results['status'] = true;
1887
            $results['id'] = $courseInfo['real_id'];
1888
            $results['code_course'] = $courseInfo['code'];
1889
            $results['title_course'] = $courseInfo['title'];
1890
            $extraFieldValues = new ExtraFieldValue('course');
1891
            $extraFields = $extraFieldValues->getAllValuesByItem($courseInfo['real_id']);
1892
            $results['extra_fields'] = $extraFields;
1893
            $results['message'] = sprintf(get_lang('CourseXAdded'), $courseInfo['code']);
1894
        } else {
1895
            $results['status'] = false;
1896
            $results['message'] = get_lang('CourseCreationFailed');
1897
        }
1898
1899
        return $results;
1900
    }
1901
1902
    /**
1903
     * @param $userParam
1904
     *
1905
     * @throws Exception
1906
     */
1907
    public function addUser($userParam): array
1908
    {
1909
        self::protectAdminEndpoint();
1910
1911
        $firstName = $userParam['firstname'];
1912
        $lastName = $userParam['lastname'];
1913
        $status = $userParam['status'];
1914
        $email = $userParam['email'];
1915
        $loginName = $userParam['loginname'];
1916
        $password = $userParam['password'];
1917
1918
        $official_code = '';
1919
        $language = '';
1920
        $phone = '';
1921
        $picture_uri = '';
1922
        $auth_source = $userParam['auth_source'] ?? PLATFORM_AUTH_SOURCE;
1923
        $expiration_date = '';
1924
        $active = 1;
1925
        $hr_dept_id = 0;
1926
        $original_user_id_name = $userParam['original_user_id_name'];
1927
        $original_user_id_value = $userParam['original_user_id_value'];
1928
        $sendMail = (empty($userParam['send_mail']) ? false : true);
1929
1930
        $extra_list = isset($userParam['extra']) ? $userParam['extra'] : [];
1931
        if (isset($userParam['language'])) {
1932
            $language = $userParam['language'];
1933
        }
1934
        if (isset($userParam['phone'])) {
1935
            $phone = $userParam['phone'];
1936
        }
1937
        if (isset($userParam['official_code'])) {
1938
            $official_code = $userParam['official_code'];
1939
        }
1940
        if (isset($userParam['expiration_date'])) {
1941
            $expiration_date = $userParam['expiration_date'];
1942
        }
1943
1944
        // If check_email_duplicates was set, trigger exception (i.e. do not create) if the e-mail is already used
1945
        if ($userParam['check_email_duplicates']) {
1946
            if (!empty($email)) {
1947
                $userFromEmail = api_get_user_info_from_email($email);
1948
                if (!empty($userFromEmail)) {
1949
                    throw new Exception(get_lang('EmailUsedTwice'));
1950
                }
1951
            }
1952
        }
1953
1954
        // Default language.
1955
        if (empty($language)) {
1956
            $language = api_get_setting('platformLanguage');
1957
        }
1958
1959
        // First check whether the login already exists.
1960
        if (!UserManager::is_username_available($loginName)) {
1961
            throw new Exception(get_lang('UserNameNotAvailable'));
1962
        }
1963
1964
        $userId = UserManager::create_user(
1965
            $firstName,
1966
            $lastName,
1967
            $status,
1968
            $email,
1969
            $loginName,
1970
            $password,
1971
            $official_code,
1972
            $language,
1973
            $phone,
1974
            $picture_uri,
1975
            $auth_source,
1976
            $expiration_date,
1977
            $active,
1978
            $hr_dept_id,
1979
            [],
1980
            '',
1981
            $sendMail
1982
        );
1983
1984
        if (empty($userId)) {
1985
            throw new Exception(get_lang('UserNotRegistered'));
1986
        }
1987
1988
        if (api_is_multiple_url_enabled()) {
1989
            if (api_get_current_access_url_id() != -1) {
1990
                UrlManager::add_user_to_url(
1991
                    $userId,
1992
                    api_get_current_access_url_id()
1993
                );
1994
            } else {
1995
                UrlManager::add_user_to_url($userId, 1);
1996
            }
1997
        } else {
1998
            // We add by default the access_url_user table with access_url_id = 1
1999
            UrlManager::add_user_to_url($userId, 1);
2000
        }
2001
2002
        // Save new field label into user_field table.
2003
        UserManager::create_extra_field(
2004
            $original_user_id_name,
2005
            1,
2006
            $original_user_id_name,
2007
            ''
2008
        );
2009
        // Save the external system's id into user_field_value table.
2010
        UserManager::update_extra_field_value(
2011
            $userId,
2012
            $original_user_id_name,
2013
            $original_user_id_value
2014
        );
2015
2016
        if (is_array($extra_list) && count($extra_list) > 0) {
2017
            foreach ($extra_list as $extra) {
2018
                $extra_field_name = $extra['field_name'];
2019
                $extra_field_value = $extra['field_value'];
2020
                // Save new field label into user_field table.
2021
                UserManager::create_extra_field(
2022
                    $extra_field_name,
2023
                    1,
2024
                    $extra_field_name,
2025
                    ''
2026
                );
2027
                // Save the external system's id into user_field_value table.
2028
                UserManager::update_extra_field_value(
2029
                    $userId,
2030
                    $extra_field_name,
2031
                    $extra_field_value
2032
                );
2033
            }
2034
        }
2035
2036
        return [$userId];
2037
    }
2038
2039
    /**
2040
     * @throws Exception
2041
     */
2042
    public function addUserGetApikey(array $userParams): array
2043
    {
2044
        list($userId) = $this->addUser($userParams);
2045
2046
        UserManager::add_api_key($userId, self::SERVICE_NAME);
2047
2048
        $apiKey = UserManager::get_api_keys($userId, self::SERVICE_NAME);
2049
2050
        return [
2051
            'id' => $userId,
2052
            'api_key' => current($apiKey),
2053
        ];
2054
    }
2055
2056
    /**
2057
     * @throws Exception
2058
     */
2059
    public function updateUserApiKey(int $userId, string $oldApiKey): array
2060
    {
2061
        if (!api_is_platform_admin() && $userId != $this->user->getId()) {
2062
            self::throwNotAllowedException();
2063
        }
2064
2065
        if (false === $currentApiKeys = UserManager::get_api_keys($userId, self::SERVICE_NAME)) {
2066
            self::throwNotAllowedException();
2067
        }
2068
2069
        if (current($currentApiKeys) !== $oldApiKey) {
2070
            self::throwNotAllowedException();
2071
        }
2072
2073
        UserManager::update_api_key($userId, self::SERVICE_NAME);
2074
2075
        $apiKey = UserManager::get_api_keys($userId, self::SERVICE_NAME);
2076
2077
        return [
2078
            'api_key' => current($apiKey),
2079
        ];
2080
    }
2081
2082
    /**
2083
     * Subscribe User to Course.
2084
     *
2085
     * @throws Exception
2086
     */
2087
    public function subscribeUserToCourse(array $params): array
2088
    {
2089
        $course_id = $params['course_id'];
2090
        $course_code = $params['course_code'];
2091
        $user_id = $params['user_id'];
2092
        $status = $params['status'] ?? STUDENT;
2093
2094
        if (!api_is_platform_admin() && $user_id != $this->user->getId()) {
2095
            self::throwNotAllowedException();
2096
        }
2097
2098
        if (!$course_id && !$course_code) {
2099
            return [false];
2100
        }
2101
        if (!$course_code) {
2102
            $course_code = CourseManager::get_course_code_from_course_id($course_id);
2103
        }
2104
2105
        if (CourseManager::subscribeUser($user_id, $course_code, $status, 0, 0, false)) {
2106
            return [true];
2107
        }
2108
2109
        return [false];
2110
    }
2111
2112
    /**
2113
     * @throws Exception
2114
     */
2115
    public function subscribeUserToCoursePassword($courseCode, $password)
2116
    {
2117
        $courseInfo = api_get_course_info($courseCode);
2118
2119
        if (empty($courseInfo)) {
2120
            throw new Exception(get_lang('NoCourse'));
2121
        }
2122
2123
        if (sha1($password) === $courseInfo['registration_code']) {
2124
            CourseManager::processAutoSubscribeToCourse($courseCode);
2125
2126
            return;
2127
        }
2128
2129
        throw new Exception(get_lang('CourseRegistrationCodeIncorrect'));
2130
    }
2131
2132
    /**
2133
     * @throws Exception
2134
     */
2135
    public function unSubscribeUserToCourse(array $params): array
2136
    {
2137
        $courseId = $params['course_id'];
2138
        $courseCode = $params['course_code'];
2139
        $userId = $params['user_id'];
2140
2141
        if (!api_is_platform_admin() && $userId != $this->user->getId()) {
2142
            self::throwNotAllowedException();
2143
        }
2144
2145
        if (!$courseId && !$courseCode) {
2146
            return [false];
2147
        }
2148
2149
        if (!$courseCode) {
2150
            $courseCode = CourseManager::get_course_code_from_course_id($courseId);
2151
        }
2152
2153
        if (CourseManager::unsubscribe_user($userId, $courseCode)) {
2154
            return [true];
2155
        }
2156
2157
        return [false];
2158
    }
2159
2160
    public function deleteUserMessage($messageId, $messageType)
2161
    {
2162
        if ($messageType === 'sent') {
2163
            return MessageManager::delete_message_by_user_sender($this->user->getId(), $messageId);
2164
        } else {
2165
            return MessageManager::delete_message_by_user_receiver($this->user->getId(), $messageId);
2166
        }
2167
    }
2168
2169
    /**
2170
     * Set a given message as already read.
2171
     *
2172
     * @param $messageId
2173
     */
2174
    public function setMessageRead(int $messageId)
2175
    {
2176
        // MESSAGE_STATUS_NEW is also used for messages that have been "read"
2177
        MessageManager::update_message_status($this->user->getId(), $messageId, MESSAGE_STATUS_NEW);
2178
    }
2179
2180
    /**
2181
     * Add a group.
2182
     *
2183
     * @param array Params
2184
     */
2185
    public function createGroup($params)
2186
    {
2187
        self::protectAdminEndpoint();
2188
2189
        $name = $params['name'];
2190
        $description = $params['description'];
2191
    }
2192
2193
    /**
2194
     * Add Campus Virtual.
2195
     *
2196
     * @param array Params Campus
2197
     *
2198
     * @return array
2199
     */
2200
    public function createCampusURL($params)
2201
    {
2202
        $urlCampus = Security::remove_XSS($params['url']);
2203
        $description = Security::remove_XSS($params['description']);
2204
2205
        $active = isset($params['active']) ? intval($params['active']) : 0;
2206
        $num = UrlManager::url_exist($urlCampus);
2207
        if ($num == 0) {
2208
            // checking url
2209
            if (substr($urlCampus, strlen($urlCampus) - 1, strlen($urlCampus)) == '/') {
2210
                $idCampus = UrlManager::add($urlCampus, $description, $active, true);
2211
            } else {
2212
                //create
2213
                $idCampus = UrlManager::add($urlCampus.'/', $description, $active, true);
2214
            }
2215
2216
            return [
2217
                'status' => true,
2218
                'id_campus' => $idCampus,
2219
            ];
2220
        }
2221
2222
        return [
2223
            'status' => false,
2224
            'id_campus' => 0,
2225
        ];
2226
    }
2227
2228
    /**
2229
     * Edit Campus Virtual.
2230
     *
2231
     * @param array Params Campus
2232
     *
2233
     * @return array
2234
     */
2235
    public function editCampusURL($params)
2236
    {
2237
        $urlCampus = Security::remove_XSS($params['url']);
2238
        $description = Security::remove_XSS($params['description']);
2239
2240
        $active = isset($params['active']) ? intval($params['active']) : 0;
2241
        $url_id = isset($params['id']) ? intval($params['id']) : 0;
2242
2243
        if (!empty($url_id)) {
2244
            //we can't change the status of the url with id=1
2245
            if ($url_id == 1) {
2246
                $active = 1;
2247
            }
2248
            //checking url
2249
            if (substr($urlCampus, strlen($urlCampus) - 1, strlen($urlCampus)) == '/') {
2250
                UrlManager::update($url_id, $urlCampus, $description, $active);
2251
            } else {
2252
                UrlManager::update($url_id, $urlCampus.'/', $description, $active);
2253
            }
2254
2255
            return [true];
2256
        }
2257
2258
        return [false];
2259
    }
2260
2261
    /**
2262
     * Delete Campus Virtual.
2263
     *
2264
     * @param array Params Campus
2265
     *
2266
     * @return array
2267
     */
2268
    public function deleteCampusURL($params)
2269
    {
2270
        $url_id = isset($params['id']) ? intval($params['id']) : 0;
2271
2272
        $result = UrlManager::delete($url_id);
2273
        if ($result) {
2274
            return [
2275
                'status' => true,
2276
                'message' => get_lang('URLDeleted'),
2277
            ];
2278
        } else {
2279
            return [
2280
                'status' => false,
2281
                'message' => get_lang('Error'),
2282
            ];
2283
        }
2284
    }
2285
2286
    /**
2287
     * @throws Exception
2288
     */
2289
    public function addCoursesSession(array $params): array
2290
    {
2291
        self::protectAdminEndpoint();
2292
2293
        $sessionId = $params['id_session'];
2294
        $courseList = $params['list_courses'];
2295
        $importAssignments = isset($params['import_assignments']) && 1 === (int) $params['import_assignments'];
2296
2297
        $result = SessionManager::add_courses_to_session(
2298
            $sessionId,
2299
            $courseList,
2300
            true,
2301
            false,
2302
            false,
2303
            $importAssignments
2304
        );
2305
2306
        if ($result) {
2307
            return [
2308
                'status' => $result,
2309
                'message' => get_lang('Updated'),
2310
            ];
2311
        }
2312
2313
        return [
2314
            'status' => $result,
2315
            'message' => get_lang('ErrorOccurred'),
2316
        ];
2317
    }
2318
2319
    /**
2320
     * Simple legacy shortcut to subscribeUsersToSession.
2321
     *
2322
     * @throws Exception
2323
     */
2324
    public function addUsersSession(array $params): array
2325
    {
2326
        return self::subscribeUsersToSession($params);
2327
    }
2328
2329
    /**
2330
     * Subscribe a list of users to the given session.
2331
     *
2332
     * @param array $params Containing 'id_session' and 'list_users' entries
2333
     *
2334
     * @throws Exception
2335
     */
2336
    public function subscribeUsersToSession(array $params): array
2337
    {
2338
        $sessionId = $params['id_session'];
2339
        $userList = $params['list_users'];
2340
2341
        if (!is_array($userList)) {
2342
            $userList = [];
2343
        }
2344
2345
        if (!api_is_platform_admin() && !in_array($this->user->getId(), $userList)) {
2346
            self::throwNotAllowedException();
2347
        }
2348
2349
        SessionManager::subscribeUsersToSession(
2350
            $sessionId,
2351
            $userList,
2352
            null,
2353
            false
2354
        );
2355
2356
        return [
2357
            'status' => true,
2358
            'message' => get_lang('UsersAdded'),
2359
        ];
2360
    }
2361
2362
    /**
2363
     * Unsubscribe a given list of users from the given session.
2364
     *
2365
     * @throws Exception
2366
     */
2367
    public function unsubscribeUsersFromSession(array $params): array
2368
    {
2369
        self::protectAdminEndpoint();
2370
2371
        $sessionId = $params['id_session'];
2372
        $userList = $params['list_users'];
2373
2374
        if (!is_array($userList)) {
2375
            $userList = [];
2376
        }
2377
2378
        if (!api_is_platform_admin() && !in_array($this->user->getId(), $userList)) {
2379
            self::throwNotAllowedException();
2380
        }
2381
2382
        foreach ($userList as $userId) {
2383
            SessionManager::unsubscribe_user_from_session(
2384
                $sessionId,
2385
                $userId
2386
            );
2387
        }
2388
2389
        return [
2390
            'status' => true,
2391
            'message' => get_lang('UserUnsubscribed'),
2392
        ];
2393
    }
2394
2395
    /**
2396
     * Creates a session from a model session.
2397
     *
2398
     * @throws Exception
2399
     */
2400
    public function createSessionFromModel(HttpRequest $request): int
2401
    {
2402
        self::protectAdminEndpoint();
2403
2404
        $modelSessionId = $request->request->getInt('modelSessionId');
2405
        $sessionName = $request->request->get('sessionName');
2406
        $startDate = $request->request->get('startDate');
2407
        $endDate = $request->request->get('endDate');
2408
        $extraFields = $request->request->get('extraFields', []);
2409
        $duplicateAgendaContent = $request->request->getBoolean('duplicateAgendaContent');
2410
2411
        if (empty($modelSessionId) || empty($sessionName) || empty($startDate) || empty($endDate)) {
2412
            throw new Exception(get_lang('NoData'));
2413
        }
2414
2415
        if (!SessionManager::isValidId($modelSessionId)) {
2416
            throw new Exception(get_lang('ModelSessionDoesNotExist'));
2417
        }
2418
2419
        $modelSession = SessionManager::fetch($modelSessionId);
2420
2421
        $modelSession['accessUrlId'] = 1;
2422
        if (api_is_multiple_url_enabled()) {
2423
            if (api_get_current_access_url_id() != -1) {
2424
                $modelSession['accessUrlId'] = api_get_current_access_url_id();
2425
            }
2426
        }
2427
2428
        $newSessionId = SessionManager::create_session(
2429
            $sessionName,
2430
            $startDate,
2431
            $endDate,
2432
            $startDate,
2433
            $endDate,
2434
            $startDate,
2435
            $endDate,
2436
            $modelSession['id_coach'],
2437
            $modelSession['session_category_id'],
2438
            $modelSession['visibility'],
2439
            false,
2440
            $modelSession['duration'],
2441
            $modelSession['description'],
2442
            $modelSession['show_description'],
2443
            $extraFields,
2444
            $modelSession['session_admin_id'],
2445
            $modelSession['send_subscription_notification'],
2446
            $modelSession['accessUrlId']
2447
        );
2448
2449
        if (empty($newSessionId)) {
2450
            throw new Exception(get_lang('SessionNotRegistered'));
2451
        }
2452
2453
        if (is_string($newSessionId)) {
2454
            throw new Exception($newSessionId);
2455
        }
2456
2457
        $promotionId = $modelSession['promotion_id'];
2458
        if ($promotionId) {
2459
            $sessionList = array_keys(SessionManager::get_all_sessions_by_promotion($promotionId));
2460
            $sessionList[] = $newSessionId;
2461
            SessionManager::subscribe_sessions_to_promotion($modelSession['promotion_id'], $sessionList);
2462
        }
2463
2464
        $modelExtraFields = [];
2465
        $fields = SessionManager::getFilteredExtraFields($modelSessionId);
2466
        if (is_array($fields) and !empty($fields)) {
2467
            foreach ($fields as $field) {
2468
                $modelExtraFields[$field['variable']] = $field['value'];
2469
            }
2470
        }
2471
        $allExtraFields = array_merge($modelExtraFields, $extraFields);
2472
        foreach ($allExtraFields as $name => $value) {
2473
            // SessionManager::update_session_extra_field_value returns false when no row is changed,
2474
            // which can happen since extra field values are initialized by SessionManager::create_session
2475
            // therefore we do not throw an exception when false is returned
2476
            SessionManager::update_session_extra_field_value($newSessionId, $name, $value);
2477
        }
2478
2479
        $courseList = array_keys(SessionManager::get_course_list_by_session_id($modelSessionId));
2480
        if (is_array($courseList)
2481
            && !empty($courseList)
2482
            && !SessionManager::add_courses_to_session($newSessionId, $courseList)) {
2483
            throw new Exception(get_lang('CoursesNotAddedToSession'));
2484
        }
2485
2486
        $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
2487
        $courseListOrdered = SessionManager::get_course_list_by_session_id($modelSessionId, null, 'position');
2488
        $count = 0;
2489
        foreach ($courseListOrdered as $course) {
2490
            if ($course['position'] == '') {
2491
                $course['position'] = $count;
2492
            }
2493
            // Saving order.
2494
            $sql = "UPDATE $table SET position = ".$course['position']."
2495
                    WHERE session_id = $newSessionId AND c_id = '".$course['real_id']."'";
2496
            Database::query($sql);
2497
            $count++;
2498
        }
2499
2500
        if ($duplicateAgendaContent) {
2501
            foreach ($courseList as $courseId) {
2502
                SessionManager::importAgendaFromSessionModel($modelSessionId, $newSessionId, $courseId);
2503
            }
2504
        }
2505
2506
        if (api_is_multiple_url_enabled()) {
2507
            if (api_get_current_access_url_id() != -1) {
2508
                UrlManager::add_session_to_url(
2509
                    $newSessionId,
2510
                    api_get_current_access_url_id()
2511
                );
2512
            } else {
2513
                UrlManager::add_session_to_url($newSessionId, 1);
2514
            }
2515
        } else {
2516
            UrlManager::add_session_to_url($newSessionId, 1);
2517
        }
2518
2519
        return $newSessionId;
2520
    }
2521
2522
    /**
2523
     * subscribes a user to a session.
2524
     *
2525
     * @throws Exception
2526
     */
2527
    public function subscribeUserToSessionFromUsername(int $sessionId, string $loginName): bool
2528
    {
2529
        if (!SessionManager::isValidId($sessionId)) {
2530
            throw new Exception(get_lang('SessionNotFound'));
2531
        }
2532
2533
        $userId = UserManager::get_user_id_from_username($loginName);
2534
        if (false === $userId) {
2535
            throw new Exception(get_lang('UserNotFound'));
2536
        }
2537
2538
        if (!api_is_platform_admin() && $userId != $this->user->getId()) {
2539
            self::throwNotAllowedException();
2540
        }
2541
2542
        $subscribed = SessionManager::subscribeUsersToSession(
2543
            $sessionId,
2544
            [$userId],
2545
            SESSION_VISIBLE_READ_ONLY,
2546
            false
2547
        );
2548
        if (!$subscribed) {
2549
            throw new Exception(get_lang('UserNotSubscribed'));
2550
        }
2551
2552
        return true;
2553
    }
2554
2555
    /**
2556
     * Finds the session which has a specific value in a specific extra field and return its ID (only that).
2557
     *
2558
     * @throws Exception when no session matched or more than one session matched
2559
     *
2560
     * @return int The matching session id, or an array with details about the session
2561
     */
2562
    public function getSessionFromExtraField(string $fieldName, string $fieldValue): int
2563
    {
2564
        // find sessions that have that value in the given field
2565
        $valueModel = new ExtraFieldValue('session');
2566
        $sessionIdList = $valueModel->get_item_id_from_field_variable_and_field_value(
2567
            $fieldName,
2568
            $fieldValue,
2569
            false,
2570
            false,
2571
            true
2572
        );
2573
2574
        // throw if none found
2575
        if (empty($sessionIdList)) {
2576
            throw new Exception(get_lang('NoSessionMatched'));
2577
        }
2578
2579
        // throw if more than one found
2580
        if (count($sessionIdList) > 1) {
2581
            throw new Exception(get_lang('MoreThanOneSessionMatched'));
2582
        }
2583
2584
        // return sessionId
2585
        return (int) $sessionIdList[0]['item_id'];
2586
    }
2587
2588
    /**
2589
     * Finds the session which has a specific value in a specific extra field and return its details.
2590
     *
2591
     * @throws Exception when no session matched or more than one session matched
2592
     *
2593
     * @return array The matching session id, or an array with details about the session
2594
     */
2595
    public function getSessionInfoFromExtraField(string $fieldName, string $fieldValue): array
2596
    {
2597
        $session = [];
2598
        // find sessions that have that value in the given field
2599
        $valueModel = new ExtraFieldValue('session');
2600
        $sessionIdList = $valueModel->get_item_id_from_field_variable_and_field_value(
2601
            $fieldName,
2602
            $fieldValue,
2603
            false,
2604
            false,
2605
            true
2606
        );
2607
2608
        // throw if none found
2609
        if (empty($sessionIdList)) {
2610
            throw new Exception(get_lang('NoSessionMatched'));
2611
        }
2612
2613
        // throw if more than one found
2614
        if (count($sessionIdList) > 1) {
2615
            throw new Exception(get_lang('MoreThanOneSessionMatched'));
2616
        }
2617
2618
        $session = api_get_session_info($sessionIdList[0]['item_id']);
2619
        $bundle = [
2620
            'id' => $session['id'],
2621
            'name' => $session['name'],
2622
            'access_start_date' => $session['access_start_date'],
2623
            'access_end_date' => $session['access_end_date'],
2624
        ];
2625
        $extraFieldValues = new ExtraFieldValue('session');
2626
        $extraFields = $extraFieldValues->getAllValuesByItem($session['id']);
2627
        // Only return these properties for each extra_field (the rest is not relevant to a webservice)
2628
        $filter = ['variable', 'value', 'display_text'];
2629
        $bundle['extra_fields'] = array_map(function ($item) use ($filter) {
2630
            return array_intersect_key($item, array_flip($filter));
2631
        }, $extraFields);
2632
2633
        // return session details, including extra fields that have filter=1
2634
        return $bundle;
2635
    }
2636
2637
    /**
2638
     * Get a list of users subscribed to the given session.
2639
     *
2640
     * @params int $sessionId
2641
     * @params int $moveInfo Whether to return the "moved_*" fields or not
2642
     */
2643
    public function getUsersSubscribedToSession(int $sessionId, int $moveInfo = 0): array
2644
    {
2645
        self::protectAdminEndpoint();
2646
2647
        $users = SessionManager::get_users_by_session($sessionId);
2648
2649
        $userList = [];
2650
        foreach ($users as $user) {
2651
            $userInfo = [
2652
                'user_id' => $user['user_id'],
2653
                'username' => $user['username'],
2654
                'firstname' => $user['firstname'],
2655
                'lastname' => $user['lastname'],
2656
                'status' => $user['relation_type'],
2657
            ];
2658
            if (1 === $moveInfo) {
2659
                $userInfo['moved_to'] = $user['moved_to'];
2660
                $userInfo['moved_status'] = $user['moved_status'];
2661
                $userInfo['moved_at'] = $user['moved_at'];
2662
            }
2663
            $userList[] = $userInfo;
2664
        }
2665
2666
        return $userList;
2667
    }
2668
2669
    /**
2670
     * Updates a user identified by its login name.
2671
     *
2672
     * @throws Exception on failure
2673
     *
2674
     * @todo make a safe version for use by the final user on its account
2675
     */
2676
    public function updateUserFromUserName(array $parameters): bool
2677
    {
2678
        // find user
2679
        $userId = null;
2680
        if (empty($parameters)) {
2681
            throw new Exception('NoData');
2682
        }
2683
        foreach ($parameters as $name => $value) {
2684
            if (strtolower($name) === 'loginname') {
2685
                $userId = UserManager::get_user_id_from_username($value);
2686
                if (false === $userId) {
2687
                    throw new Exception(get_lang('UserNotFound'));
2688
                }
2689
                break;
2690
            }
2691
        }
2692
        if (is_null($userId)) {
2693
            throw new Exception(get_lang('NoData'));
2694
        }
2695
2696
        if (!api_is_platform_admin() && $userId != $this->user->getId()) {
2697
            self::throwNotAllowedException();
2698
        }
2699
2700
        if (!empty($parameters['new_login_name'])) {
2701
            // Make sure the new username, if set, is available
2702
            if (!UserManager::is_username_available($parameters['new_login_name'])) {
2703
                throw new Exception(get_lang('LoginAlreadyTaken'));
2704
            }
2705
        }
2706
2707
        /** @var User $user */
2708
        $user = UserManager::getRepository()->find($userId);
2709
        if (empty($user)) {
2710
            throw new Exception(get_lang('CouldNotLoadUser'));
2711
        }
2712
2713
        // tell the world we are about to update a user
2714
        $hook = HookUpdateUser::create();
2715
        if (!empty($hook)) {
2716
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_PRE);
2717
        }
2718
2719
        // apply submitted modifications
2720
        foreach ($parameters as $name => $value) {
2721
            switch (strtolower($name)) {
2722
                case 'email':
2723
                    $user->setEmail($value);
2724
                    break;
2725
                case 'enabled':
2726
                    $user->setEnabled($value);
2727
                    break;
2728
                case 'lastname':
2729
                    $user->setLastname($value);
2730
                    break;
2731
                case 'firstname':
2732
                    $user->setFirstname($value);
2733
                    break;
2734
                case 'new_login_name':
2735
                    $user->setUsername($value);
2736
                    break;
2737
                case 'phone':
2738
                    $user->setPhone($value);
2739
                    break;
2740
                case 'address':
2741
                    $user->setAddress($value);
2742
                    break;
2743
                case 'roles':
2744
                    $user->setRoles($value);
2745
                    break;
2746
                case 'profile_completed':
2747
                    $user->setProfileCompleted($value);
2748
                    break;
2749
                case 'auth_source':
2750
                    $user->setAuthSource($value);
2751
                    break;
2752
                case 'status':
2753
                    $user->setStatus($value);
2754
                    break;
2755
                case 'official_code':
2756
                    $user->setOfficialCode($value);
2757
                    break;
2758
                case 'picture_uri':
2759
                    $user->setPictureUri($value);
2760
                    break;
2761
                case 'creator_id':
2762
                    $user->setCreatorId($value);
2763
                    break;
2764
                case 'competences':
2765
                    $user->setCompetences($value);
2766
                    break;
2767
                case 'diplomas':
2768
                    $user->setDiplomas($value);
2769
                    break;
2770
                case 'openarea':
2771
                    $user->setOpenArea($value);
2772
                    break;
2773
                case 'teach':
2774
                    $user->setTeach($value);
2775
                    break;
2776
                case 'productions':
2777
                    $user->setProductions($value);
2778
                    break;
2779
                case 'language':
2780
                    $languages = api_get_languages();
2781
                    if (!in_array($value, $languages['folder'])) {
2782
                        throw new Exception(get_lang('LanguageUnavailable'));
2783
                    }
2784
                    $user->setLanguage($value);
2785
                    break;
2786
                case 'registration_date':
2787
                    $user->setRegistrationDate($value);
2788
                    break;
2789
                case 'expiration_date':
2790
                    $user->setExpirationDate(
2791
                        new DateTime(
2792
                            api_get_utc_datetime($value),
2793
                            new DateTimeZone('UTC')
2794
                        )
2795
                    );
2796
                    break;
2797
                case 'active':
2798
                    // see UserManager::update_user() usermanager.lib.php:1205
2799
                    if ($user->getActive() != $value) {
2800
                        $user->setActive($value);
2801
                        Event::addEvent($value ? LOG_USER_ENABLE : LOG_USER_DISABLE, LOG_USER_ID, $userId);
2802
                    }
2803
                    break;
2804
                case 'openid':
2805
                    $user->setOpenId($value);
2806
                    break;
2807
                case 'theme':
2808
                    $user->setTheme($value);
2809
                    break;
2810
                case 'hr_dept_id':
2811
                    $user->setHrDeptId($value);
2812
                    break;
2813
                case 'extra':
2814
                    if (is_array($value)) {
2815
                        if (count($value) > 0) {
2816
                            if (is_array($value[0])) {
2817
                                foreach ($value as $field) {
2818
                                    $fieldName = $field['field_name'];
2819
                                    $fieldValue = $field['field_value'];
2820
                                    if (!isset($fieldName) || !isset($fieldValue) ||
2821
                                        !UserManager::update_extra_field_value($userId, $fieldName, $fieldValue)) {
2822
                                        throw new Exception(get_lang('CouldNotUpdateExtraFieldValue').': '.print_r($field, true));
2823
                                    }
2824
                                }
2825
                            } else {
2826
                                foreach ($value as $fieldName => $fieldValue) {
2827
                                    if (!UserManager::update_extra_field_value($userId, $fieldName, $fieldValue)) {
2828
                                        throw new Exception(get_lang('CouldNotUpdateExtraFieldValue').': '.$fieldName);
2829
                                    }
2830
                                }
2831
                            }
2832
                        }
2833
                    }
2834
                    break;
2835
                case 'username':
2836
                case 'api_key':
2837
                case 'action':
2838
                case 'loginname':
2839
                    break;
2840
                case 'email_canonical':
2841
                case 'locked':
2842
                case 'expired':
2843
                case 'credentials_expired':
2844
                case 'credentials_expire_at':
2845
                case 'expires_at':
2846
                case 'salt':
2847
                case 'last_login':
2848
                case 'created_at':
2849
                case 'updated_at':
2850
                case 'confirmation_token':
2851
                case 'password_requested_at':
2852
                case 'password': // see UserManager::update_user usermanager.lib.php:1182
2853
                case 'username_canonical':
2854
                default:
2855
                    throw new Exception(get_lang('UnsupportedUpdate')." '$name'");
2856
            }
2857
        }
2858
2859
        // save modifications
2860
        UserManager::getManager()->updateUser($user, true);
2861
2862
        // tell the world we just updated this user
2863
        if (!empty($hook)) {
2864
            $hook->setEventData(['user' => $user]);
2865
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_POST);
2866
        }
2867
2868
        // invalidate cache for this user
2869
        $cacheAvailable = api_get_configuration_value('apc');
2870
        if ($cacheAvailable === true) {
2871
            $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$userId;
2872
            if (apcu_exists($apcVar)) {
2873
                apcu_delete($apcVar);
2874
            }
2875
        }
2876
2877
        return true;
2878
    }
2879
2880
    /**
2881
     * Returns whether a user login name exists.
2882
     *
2883
     * @param string $loginname the user login name
2884
     *
2885
     * @return bool whether the user login name exists
2886
     */
2887
    public function usernameExist($loginname)
2888
    {
2889
        return false !== api_get_user_info_from_username($loginname);
2890
    }
2891
2892
    /**
2893
     * Returns whether a user group name exists.
2894
     *
2895
     * @param string $name the group name
2896
     *
2897
     * @return bool whether the group name exists
2898
     */
2899
    public function groupExists($name)
2900
    {
2901
        $userGroup = new UserGroup();
2902
2903
        return false !== $userGroup->usergroup_exists($name);
2904
    }
2905
2906
    /**
2907
     * This service roughly matches what the call to MDL's API core_course_get_contents function returns.
2908
     *
2909
     * @return array
2910
     */
2911
    public function getCourseQuizMdlCompat()
2912
    {
2913
        $userId = $this->user->getId();
2914
        $courseId = $this->course->getId();
2915
        $sessionId = $this->session ? $this->session->getId() : 0;
2916
2917
        $toolVisibility = CourseHome::getToolVisibility(TOOL_QUIZ, $courseId, $sessionId);
2918
2919
        $json = [
2920
            "id" => $this->course->getId(),
2921
            "name" => get_lang('Exercises'),
2922
            "visible" => (int) $toolVisibility,
2923
            "summary" => '',
2924
            "summaryformat" => 1,
2925
            "section" => 1,
2926
            "hiddenbynumsections" => 0,
2927
            "uservisible" => $toolVisibility,
2928
            "modules" => [],
2929
        ];
2930
2931
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_SMALL, false, true);
2932
2933
        $json['modules'] = array_map(
2934
            function (array $exercise) use ($quizIcon) {
2935
                return [
2936
                    'id' => (int) $exercise['id'],
2937
                    'url' => $exercise['url'],
2938
                    'name' => $exercise['name'],
2939
                    'instance' => 1,
2940
                    'visible' => 1,
2941
                    'uservisible' => true,
2942
                    'visibleoncoursepage' => 0,
2943
                    'modicon' => $quizIcon,
2944
                    'modname' => 'quiz',
2945
                    'modplural' => get_lang('Exercises'),
2946
                    'availability' => null,
2947
                    'indent' => 0,
2948
                    'onclick' => '',
2949
                    'afterlink' => null,
2950
                    'customdata' => "",
2951
                    'noviewlink' => false,
2952
                    'completion' => (int) ($exercise[1] > 0),
2953
                ];
2954
            },
2955
            Exercise::exerciseGrid(0, '', $userId, $courseId, $sessionId, true)
2956
        );
2957
2958
        return [$json];
2959
    }
2960
2961
    /**
2962
     * @throws Exception
2963
     */
2964
    public function updateSession(array $params): array
2965
    {
2966
        self::protectAdminEndpoint();
2967
2968
        $id = $params['session_id'];
2969
        $reset = $params['reset'] ?? null;
2970
        $name = $params['name'] ?? null;
2971
        $coachId = isset($params['id_coach']) ? (int) $params['id_coach'] : null;
2972
        $sessionCategoryId = isset($params['session_category_id']) ? (int) $params['session_category_id'] : null;
2973
        $description = $params['description'] ?? null;
2974
        $showDescription = $params['show_description'] ?? null;
2975
        $duration = $params['duration'] ?? null;
2976
        $visibility = $params['visibility'] ?? null;
2977
        $promotionId = $params['promotion_id'] ?? null;
2978
        $displayStartDate = $params['display_start_date'] ?? null;
2979
        $displayEndDate = $params['display_end_date'] ?? null;
2980
        $accessStartDate = $params['access_start_date'] ?? null;
2981
        $accessEndDate = $params['access_end_date'] ?? null;
2982
        $coachStartDate = $params['coach_access_start_date'] ?? null;
2983
        $coachEndDate = $params['coach_access_end_date'] ?? null;
2984
        $sendSubscriptionNotification = $params['send_subscription_notification'] ?? null;
2985
        $extraFields = $params['extra'] ?? [];
2986
2987
        $reset = (bool) $reset;
2988
        $visibility = (int) $visibility;
2989
        $tblSession = Database::get_main_table(TABLE_MAIN_SESSION);
2990
2991
        if (!SessionManager::isValidId($id)) {
2992
            throw new Exception(get_lang('NoData'));
2993
        }
2994
2995
        if (!empty($accessStartDate) && !api_is_valid_date($accessStartDate, 'Y-m-d H:i') &&
2996
            !api_is_valid_date($accessStartDate, 'Y-m-d H:i:s')
2997
        ) {
2998
            throw new Exception(get_lang('InvalidDate'));
2999
        }
3000
3001
        if (!empty($accessEndDate) && !api_is_valid_date($accessEndDate, 'Y-m-d H:i') &&
3002
            !api_is_valid_date($accessEndDate, 'Y-m-d H:i:s')
3003
        ) {
3004
            throw new Exception(get_lang('InvalidDate'));
3005
        }
3006
3007
        if (!empty($accessStartDate) && !empty($accessEndDate) && $accessStartDate >= $accessEndDate) {
3008
            throw new Exception(get_lang('InvalidDate'));
3009
        }
3010
3011
        $values = [];
3012
3013
        if ($reset) {
3014
            $values['name'] = $name;
3015
            $values['id_coach'] = $coachId;
3016
            $values['session_category_id'] = $sessionCategoryId;
3017
            $values['description'] = $description;
3018
            $values['show_description'] = $showDescription;
3019
            $values['duration'] = $duration;
3020
            $values['visibility'] = $visibility;
3021
            $values['promotion_id'] = $promotionId;
3022
            $values['display_start_date'] = !empty($displayStartDate) ? api_get_utc_datetime($displayStartDate) : null;
3023
            $values['display_end_date'] = !empty($displayEndDate) ? api_get_utc_datetime($displayEndDate) : null;
3024
            $values['access_start_date'] = !empty($accessStartDate) ? api_get_utc_datetime($accessStartDate) : null;
3025
            $values['access_end_date'] = !empty($accessEndDate) ? api_get_utc_datetime($accessEndDate) : null;
3026
            $values['coach_access_start_date'] = !empty($coachStartDate) ? api_get_utc_datetime($coachStartDate) : null;
3027
            $values['coach_access_end_date'] = !empty($coachEndDate) ? api_get_utc_datetime($coachEndDate) : null;
3028
            $values['send_subscription_notification'] = $sendSubscriptionNotification;
3029
        } else {
3030
            if (!empty($name)) {
3031
                $values['name'] = $name;
3032
            }
3033
3034
            if (!empty($coachId)) {
3035
                $values['id_coach'] = $coachId;
3036
            }
3037
3038
            if (!empty($sessionCategoryId)) {
3039
                $values['session_category_id'] = $sessionCategoryId;
3040
            }
3041
3042
            if (!empty($description)) {
3043
                $values['description'] = $description;
3044
            }
3045
3046
            if (!empty($showDescription)) {
3047
                $values['show_description'] = $showDescription;
3048
            }
3049
3050
            if (!empty($duration)) {
3051
                $values['duration'] = $duration;
3052
            }
3053
3054
            if (!empty($visibility)) {
3055
                $values['visibility'] = $visibility;
3056
            }
3057
3058
            if (!empty($promotionId)) {
3059
                $values['promotion_id'] = $promotionId;
3060
            }
3061
3062
            if (!empty($displayStartDate)) {
3063
                $values['display_start_date'] = api_get_utc_datetime($displayStartDate);
3064
            }
3065
3066
            if (!empty($displayEndDate)) {
3067
                $values['display_end_date'] = api_get_utc_datetime($displayEndDate);
3068
            }
3069
3070
            if (!empty($accessStartDate)) {
3071
                $values['access_start_date'] = api_get_utc_datetime($accessStartDate);
3072
            }
3073
3074
            if (!empty($accessEndDate)) {
3075
                $values['access_end_date'] = api_get_utc_datetime($accessEndDate);
3076
            }
3077
3078
            if (!empty($coachStartDate)) {
3079
                $values['coach_access_start_date'] = api_get_utc_datetime($coachStartDate);
3080
            }
3081
3082
            if (!empty($coachEndDate)) {
3083
                $values['coach_access_end_date'] = api_get_utc_datetime($coachEndDate);
3084
            }
3085
3086
            if (!empty($sendSubscriptionNotification)) {
3087
                $values['send_subscription_notification'] = $sendSubscriptionNotification;
3088
            }
3089
        }
3090
3091
        Database::update(
3092
            $tblSession,
3093
            $values,
3094
            ['id = ?' => $id]
3095
        );
3096
3097
        if (!empty($extraFields)) {
3098
            $extraFields['item_id'] = $id;
3099
            $sessionFieldValue = new ExtraFieldValue('session');
3100
            $sessionFieldValue->saveFieldValues($extraFields);
3101
        }
3102
3103
        return [
3104
            'status' => true,
3105
            'message' => get_lang('Updated'),
3106
            'id_session' => $id,
3107
        ];
3108
    }
3109
3110
    public function checkConditionalLogin(): bool
3111
    {
3112
        $file = api_get_path(SYS_CODE_PATH).'auth/conditional_login/conditional_login.php';
3113
3114
        if (!file_exists($file)) {
3115
            return true;
3116
        }
3117
3118
        include_once $file;
3119
3120
        if (!isset($login_conditions)) {
3121
            return true;
3122
        }
3123
3124
        foreach ($login_conditions as $condition) {
3125
            //If condition fails we redirect to the URL defined by the condition
3126
            if (!isset($condition['conditional_function'])) {
3127
                continue;
3128
            }
3129
3130
            $function = $condition['conditional_function'];
3131
            $result = $function(['user_id' => $this->user->getId()]);
3132
3133
            if ($result == false) {
3134
                return false;
3135
            }
3136
        }
3137
3138
        return true;
3139
    }
3140
3141
    public function getLegalConditions(): array
3142
    {
3143
        $language = api_get_language_id(
3144
            api_get_interface_language()
3145
        );
3146
3147
        $termPreview = LegalManager::get_last_condition($language);
3148
3149
        if ($termPreview) {
3150
            return $termPreview;
3151
        }
3152
3153
        $language = api_get_language_id(
3154
            api_get_setting('platformLanguage')
3155
        );
3156
3157
        $termPreview = LegalManager::get_last_condition($language);
3158
3159
        if ($termPreview) {
3160
            return $termPreview;
3161
        }
3162
3163
        $language = api_get_language_id('english');
3164
3165
        return LegalManager::get_last_condition($language);
3166
    }
3167
3168
    public function updateConditionAccepted()
3169
    {
3170
        $legalAcceptType = $_POST['legal_accept_type'] ?? null;
3171
3172
        $condArray = explode(':', $legalAcceptType);
3173
        $condArray = array_map('intval', $condArray);
3174
3175
        if (empty($condArray[0]) || empty($condArray[1])) {
3176
            return;
3177
        }
3178
3179
        $conditionToSave = intval($condArray[0]).':'.intval($condArray[1]).':'.time();
3180
3181
        LegalManager::sendEmailToUserBoss(
3182
            $this->user->getId(),
3183
            $conditionToSave
3184
        );
3185
    }
3186
3187
    /**
3188
     * Get the list of test with last user attempt and his datetime.
3189
     *
3190
     * @param array $fields A list of extra fields to include in the answer. Searches for the field in exercise, then in course
3191
     *
3192
     * @throws Exception
3193
     */
3194
    public function getTestUpdatesList($fields = []): array
3195
    {
3196
        self::protectAdminEndpoint();
3197
3198
        $tableCQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
3199
        $tableTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3200
        $tableUser = Database::get_main_table(TABLE_MAIN_USER);
3201
        $resultArray = [];
3202
3203
        // Check the extra fields criteria (whether to add extra field information or not)
3204
        $fieldSource = [];
3205
        $extraFieldValuesTable = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
3206
        $fieldsSearchString = "SELECT field_id, value FROM $extraFieldValuesTable WHERE item_id = %d AND field_id = %d";
3207
        if (count($fields) > 0) {
3208
            // For each field, check where to get it from (quiz or course)
3209
            $quizExtraField = new ExtraField('exercise');
3210
            $courseExtraField = new ExtraField('course');
3211
            foreach ($fields as $fieldName) {
3212
                $fieldExists = $quizExtraField->get_handler_field_info_by_field_variable($fieldName);
3213
                if ($fieldExists === false) {
3214
                    // The field does not exist on the exercise, so use it from the course
3215
                    $courseFieldExists = $courseExtraField->get_handler_field_info_by_field_variable($fieldName);
3216
                    if ($courseFieldExists === false) {
3217
                        continue;
3218
                    }
3219
                    $fieldSource[$fieldName] = ['item_type' => 'course', 'id' => $courseFieldExists['id']];
3220
                } else {
3221
                    $fieldSource[$fieldName] = ['item_type' => 'exercise', 'id' => $fieldExists['id']];
3222
                }
3223
            }
3224
        }
3225
3226
        $sql = "
3227
            SELECT q.iid AS id,
3228
                q.title,
3229
                MAX(a.start_date) AS last_attempt_time,
3230
                u.username AS last_attempt_username,
3231
                q.c_id
3232
            FROM $tableCQuiz q
3233
            JOIN $tableTrackExercises a ON q.iid = a.exe_exo_id
3234
            JOIN $tableUser u ON a.exe_user_id = u.id
3235
            GROUP BY q.iid
3236
        ";
3237
3238
        $result = Database::query($sql);
3239
        if (Database::num_rows($result) > 0) {
3240
            while ($row = Database::fetch_assoc($result)) {
3241
                // Check the whole extra fields thing
3242
                if (count($fieldSource) > 0) {
3243
                    foreach ($fieldSource as $fieldName => $fieldDetails) {
3244
                        if ($fieldDetails['item_type'] == 'course') {
3245
                            $itemId = $row['c_id'];
3246
                        } else {
3247
                            $itemId = $row['id'];
3248
                        }
3249
                        $fieldResult = Database::query(sprintf($fieldsSearchString, $itemId, $fieldDetails['id']));
3250
                        if (Database::num_rows($fieldResult) > 0) {
3251
                            $fieldRow = Database::fetch_assoc($fieldResult);
3252
                            $row['extra_'.$fieldName] = $fieldRow['value'];
3253
                        } else {
3254
                            $row['extra_'.$fieldName] = '';
3255
                        }
3256
                    }
3257
                }
3258
                // Get item authoring data
3259
                $itemProps = api_get_last_item_property_info($row['c_id'], 'quiz', $row['id']);
3260
                $row['created_by'] = $this->__getConfiguredUsernameById($itemProps['insert_user_id']);
3261
                if ($itemProps['insert_user_id'] == $itemProps['lastedit_user_id']) {
3262
                    $row['updated_by'] = $row['created_by'];
3263
                } else {
3264
                    $row['updated_by'] = $this->__getConfiguredUsernameById($itemProps['lastedit_user_id']);
3265
                }
3266
                $resultArray[] = $row;
3267
            }
3268
        }
3269
3270
        return $resultArray;
3271
    }
3272
3273
    /**
3274
     * Get tests results data
3275
     * Not support sessions
3276
     * By default, is successful if score greater than 50%.
3277
     *
3278
     * @throws Exception
3279
     *
3280
     * @return array e.g: [ { "id": 4, "title": "aiken", "updated_by": "-", "type": "1", "completion": 0 } ]
3281
     */
3282
    public function getTestAverageResultsList(array $ids = [], ?array $fields = []): array
3283
    {
3284
        self::protectAdminEndpoint();
3285
        $tableTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3286
        $tableCQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
3287
        $tableCourseRelUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3288
3289
        $resultArray = [];
3290
        $countUsersInCourses = [];
3291
        $extraArray = [];
3292
3293
        if (!empty($ids)) {
3294
            if (!is_array($ids)) {
3295
                $ids = [$ids];
3296
            }
3297
            if (!is_array($fields)) {
3298
                $fields = [$fields];
3299
            }
3300
            if (!empty($fields)) {
3301
                foreach ($fields as $field) {
3302
                    $extraArray['extra_'.$field] = '';
3303
                }
3304
            }
3305
3306
            $queryUsersInCourses = "
3307
                SELECT c_id, count(*)
3308
                FROM $tableCourseRelUser
3309
                GROUP BY c_id
3310
                ORDER BY c_id;
3311
            ";
3312
3313
            $resultUsersInCourses = Database::query($queryUsersInCourses);
3314
            while ($row = Database::fetch_array($resultUsersInCourses)) {
3315
                $countUsersInCourses[$row[0]] = $row[1];
3316
            }
3317
3318
            foreach ($ids as $item) {
3319
                $item = (int) $item;
3320
3321
                $queryCQuiz = "
3322
                    SELECT c_id,
3323
                        title,
3324
                        feedback_type,
3325
                        pass_percentage
3326
                    FROM $tableCQuiz
3327
                    WHERE iid = $item";
3328
3329
                $resultCQuiz = Database::query($queryCQuiz);
3330
                if (Database::num_rows($resultCQuiz) <= 0) {
3331
                    continue;
3332
                }
3333
                $row = Database::fetch_assoc($resultCQuiz);
3334
3335
                $cId = $row['c_id'];
3336
                $title = $row['title'];
3337
                $type = Exercise::getFeedbackTypeLiteral($row['feedback_type']);
3338
                $passPercentage = empty($row['pass_percentage']) ? 0.5 : $row['pass_percentage'];
3339
3340
                // Get item authoring data
3341
                $itemProps = api_get_last_item_property_info($row['c_id'], 'quiz', $item);
3342
                $createdBy = $this->__getConfiguredUsernameById($itemProps['insert_user_id']);
3343
                if ($itemProps['insert_user_id'] == $itemProps['lastedit_user_id']) {
3344
                    $updatedBy = $createdBy;
3345
                } else {
3346
                    $updatedBy = $this->__getConfiguredUsernameById($itemProps['lastedit_user_id']);
3347
                }
3348
3349
                $sql = "
3350
                    SELECT a.exe_exo_id AS id,
3351
                           a.exe_user_id,
3352
                           MAX(a.start_date),
3353
                           a.exe_result,
3354
                           a.exe_weighting
3355
                    FROM $tableTrackExercises a
3356
                    WHERE a.exe_exo_id = $item
3357
                    GROUP BY a.exe_exo_id, a.exe_user_id
3358
                ";
3359
3360
                $result = Database::query($sql);
3361
                if (Database::num_rows($result) > 0) {
3362
                    $countAttempts = 0;
3363
                    $countSuccess = 0;
3364
                    $scoreSum = 0;
3365
3366
                    while ($row = Database::fetch_assoc($result)) {
3367
3368
                        // If test is badly configured, with all questions at score 0
3369
                        if ($row['exe_weighting'] == 0) {
3370
                            continue;
3371
                        }
3372
                        $score = $row['exe_result'] / $row['exe_weighting'];
3373
                        if ($score >= $passPercentage) {
3374
                            $countSuccess++;
3375
                        }
3376
                        $scoreSum += $score;
3377
                        $countAttempts++;
3378
                    }
3379
                    $completionMethod = 'Success on users count';
3380
                    if ($countAttempts === 0) {
3381
                        // In some cases, there are no attempts at all. Return 0 completion & score.
3382
                        $averageScore = 0;
3383
                        $completion = 0;
3384
                    } else {
3385
                        $averageScore = round(($scoreSum / $countAttempts) * 100, 2);
3386
                        if (empty($countUsersInCourses[$cId])) {
3387
                            // Users might have all been unsubscribed from the course since taking the test
3388
                            $completion = $countSuccess / $countAttempts;
3389
                            $completionMethod = 'Success on attempts count';
3390
                        } else {
3391
                            $completion = $countSuccess / $countUsersInCourses[$cId];
3392
                        }
3393
                    }
3394
                    $params = [
3395
                        'id' => $item,
3396
                        'title' => $title,
3397
                        'created_by' => $createdBy,
3398
                        'updated_by' => $updatedBy,
3399
                        'type' => $type,
3400
                        'completion' => $completion,
3401
                        'completion_method' => $completionMethod,
3402
                        'number_of_last_attempts' => $countAttempts,
3403
                        'average_score_in_percent' => $averageScore,
3404
                    ];
3405
                    foreach ($extraArray as $name => $value) {
3406
                        $params[$name] = $value;
3407
                    }
3408
                    $resultArray[] = $params;
3409
                }
3410
            }
3411
        }
3412
3413
        return $resultArray;
3414
    }
3415
3416
    public function logout()
3417
    {
3418
        online_logout($this->user->getId());
3419
3420
        Event::courseLogout(
3421
            [
3422
                'uid' => $this->user->getId(),
3423
                'cid' => $this->course ? $this->course->getId() : 0,
3424
                'sid' => $this->session ? $this->session->getId() : 0,
3425
            ]
3426
        );
3427
    }
3428
3429
    /**
3430
     * @throws Exception
3431
     */
3432
    public function setThreadNotify(int $threadId): string
3433
    {
3434
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
3435
3436
        $result = set_notification(
3437
            'thread',
3438
            $threadId,
3439
            false,
3440
            api_get_user_info($this->user->getId()),
3441
            api_get_course_info($this->course->getCode())
3442
        );
3443
3444
        if (false === $result) {
3445
            self::throwNotAllowedException();
3446
        }
3447
3448
        return $result;
3449
    }
3450
3451
    public function getCourseWorks(): array
3452
    {
3453
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3454
3455
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3456
3457
        $isAllowedToEdit = $this->user->getStatus() !== STUDENT;
3458
3459
        $courseId = $this->course->getId();
3460
        $sessionId = $this->session ? $this->session->getId() : 0;
3461
3462
        $courseInfo = api_get_course_info_by_id($this->course->getId());
3463
3464
        $works = array_filter(
3465
            getWorkListTeacherData($courseId, $sessionId, 0, 0, 0, 'title', 'ASC', ''),
3466
            function (array $work) use ($isAllowedToEdit, $courseInfo, $courseId, $sessionId) {
3467
                if (!$isAllowedToEdit
3468
                    && !userIsSubscribedToWork($this->user->getId(), $work['id'], $courseId)
3469
                ) {
3470
                    return false;
3471
                }
3472
3473
                $visibility = api_get_item_visibility($courseInfo, 'work', $work['id'], $sessionId);
3474
3475
                if (!$isAllowedToEdit && $visibility != 1) {
3476
                    return false;
3477
                }
3478
3479
                return true;
3480
            }
3481
        );
3482
3483
        return array_map(
3484
            function (array $work) use ($isAllowedToEdit, $courseInfo) {
3485
                $work['type'] = 'work.png';
3486
3487
                if (!$isAllowedToEdit) {
3488
                    $workList = get_work_user_list(
3489
                        0,
3490
                        1000,
3491
                        null,
3492
                        null,
3493
                        $work['id'],
3494
                        ' AND u.id = '.$this->user->getId()
3495
                    );
3496
3497
                    $count = getTotalWorkComment($workList, $courseInfo);
3498
                    $lastWork = getLastWorkStudentFromParentByUser($this->user->getId(), $work, $courseInfo);
3499
3500
                    $work['feedback'] = ' '.Display::label('0 '.get_lang('Feedback'), 'warning');
3501
3502
                    if (!empty($count)) {
3503
                        $work['feedback'] = ' '.Display::label($count.' '.get_lang('Feedback'), 'info');
3504
                    }
3505
3506
                    $work['last_upload'] = '';
3507
3508
                    if (!empty($lastWork)) {
3509
                        $work['last_upload'] = !empty($lastWork['qualification'])
3510
                            ? $lastWork['qualification_rounded'].' - '
3511
                            : '';
3512
                        $work['last_upload'] .= api_get_local_time($lastWork['sent_date']);
3513
                    }
3514
                }
3515
3516
                return $work;
3517
            },
3518
            $works
3519
        );
3520
    }
3521
3522
    /**
3523
     * Returns a list of exercises in the given course. The given course is received through generic param at instanciation.
3524
     *
3525
     * @param array $fields A list of extra fields to include in the answer. Searches for the field in exercise, then in course
3526
     */
3527
    public function getCourseExercises($fields = []): array
3528
    {
3529
        Event::event_access_tool(TOOL_QUIZ);
3530
3531
        $sessionId = $this->session ? $this->session->getId() : 0;
3532
        $courseInfo = api_get_course_info_by_id($this->course->getId());
3533
3534
        // Check the extra fields criteria (whether to add extra field information or not)
3535
        $fieldSource = [];
3536
        if (count($fields) > 0) {
3537
            // For each field, check where to get it from (quiz or course)
3538
            $quizExtraField = new ExtraField('exercise');
3539
            $courseExtraField = new ExtraField('course');
3540
            foreach ($fields as $fieldName) {
3541
                $fieldExists = $quizExtraField->get_handler_field_info_by_field_variable($fieldName);
3542
                if ($fieldExists === false) {
3543
                    // The field does not exist on the exercise, so use it from the course
3544
                    $courseFieldExists = $courseExtraField->get_handler_field_info_by_field_variable($fieldName);
3545
                    if ($courseFieldExists === false) {
3546
                        continue;
3547
                    }
3548
                    $fieldSource[$fieldName] = ['item_type' => 'course', 'id' => $courseFieldExists['id']];
3549
                } else {
3550
                    $fieldSource[$fieldName] = ['item_type' => 'exercise', 'id' => $fieldExists['id']];
3551
                }
3552
            }
3553
        }
3554
        $list = ExerciseLib::get_all_exercises($courseInfo, $sessionId);
3555
3556
        // Now check the whole extra fields thing
3557
        if (count($fieldSource) > 0) {
3558
            $extraFieldValuesTable = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
3559
            $fieldsSearchString = "SELECT field_id, value FROM $extraFieldValuesTable WHERE item_id = %d AND field_id = %d";
3560
            foreach ($list as $id => $exercise) {
3561
                foreach ($fieldSource as $fieldName => $fieldDetails) {
3562
                    if ($fieldDetails['item_type'] == 'course') {
3563
                        $itemId = $exercise['c_id'];
3564
                    } else {
3565
                        $itemId = $exercise['iid'];
3566
                    }
3567
                    $result = Database::query(sprintf($fieldsSearchString, $itemId, $fieldDetails['id']));
3568
                    if (Database::num_rows($result) > 0) {
3569
                        $row = Database::fetch_assoc($result);
3570
                        $list[$id]['extra_'.$fieldName] = $row['value'];
3571
                    } else {
3572
                        $list[$id]['extra_'.$fieldName] = '';
3573
                    }
3574
                }
3575
            }
3576
        }
3577
        foreach ($list as $id => $row) {
3578
            // Get item authoring data
3579
            $itemProps = api_get_last_item_property_info($row['c_id'], 'quiz', $row['iid']);
3580
            $createdBy = $this->__getConfiguredUsernameById($itemProps['insert_user_id']);
3581
            if ($itemProps['insert_user_id'] == $itemProps['lastedit_user_id']) {
3582
                $updatedBy = $createdBy;
3583
            } else {
3584
                $updatedBy = $this->__getConfiguredUsernameById($itemProps['lastedit_user_id']);
3585
            }
3586
            $list[$id]['created_by'] = $createdBy;
3587
            $list[$id]['updated_by'] = $updatedBy;
3588
        }
3589
3590
        return $list;
3591
    }
3592
3593
    /**
3594
     * @throws Exception
3595
     */
3596
    public function putCourseWorkVisibility(int $workId, int $status): bool
3597
    {
3598
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3599
3600
        $courseInfo = api_get_course_info_by_id($this->course->getId());
3601
3602
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3603
3604
        switch ($status) {
3605
            case 1:
3606
                return makeVisible($workId, $courseInfo);
3607
            case 0:
3608
                return makeInvisible($workId, $courseInfo);
3609
            default:
3610
                throw new Exception(get_lang('ActionNotAllowed'));
3611
        }
3612
    }
3613
3614
    public function deleteWorkStudentItem(int $workId): string
3615
    {
3616
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3617
3618
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3619
3620
        $courseInfo = api_get_course_info_by_id($this->course->getId());
3621
3622
        $fileDeleted = deleteWorkItem($workId, $courseInfo);
3623
3624
        if ($fileDeleted) {
3625
            return get_lang('TheDocumentHasBeenDeleted');
3626
        }
3627
3628
        return get_lang('YouAreNotAllowedToDeleteThisDocument');
3629
    }
3630
3631
    public function deleteWorkCorrections(int $workId): string
3632
    {
3633
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3634
3635
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3636
3637
        $courseInfo = api_get_course_info_by_id($this->course->getId());
3638
3639
        $result = get_work_user_list(null, null, null, null, $workId);
3640
3641
        if ($result) {
3642
            foreach ($result as $item) {
3643
                $workInfo = get_work_data_by_id($item['id']);
3644
3645
                deleteCorrection($courseInfo, $workInfo);
3646
            }
3647
        }
3648
3649
        return get_lang('Deleted');
3650
    }
3651
3652
    public function getWorkList(int $workId): array
3653
    {
3654
        $isAllowedToEdit = api_is_allowed_to_edit();
3655
3656
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3657
3658
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3659
3660
        $userId = $this->user->getId();
3661
        $courseId = $this->course->getId();
3662
        $sessionId = $this->session ? $this->session->getId() : 0;
3663
3664
        $courseInfo = api_get_course_info_by_id($courseId);
3665
        $webPath = api_get_path(WEB_PATH);
3666
3667
        $whereCondition = !$isAllowedToEdit ? " AND u.id = $userId" : '';
3668
3669
        $works = get_work_user_list(
3670
            0,
3671
            0,
3672
            'title',
3673
            'asc',
3674
            $workId,
3675
            $whereCondition,
3676
            null,
3677
            false,
3678
            $courseId,
3679
            $sessionId
3680
        );
3681
3682
        return array_map(
3683
            function (array $work) use ($courseInfo, $webPath) {
3684
                $itemId = $work['id'];
3685
                $count = getWorkCommentCount($itemId, $courseInfo);
3686
3687
                $work['feedback'] = $count.' '.Display::returnFontAwesomeIcon('comments-o');
3688
                $work['feedback_clean'] = $count;
3689
3690
                $workInfo = get_work_data_by_id($itemId);
3691
                $commentsTmp = getWorkComments($workInfo);
3692
                $comments = [];
3693
3694
                foreach ($commentsTmp as $comment) {
3695
                    $comment['comment'] = str_replace('src="/', 'src="'.$webPath.'app/', $comment['comment']);
3696
                    $comments[] = $comment;
3697
                }
3698
3699
                $work['comments'] = $comments;
3700
3701
                if (empty($workInfo['qualificator_id'])) {
3702
                    $qualificator_id = Display::label(get_lang('NotRevised'), 'warning');
3703
                } else {
3704
                    $qualificator_id = Display::label(get_lang('Revised'), 'success');
3705
                }
3706
3707
                $work['qualificator_id'] = $qualificator_id;
3708
3709
                return $work;
3710
            },
3711
            $works
3712
        );
3713
    }
3714
3715
    public function getWorkStudentsWithoutPublications(int $workId): array
3716
    {
3717
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3718
3719
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3720
3721
        return get_list_users_without_publication($workId);
3722
    }
3723
3724
    public function getWorkUsers(int $workId): array
3725
    {
3726
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3727
3728
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3729
3730
        $courseId = $this->course->getId();
3731
        $sessionId = $this->session ? $this->session->getId() : 0;
3732
        $courseInfo = api_get_course_info_by_id($courseId);
3733
3734
        $items = getAllUserToWork($workId, $courseId);
3735
        $usersAdded = [];
3736
        $result = [
3737
            'users_added' => [],
3738
            'users_to_add' => [],
3739
        ];
3740
3741
        if (!empty($items)) {
3742
            foreach ($items as $data) {
3743
                $usersAdded[] = $data['user_id'];
3744
3745
                $userInfo = api_get_user_info($data['user_id']);
3746
3747
                $result['users_added'][] = [
3748
                    'user_id' => (int) $data['user_id'],
3749
                    'complete_name_with_username' => $userInfo['complete_name_with_username'],
3750
                ];
3751
            }
3752
        }
3753
3754
        if (empty($sessionId)) {
3755
            $status = STUDENT;
3756
        } else {
3757
            $status = 0;
3758
        }
3759
3760
        $userList = CourseManager::get_user_list_from_course_code(
3761
            $courseInfo['code'],
3762
            $sessionId,
3763
            null,
3764
            null,
3765
            $status
3766
        );
3767
3768
        $userToAddList = [];
3769
        foreach ($userList as $user) {
3770
            if (!in_array($user['user_id'], $usersAdded)) {
3771
                $userToAddList[] = $user;
3772
            }
3773
        }
3774
3775
        if (!empty($userToAddList)) {
3776
            foreach ($userToAddList as $user) {
3777
                $userName = api_get_person_name($user['firstname'], $user['lastname']).' ('.$user['username'].') ';
3778
3779
                $result['users_to_add'][] = [
3780
                    'user_id' => (int) $user['user_id'],
3781
                    'complete_name_with_username' => $userName,
3782
                ];
3783
            }
3784
        }
3785
3786
        return $result;
3787
    }
3788
3789
    public function getWorkStudentList(int $workId): array
3790
    {
3791
        Event::event_access_tool(TOOL_STUDENTPUBLICATION);
3792
3793
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
3794
3795
        $courseId = $this->course->getId();
3796
        $courseCode = $this->course->getCode();
3797
        $sessionId = $this->session ? $this->session->getId() : 0;
3798
3799
        $myFolderData = get_work_data_by_id($workId);
3800
3801
        $workParents = [];
3802
3803
        if (empty($myFolderData)) {
3804
            $workParents = getWorkList($workId, $myFolderData);
3805
        }
3806
3807
        $workIdList = [];
3808
3809
        if (!empty($workParents)) {
3810
            foreach ($workParents as $work) {
3811
                $workIdList[] = $work->id;
3812
            }
3813
        }
3814
3815
        $userList = getWorkUserList(
3816
            $courseCode,
3817
            $sessionId,
3818
            0,
3819
            0,
3820
            null,
3821
            null,
3822
            null
3823
        );
3824
3825
        return array_map(
3826
            function ($userId) use ($courseId, $sessionId, $workParents, $workIdList) {
3827
                $user = api_get_user_info($userId);
3828
3829
                $userWorks = 0;
3830
3831
                if (!empty($workIdList)) {
3832
                    $userWorks = getUniqueStudentAttempts(
3833
                        $workIdList,
3834
                        0,
3835
                        $courseId,
3836
                        $sessionId,
3837
                        $user['user_id']
3838
                    );
3839
                }
3840
3841
                $works = $userWorks." / ".count($workParents);
3842
3843
                return [
3844
                    'id' => $userId,
3845
                    'complete_name' => api_get_person_name($user['firstname'], $user['lastname']),
3846
                    'works' => $works,
3847
                ];
3848
            },
3849
            $userList
3850
        );
3851
    }
3852
3853
    public function viewUserProfile(int $userId)
3854
    {
3855
        $url = api_get_path(WEB_CODE_PATH).'social/profile.php';
3856
3857
        if ($userId) {
3858
            $url .= '?'.http_build_query(['u' => $userId]);
3859
        }
3860
3861
        header("Location: $url");
3862
        exit;
3863
    }
3864
3865
    public function viewCourseHome()
3866
    {
3867
        $url = api_get_course_url($this->course->getCode(), $this->session ? $this->session->getId() : 0);
3868
3869
        header("Location: $url");
3870
        exit;
3871
    }
3872
3873
    public function viewDocumentInFrame(int $documentId)
3874
    {
3875
        $courseCode = $this->course->getCode();
3876
        $sessionId = $this->session ? $this->session->getId() : 0;
3877
3878
        $url = api_get_path(WEB_CODE_PATH).'document/showinframes.php?'
3879
            .http_build_query(
3880
                [
3881
                    'cidReq' => $courseCode,
3882
                    'id_session' => $sessionId,
3883
                    'gidReq' => 0,
3884
                    'gradebook' => 0,
3885
                    'origin' => self::SERVICE_NAME,
3886
                    'id' => $documentId,
3887
                ]
3888
            );
3889
3890
        header("Location: $url");
3891
        exit;
3892
    }
3893
3894
    public function viewQuizTool()
3895
    {
3896
        $courseCode = $this->course->getCode();
3897
        $sessionId = $this->session ? $this->session->getId() : 0;
3898
3899
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'
3900
            .http_build_query(
3901
                [
3902
                    'cidReq' => $courseCode,
3903
                    'id_session' => $sessionId,
3904
                    'gidReq' => 0,
3905
                    'gradebook' => 0,
3906
                    'origin' => self::SERVICE_NAME,
3907
                ]
3908
            );
3909
3910
        header("Location: $url");
3911
        exit;
3912
    }
3913
3914
    public function viewSurveyTool()
3915
    {
3916
        $courseCode = $this->course->getCode();
3917
        $sessionId = $this->session ? $this->session->getId() : 0;
3918
3919
        $url = api_get_path(WEB_CODE_PATH).'survey/survey_list.php?'
3920
            .http_build_query(
3921
                [
3922
                    'cidReq' => $courseCode,
3923
                    'id_session' => $sessionId,
3924
                    'gidReq' => 0,
3925
                    'gradebook' => 0,
3926
                    'origin' => self::SERVICE_NAME,
3927
                ]
3928
            );
3929
3930
        header("Location: $url");
3931
        exit;
3932
    }
3933
3934
    public function viewMessage(int $messageId)
3935
    {
3936
        $url = api_get_path(WEB_CODE_PATH).'messages/view_message.php?'.http_build_query(['id' => $messageId]);
3937
3938
        header("Location: $url");
3939
        exit;
3940
    }
3941
3942
    public function downloadForumPostAttachment(string $path)
3943
    {
3944
        $courseCode = $this->course->getCode();
3945
        $sessionId = $this->session ? $this->session->getId() : 0;
3946
3947
        $url = api_get_path(WEB_CODE_PATH).'forum/download.php?'
3948
            .http_build_query(
3949
                [
3950
                    'cidReq' => $courseCode,
3951
                    'id_session' => $sessionId,
3952
                    'gidReq' => 0,
3953
                    'gradebook' => 0,
3954
                    'origin' => self::SERVICE_NAME,
3955
                    'file' => Security::remove_XSS($path),
3956
                ]
3957
            );
3958
3959
        header("Location: $url");
3960
        exit;
3961
    }
3962
3963
    public function downloadWorkFolder(int $workId)
3964
    {
3965
        $cidReq = api_get_cidreq();
3966
        $url = api_get_path(WEB_CODE_PATH)."work/downloadfolder.inc.php?id=$workId&$cidReq";
3967
3968
        header("Location: $url");
3969
        exit;
3970
    }
3971
3972
    public function downloadWorkCommentAttachment(int $commentId)
3973
    {
3974
        $cidReq = api_get_cidreq();
3975
        $url = api_get_path(WEB_CODE_PATH)."work/download_comment_file.php?comment_id=$commentId&$cidReq";
3976
3977
        header("Location: $url");
3978
        exit;
3979
    }
3980
3981
    public function downloadWork(int $workId, bool $isCorrection = false)
3982
    {
3983
        $cidReq = api_get_cidreq();
3984
        $url = api_get_path(WEB_CODE_PATH)."work/download.php?$cidReq&"
3985
            .http_build_query(
3986
                [
3987
                    'id' => $workId,
3988
                    'correction' => $isCorrection ? 1 : null,
3989
                ]
3990
            );
3991
3992
        header("Location: $url");
3993
        exit;
3994
    }
3995
3996
    /**
3997
     * @throws Exception
3998
     */
3999
    public function getAllUsersApiKeys(int $page, int $length, bool $force = false, ?int $urlId = null): array
4000
    {
4001
        if (false === api_get_configuration_value('webservice_enable_adminonly_api')
4002
            || !UserManager::is_admin($this->user->getId())
4003
        ) {
4004
            self::throwNotAllowedException();
4005
        }
4006
4007
        $limitOffset = ($page - 1) * $length;
4008
4009
        $currentUrlId = $urlId ?: api_get_current_access_url_id();
4010
4011
        $data = [];
4012
        $data['total'] = UserManager::get_number_of_users(0, $currentUrlId);
4013
        $data['list'] = array_map(
4014
            function (array $user) use ($force) {
4015
                $apiKeys = UserManager::get_api_keys($user['id'], self::SERVICE_NAME);
4016
                $apiKey = $apiKeys ? current($apiKeys) : null;
4017
4018
                if ($force && empty($apiKey)) {
4019
                    $apiKey = self::generateApiKeyForUser((int) $user['id']);
4020
                }
4021
4022
                return [
4023
                    'id' => (int) $user['id'],
4024
                    'username' => $user['username'],
4025
                    'api_key' => $apiKey,
4026
                ];
4027
            },
4028
            $users = UserManager::get_user_list([], [], $limitOffset, $length, $currentUrlId)
4029
        );
4030
        $data['length'] = count($users);
4031
4032
        if ($page * $length < $data['total']) {
4033
            $nextPageQueryParams = [
4034
                'page' => $page + 1,
4035
                'per_page' => $length,
4036
                'url_id' => $urlId,
4037
            ];
4038
4039
            $data['next'] = $this->generateUrl($nextPageQueryParams);
4040
        }
4041
4042
        return $data;
4043
    }
4044
4045
    /**
4046
     * @throws Exception
4047
     */
4048
    public function getUserApiKey(string $username, bool $force = false): array
4049
    {
4050
        if (false === api_get_configuration_value('webservice_enable_adminonly_api')
4051
            || !UserManager::is_admin($this->user->getId())
4052
        ) {
4053
            self::throwNotAllowedException();
4054
        }
4055
4056
        $userInfo = api_get_user_info_from_username($username);
4057
4058
        if (empty($userInfo)) {
4059
            throw new Exception(get_lang('UserNotFound'));
4060
        }
4061
4062
        $apiKeys = UserManager::get_api_keys($userInfo['id'], self::SERVICE_NAME);
4063
        $apiKey = $apiKeys ? current($apiKeys) : null;
4064
4065
        if ($force && empty($apiKey)) {
4066
            $apiKey = self::generateApiKeyForUser((int) $userInfo['id']);
4067
        }
4068
4069
        return [
4070
            'id' => $userInfo['id'],
4071
            'username' => $userInfo['username'],
4072
            'api_key' => $apiKey,
4073
        ];
4074
    }
4075
4076
    /**
4077
     * @throws Exception
4078
     */
4079
    public function getUserLastConnexion(string $username): array
4080
    {
4081
        $userInfo = api_get_user_info_from_username($username);
4082
4083
        if (empty($userInfo)) {
4084
            throw new Exception(get_lang('UserNotFound'));
4085
        }
4086
4087
        $lastConnexionDate = Tracking::get_last_connection_date($userInfo['id']);
4088
4089
        return [
4090
            'id' => $userInfo['id'],
4091
            'username' => $userInfo['username'],
4092
            'last_connexion_date' => $lastConnexionDate,
4093
        ];
4094
    }
4095
4096
    /**
4097
     * @throws Exception
4098
     */
4099
    public function getUserTotalConnexionTime(string $username): array
4100
    {
4101
        $userInfo = api_get_user_info_from_username($username);
4102
4103
        if (empty($userInfo)) {
4104
            throw new Exception(get_lang('UserNotFound'));
4105
        }
4106
4107
        $totalConnexionTimeInSecond = Tracking::get_time_spent_on_the_platform($userInfo['id'], 'ever');
4108
        $totalConnexionTime = api_time_to_hms($totalConnexionTimeInSecond);
4109
4110
        return [
4111
            'id' => $userInfo['id'],
4112
            'username' => $userInfo['username'],
4113
            'total_connexion_time' => $totalConnexionTime,
4114
        ];
4115
    }
4116
4117
    public static function isAllowedByRequest(bool $inpersonate = false): bool
4118
    {
4119
        $username = $_GET['username'] ?? null;
4120
        $apiKey = $_GET['api_key'] ?? null;
4121
4122
        if (empty($username) || empty($apiKey)) {
4123
            return false;
4124
        }
4125
4126
        try {
4127
            $restApi = self::validate($username, $apiKey);
4128
        } catch (Exception $e) {
4129
            return false;
4130
        }
4131
4132
        if ($inpersonate) {
4133
            Login::init_user($restApi->getUser()->getId(), true);
4134
        }
4135
4136
        return (bool) $restApi;
4137
    }
4138
4139
    public function viewMyCourses()
4140
    {
4141
        $url = api_get_path(WEB_PATH).'user_portal.php?'
4142
            .http_build_query(['nosession' => 'true']);
4143
4144
        header("Location: $url");
4145
        exit;
4146
    }
4147
4148
    /**
4149
     * Create a group/class.
4150
     *
4151
     * @param $params
4152
     *
4153
     * @throws Exception
4154
     */
4155
    public function addGroup($params): array
4156
    {
4157
        self::protectAdminEndpoint();
4158
4159
        if (!empty($params['type'])) {
4160
            $params['group_type'] = $params['type'];
4161
        }
4162
4163
        // First check wether the login already exists.
4164
        $userGroup = new UserGroup();
4165
        if ($userGroup->usergroup_exists($params['name'])) {
4166
            throw new Exception($params['name'].' '.get_lang('AlreadyExists'));
4167
        }
4168
4169
        $groupId = $userGroup->save($params);
4170
4171
        if (empty($groupId)) {
4172
            throw new Exception(get_lang('NotRegistered'));
4173
        }
4174
4175
        return [$groupId];
4176
    }
4177
4178
    /**
4179
     * Delete a group/class.
4180
     *
4181
     * @throws Exception
4182
     *
4183
     * @return bool
4184
     */
4185
    public function deleteGroup(int $id): array
4186
    {
4187
        self::protectAdminEndpoint();
4188
4189
        if (empty($id)) {
4190
            return false;
4191
        }
4192
4193
        // First check wether the login already exists.
4194
        $userGroup = new UserGroup();
4195
        if (!$userGroup->delete($id)) {
4196
            throw new Exception(get_lang('NotDeleted'));
4197
        }
4198
4199
        return [$id];
4200
    }
4201
4202
    /**
4203
     * Get the list of users subscribed to the given group/class.
4204
     *
4205
     * @return array The list of users (userID => [firstname, lastname, relation_type]
4206
     */
4207
    public function getGroupSubscribedUsers(int $groupId): array
4208
    {
4209
        $userGroup = new UserGroup();
4210
4211
        return $userGroup->get_all_users_by_group($groupId);
4212
    }
4213
4214
    /**
4215
     * Get the list of courses to which the given group/class is subscribed.
4216
     *
4217
     * @return array The list of courses (ID => [title]
4218
     */
4219
    public function getGroupSubscribedCourses(int $groupId): array
4220
    {
4221
        $userGroup = new UserGroup();
4222
4223
        return $userGroup->get_courses_by_usergroup($groupId, true);
4224
    }
4225
4226
    /**
4227
     * Get the list of sessions to which the given group/class is subscribed.
4228
     *
4229
     * @return array The list of courses (ID => [title]
4230
     */
4231
    public function getGroupSubscribedSessions(int $groupId): array
4232
    {
4233
        $userGroup = new UserGroup();
4234
4235
        return $userGroup->get_sessions_by_usergroup($groupId, true);
4236
    }
4237
4238
    /**
4239
     * Add a new user to the given group/class.
4240
     *
4241
     * @param int $relationType (1:admin, 2:reader, etc. See GROUP_USER_PERMISSION_ constants in api.lib.php)
4242
     *
4243
     * @return array One item array containing true on success, false otherwise
4244
     */
4245
    public function addGroupSubscribedUser(int $groupId, int $userId, int $relationType = 2): array
4246
    {
4247
        $userGroup = new UserGroup();
4248
4249
        if (!$userGroup->groupExists($groupId) or !$userGroup->userExists($userId)) {
4250
            throw new Exception('user_id or group_id does not exist');
4251
        }
4252
4253
        return [$userGroup->add_user_to_group($userId, $groupId, $relationType)];
4254
    }
4255
4256
    /**
4257
     * Get the list of group/class IDs to which the user belongs.
4258
     *
4259
     * @return array Array containing the group IDs like ['groups' => [1, 2, 3]]
4260
     */
4261
    public function getUserSubGroup(int $userId): array
4262
    {
4263
        $userGroup = new UserGroup();
4264
4265
        $res = $userGroup->get_usergroup_by_user($userId);
4266
4267
        return ['groups' => $res];
4268
    }
4269
4270
    /**
4271
     * Add a new course to which the given group/class is subscribed.
4272
     *
4273
     * @return array One item array containing the ID of the course on success, nothing on failure
4274
     */
4275
    public function addGroupSubscribedCourse(int $groupId, int $courseId): array
4276
    {
4277
        $userGroup = new UserGroup();
4278
4279
        return [$userGroup->subscribe_courses_to_usergroup($groupId, [$courseId], false)];
4280
    }
4281
4282
    /**
4283
     * Add a new session to which the given group/class is subscribed.
4284
     *
4285
     * @return array One item array containing the ID of the session on success, nothing on failure
4286
     */
4287
    public function addGroupSubscribedSession(int $groupId, int $sessionId): array
4288
    {
4289
        $userGroup = new UserGroup();
4290
4291
        return [$userGroup->subscribe_sessions_to_usergroup($groupId, [$sessionId], false)];
4292
    }
4293
4294
    /**
4295
     * Remove a user from the given group/class.
4296
     *
4297
     * @return array One item array containing true on success, false otherwise
4298
     */
4299
    public function deleteGroupSubscribedUser(int $groupId, int $userId): array
4300
    {
4301
        $userGroup = new UserGroup();
4302
4303
        return [$userGroup->delete_user_rel_group($userId, $groupId)];
4304
    }
4305
4306
    /**
4307
     * Remove a course to which the given group/class is subscribed.
4308
     *
4309
     * @return array One item array containing true on success, false otherwise
4310
     */
4311
    public function deleteGroupSubscribedCourse(int $groupId, int $courseId): array
4312
    {
4313
        $userGroup = new UserGroup();
4314
4315
        return [$userGroup->unsubscribe_courses_from_usergroup($groupId, [$courseId])];
4316
    }
4317
4318
    /**
4319
     * Remove a session to which the given group/class is subscribed.
4320
     *
4321
     * @return array One item array containing true on success, false otherwise
4322
     */
4323
    public function deleteGroupSubscribedSession(int $groupId, int $sessionId): array
4324
    {
4325
        $userGroup = new UserGroup();
4326
4327
        return [$userGroup->unsubscribeSessionsFromUserGroup($groupId, [$sessionId], false)];
4328
    }
4329
4330
    /**
4331
     * Get audit items from track_e_default.
4332
     *
4333
     * @throws Exception
4334
     */
4335
    public function getAuditItems(
4336
        string $defaultEventType,
4337
        ?int $cId = null,
4338
        ?int $sessionId = null,
4339
        ?string $afterDate = null,
4340
        ?string $beforeDate = null,
4341
        ?int $userId = null,
4342
        int $offset = 0,
4343
        int $limit = 100
4344
    ): array {
4345
        self::protectAdminEndpoint();
4346
4347
        return Event::getAuditItems(
4348
            $defaultEventType,
4349
            $cId,
4350
            $sessionId,
4351
            $afterDate,
4352
            $beforeDate,
4353
            $userId,
4354
            $offset,
4355
            $limit
4356
        );
4357
    }
4358
4359
    /**
4360
     * Returns the progress and time spent by the user in the session.
4361
     *
4362
     * @throws Exception
4363
     */
4364
    public function getUserProgressAndTimeInSession(int $userId, int $sessionId): array
4365
    {
4366
        $totalProgress = 0;
4367
        $totalTime = 0;
4368
        $nbCourses = 0;
4369
        $courses = SessionManager::getCoursesInSession($sessionId);
4370
        foreach ($courses as $courseId) {
4371
            $nbCourses++;
4372
            $totalTime += Tracking::get_time_spent_on_the_course(
4373
                $userId,
4374
                $courseId,
4375
                $sessionId
4376
            );
4377
            $courseInfo = api_get_course_info_by_id($courseId);
4378
            $totalProgress += Tracking::get_avg_student_progress(
4379
                $userId,
4380
                $courseInfo['code'],
4381
                [],
4382
                $sessionId
4383
            );
4384
        }
4385
        $userAverageCoursesTime = 0;
4386
        $userAverageProgress = 0;
4387
        if ($nbCourses != 0) {
4388
            $userAverageCoursesTime = $totalTime / $nbCourses;
4389
            $userAverageProgress = $totalProgress / $nbCourses;
4390
        }
4391
4392
        return [
4393
            'userAverageCoursesTime' => $userAverageCoursesTime,
4394
            'userAverageProgress' => $userAverageProgress,
4395
        ];
4396
    }
4397
4398
    /**
4399
     * Subscribe a specific course to a specific session, identified via extra field values.
4400
     *
4401
     * This method:
4402
     * - Locates the session ID using the provided session extra field name/value via ExtraFieldValue('session').
4403
     * - Locates the course c_id using the provided course extra field name/value via ExtraFieldValue('course').
4404
     * - Adds the course to the session using SessionManager::add_courses_to_session() (similar to addCoursesSession()).
4405
     *
4406
     * Required parameters:
4407
     * - session_field_name: Name of the extra field for sessions (e.g., 'peoplesoft_sid').
4408
     * - session_field_value: Value of the session extra field (e.g., '123450').
4409
     * - course_field_name: Name of the extra field for courses (e.g., 'peoplesoft_cid').
4410
     * - course_field_value: Value of the course extra field (e.g., '1').
4411
     *
4412
     * @param array $params Associative array of POST parameters.
4413
     *
4414
     * @throws Exception
4415
     *
4416
     * @return array Response in format: ['error' => bool, 'data' => array] on success, or ['error' => true, 'message' => string] on failure.
4417
     */
4418
    public function subscribeCourseToSessionFromExtraField($params)
4419
    {
4420
        // Validate required parameters (redundant with v2.php but for safety)
4421
        $required = ['session_field_name', 'session_field_value', 'course_field_name', 'course_field_value'];
4422
        foreach ($required as $key) {
4423
            if (empty($params[$key])) {
4424
                return [
4425
                    'error' => true,
4426
                    'message' => 'Missing required parameter: '.$key,
4427
                ];
4428
            }
4429
        }
4430
4431
        $sessionFieldName = $params['session_field_name'];
4432
        $sessionFieldValue = $params['session_field_value'];
4433
        $courseFieldName = $params['course_field_name'];
4434
        $courseFieldValue = $params['course_field_value'];
4435
4436
        // Get session ID from extra field value using ExtraFieldValue model
4437
        $sessionValueModel = new ExtraFieldValue('session');
4438
        $sessionIdList = $sessionValueModel->get_item_id_from_field_variable_and_field_value(
4439
            $sessionFieldName,
4440
            $sessionFieldValue,
4441
            false,
4442
            false,
4443
            true
4444
        );
4445
        if (empty($sessionIdList)) {
4446
            return [
4447
                'error' => true,
4448
                'message' => 'No session found with extra field value "'.$sessionFieldValue.'".',
4449
            ];
4450
        }
4451
        $sessionId = (int) $sessionIdList[0]['item_id']; // Assume single match
4452
4453
        // Get course c_id from extra field value using ExtraFieldValue model
4454
        $courseValueModel = new ExtraFieldValue('course');
4455
        $courseIdList = $courseValueModel->get_item_id_from_field_variable_and_field_value(
4456
            $courseFieldName,
4457
            $courseFieldValue,
4458
            false,
4459
            false,
4460
            true
4461
        );
4462
        if (empty($courseIdList)) {
4463
            return [
4464
                'error' => true,
4465
                'message' => 'No course found with extra field value "'.$courseFieldValue.'".',
4466
            ];
4467
        }
4468
        $cId = (int) $courseIdList[0]['item_id']; // Assume single match
4469
4470
        // Add course to session using existing core method (mirrors addCoursesSession logic)
4471
        $success = SessionManager::add_courses_to_session($sessionId, [$cId], false);
4472
4473
        if ($success) {
4474
            return [
4475
                'error' => false,
4476
                'data' => [
4477
                    'status' => true,
4478
                    'message' => 'Course subscribed to session',
4479
                    'id_session' => $sessionId,
4480
                    'c_id' => $cId,
4481
                ],
4482
            ];
4483
        } else {
4484
            return [
4485
                'error' => true,
4486
                'message' => 'Failed to subscribe course to session.',
4487
            ];
4488
        }
4489
    }
4490
4491
    /**
4492
     * Subscribe a specific user to a specific session, identified via extra field values.
4493
     *
4494
     * This method:
4495
     * - Locates the session ID using the provided session extra field name/value via ExtraFieldValue('session').
4496
     * - Locates the user ID using the provided user extra field name/value via ExtraFieldValue('user').
4497
     * - Adds the user to the session using SessionManager::subscribe_users_to_session() (similar to subscribeUsersToSession()).
4498
     *
4499
     * Required parameters:
4500
     * - session_field_name: Name of the extra field for sessions (e.g., 'peoplesoft_sid').
4501
     * - session_field_value: Value of the session extra field (e.g., '123450').
4502
     * - user_field_name: Name of the extra field for users (e.g., 'peoplesoft_uid').
4503
     * - user_field_value: Value of the user extra field (e.g., '1').
4504
     *
4505
     * @param array $params Associative array of POST parameters.
4506
     *
4507
     * @return array Response in format: ['error' => bool, 'data' => array] on success, or ['error' => true, 'message' => string] on failure.
4508
     */
4509
    public function subscribeUserToSessionFromExtraField($params)
4510
    {
4511
        // Validate required parameters (redundant with v2.php but for safety)
4512
        $required = ['session_field_name', 'session_field_value', 'user_field_name', 'user_field_value'];
4513
        foreach ($required as $key) {
4514
            if (empty($params[$key])) {
4515
                return [
4516
                    'error' => true,
4517
                    'message' => 'Missing required parameter: '.$key,
4518
                ];
4519
            }
4520
        }
4521
4522
        $sessionFieldName = $params['session_field_name'];
4523
        $sessionFieldValue = $params['session_field_value'];
4524
        $userFieldName = $params['user_field_name'];
4525
        $userFieldValue = $params['user_field_value'];
4526
4527
        // Get session ID from extra field value using ExtraFieldValue model
4528
        $sessionValueModel = new ExtraFieldValue('session');
4529
        $sessionIdList = $sessionValueModel->get_item_id_from_field_variable_and_field_value(
4530
            $sessionFieldName,
4531
            $sessionFieldValue,
4532
            false,
4533
            false,
4534
            true
4535
        );
4536
        if (empty($sessionIdList)) {
4537
            return [
4538
                'error' => true,
4539
                'message' => 'No session found with extra field value "'.$sessionFieldValue.'".',
4540
            ];
4541
        }
4542
        $sessionId = (int) $sessionIdList[0]['item_id']; // Extract item_id from sub-array, assume single match
4543
4544
        // Get user ID from extra field value using ExtraFieldValue model
4545
        $userValueModel = new ExtraFieldValue('user');
4546
        $userIdList = $userValueModel->get_item_id_from_field_variable_and_field_value(
4547
            $userFieldName,
4548
            $userFieldValue,
4549
            false,
4550
            false,
4551
            true
4552
        );
4553
        if (empty($userIdList)) {
4554
            return [
4555
                'error' => true,
4556
                'message' => 'No user found with extra field value "'.$userFieldValue.'".',
4557
            ];
4558
        }
4559
        $userId = (int) $userIdList[0]['item_id']; // Extract item_id from sub-array, assume single match
4560
4561
        // Add user to session using existing core method (mirrors subscribeUsersToSession logic)
4562
        $success = SessionManager::subscribeUsersToSession($sessionId, [$userId]);
4563
4564
        if ($success) {
4565
            return [
4566
                'error' => false,
4567
                'data' => [
4568
                    'status' => true,
4569
                    'message' => 'User subscribed to session',
4570
                    'id_session' => $sessionId,
4571
                    'user_id' => $userId,
4572
                ],
4573
            ];
4574
        } else {
4575
            return [
4576
                'error' => true,
4577
                'message' => 'Failed to subscribe user to session.',
4578
            ];
4579
        }
4580
    }
4581
4582
    /**
4583
     * Update a specific session, identified via extra field value.
4584
     *
4585
     * This method:
4586
     * - Locates the session ID using the provided extra field name/value via ExtraFieldValue('session').
4587
     * - Calls updateSession() with the located ID and provided update parameters (e.g., name, coach_username, dates).
4588
     *
4589
     * Required parameters:
4590
     * - field_name: Name of the extra field for sessions (e.g., 'peoplesoft_sid').
4591
     * - field_value: Value of the session extra field (e.g., PeopleSoft ID).
4592
     * - Optional update fields: name, coach_username, access_start_date, access_end_date, etc.
4593
     *
4594
     * @param array $params Associative array of POST parameters.
4595
     *
4596
     * @return array Response in format: ['error' => bool, 'data' => array] on success, or ['error' => true, 'message' => string] on failure.
4597
     */
4598
    public function updateSessionFromExtraField($params)
4599
    {
4600
        // Validate required parameters (redundant with v2.php but for safety)
4601
        $required = ['field_name', 'field_value'];
4602
        foreach ($required as $key) {
4603
            if (empty($params[$key])) {
4604
                return [
4605
                    'error' => true,
4606
                    'message' => 'Missing required parameter: '.$key,
4607
                ];
4608
            }
4609
        }
4610
4611
        $fieldName = $params['field_name'];
4612
        $fieldValue = $params['field_value'];
4613
4614
        // Get session ID from extra field value using ExtraFieldValue model
4615
        $sessionValueModel = new ExtraFieldValue('session');
4616
        $sessionIdList = $sessionValueModel->get_item_id_from_field_variable_and_field_value(
4617
            $fieldName,
4618
            $fieldValue,
4619
            false,
4620
            false,
4621
            true
4622
        );
4623
        if (empty($sessionIdList)) {
4624
            return [
4625
                'error' => true,
4626
                'message' => 'No session found with extra field value "'.$fieldValue.'".',
4627
            ];
4628
        }
4629
        $sessionId = (int) $sessionIdList[0]['item_id']; // Extract item_id from sub-array, assume single match
4630
4631
        // Prepare params for updateSession() by adding the located ID
4632
        $params['id_session'] = $sessionId;
4633
4634
        // Get coach ID if we got it as username
4635
        if (!empty($params['coach_username'])) {
4636
            $params['id_coach'] = UserManager::get_user_id_from_username($params['coach_username']);
4637
        }
4638
        // Delegate to existing updateSession() method (mirrors its logic)
4639
        $result = $this->updateSession($params);
4640
4641
        // Override message and include ID in data if successful
4642
        if (!$result['error']) {
4643
            $result['data']['id_session'] = $sessionId;
4644
            $result['data']['message'] = 'Session updated';
4645
        }
4646
4647
        return $result;
4648
    }
4649
4650
    /**
4651
     * Generate an API key for webservices access for the given user ID.
4652
     */
4653
    protected static function generateApiKeyForUser(int $userId): string
4654
    {
4655
        UserManager::add_api_key($userId, self::SERVICE_NAME);
4656
4657
        $apiKeys = UserManager::get_api_keys($userId, self::SERVICE_NAME);
4658
4659
        return current($apiKeys);
4660
    }
4661
4662
    /**
4663
     * Encode the given parameters (structured array) in JSON format.
4664
     *
4665
     * @param array $additionalParams Optional
4666
     *
4667
     * @return string
4668
     */
4669
    private function encodeParams(array $additionalParams = [])
4670
    {
4671
        $params = array_merge(
4672
            $additionalParams,
4673
            [
4674
                'api_key' => $this->apiKey,
4675
                'username' => $this->user->getUsername(),
4676
            ]
4677
        );
4678
4679
        return json_encode($params);
4680
    }
4681
4682
    /**
4683
     * Helper generating a query URL (to the current script) from an array of parameters
4684
     * (course, session, api_key and username) commonly used in webservice calls.
4685
     */
4686
    private function generateUrl(array $additionalParams = []): string
4687
    {
4688
        $queryParams = [
4689
            'course' => $this->course ? $this->course->getId() : null,
4690
            'session' => $this->session ? $this->session->getId() : null,
4691
            'api_key' => $this->apiKey,
4692
            'username' => $this->user->getUsername(),
4693
        ];
4694
4695
        return api_get_self().'?'
4696
            .http_build_query(array_merge($queryParams, $additionalParams));
4697
    }
4698
}
4699