Passed
Push — 1.11.x ( ad24b8...7487d3 )
by Angel Fernando Quiroz
11:41
created

Rest::getCourseQuizMdlCompat()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 48
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

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