Passed
Push — 1.11.x ( 0dc2f4...ebe695 )
by Yannick
10:06
created

Rest::subscribeCourseToSessionFromExtraField()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 69
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 47
c 1
b 0
f 0
dl 0
loc 69
rs 8.5341
cc 6
nc 9
nop 1

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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