Passed
Push — 1.11.x ( 859289...9e1dc9 )
by Angel Fernando Quiroz
09:54
created

Rest::addCourse()   B

Complexity

Conditions 7
Paths 25

Size

Total Lines 54
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 39
c 1
b 0
f 0
dl 0
loc 54
rs 8.3626
cc 7
nc 25
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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