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