Rest::deleteWorkCorrections()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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