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

Rest::getAuditItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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