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

Rest::getAuditItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\ExtraFieldValues;
7
use Chamilo\CoreBundle\Entity\Session;
8
use Chamilo\CourseBundle\Entity\CLpCategory;
9
use Chamilo\CourseBundle\Entity\CNotebook;
10
use Chamilo\CourseBundle\Entity\Repository\CNotebookRepository;
11
use Chamilo\UserBundle\Entity\User;
12
use Symfony\Component\HttpFoundation\Request as HttpRequest;
13
14
/**
15
 * Class RestApi.
16
 */
17
class Rest extends WebService
18
{
19
    public const SERVICE_NAME = 'MsgREST';
20
    public const EXTRA_FIELD_GCM_REGISTRATION = 'gcm_registration_id';
21
22
    public const GET_AUTH = 'authenticate';
23
    public const SAVE_GCM_ID = 'gcm_id';
24
    public const LOGOUT = 'logout';
25
26
    public const GET_USER_MESSAGES = 'user_messages';
27
    public const GET_USER_MESSAGES_RECEIVED = 'user_messages_received';
28
    public const DELETE_USER_MESSAGE = 'delete_user_message';
29
    public const GET_USER_MESSAGES_SENT = 'user_messages_sent';
30
    public const GET_COUNT_NEW_MESSAGES = 'get_count_new_messages';
31
    public const SET_MESSAGE_READ = 'set_message_read';
32
    public const POST_USER_MESSAGE_READ = 'user_message_read';
33
    public const POST_USER_MESSAGE_UNREAD = 'user_message_unread';
34
    public const SAVE_USER_MESSAGE = 'save_user_message';
35
    public const GET_MESSAGE_USERS = 'message_users';
36
    public const VIEW_MESSAGE = 'view_message';
37
38
    public const GET_USER_COURSES = 'user_courses';
39
    public const GET_USER_COURSES_BY_DATES = 'user_courses_by_dates';
40
    public const GET_USER_SESSIONS = 'user_sessions';
41
42
    public const VIEW_PROFILE = 'view_user_profile';
43
    public const GET_PROFILE = 'user_profile';
44
    public const GET_PROFILES_BY_EXTRA_FIELD = 'users_profiles_by_extra_field';
45
46
    public const VIEW_MY_COURSES = 'view_my_courses';
47
    public const VIEW_COURSE_HOME = 'view_course_home';
48
    public const GET_COURSE_INFO = 'course_info';
49
    public const GET_COURSE_DESCRIPTIONS = 'course_descriptions';
50
    public const GET_COURSE_DOCUMENTS = 'course_documents';
51
    public const GET_COURSE_ANNOUNCEMENTS = 'course_announcements';
52
    public const GET_COURSE_ANNOUNCEMENT = 'course_announcement';
53
    public const GET_COURSE_AGENDA = 'course_agenda';
54
    public const GET_COURSE_NOTEBOOKS = 'course_notebooks';
55
    public const GET_COURSE_FORUM_CATEGORIES = 'course_forumcategories';
56
    public const GET_COURSE_FORUM = 'course_forum';
57
    public const GET_COURSE_FORUM_THREAD = 'course_forumthread';
58
    public const GET_COURSE_LEARNPATHS = 'course_learnpaths';
59
    public const GET_COURSE_LEARNPATH = 'course_learnpath';
60
    public const GET_COURSE_LP_PROGRESS = 'course_lp_progress';
61
    public const GET_COURSE_LINKS = 'course_links';
62
    public const GET_COURSE_WORKS = 'course_works';
63
    public const GET_COURSE_EXERCISES = 'course_exercises';
64
    public const GET_COURSES_DETAILS_BY_EXTRA_FIELD = 'courses_details_by_extra_field';
65
66
    public const SAVE_COURSE_NOTEBOOK = 'save_course_notebook';
67
68
    public const SAVE_FORUM_POST = 'save_forum_post';
69
    public const SAVE_FORUM_THREAD = 'save_forum_thread';
70
    public const SET_THREAD_NOTIFY = 'set_thread_notify';
71
    public const DOWNLOAD_FORUM_ATTACHMENT = 'download_forum_attachment';
72
73
    public const GET_WORK_LIST = 'get_work_list';
74
    public const GET_WORK_STUDENTS_WITHOUT_PUBLICATIONS = 'get_work_students_without_publications';
75
    public const GET_WORK_USERS = 'get_work_users';
76
    public const GET_WORK_STUDENT_LIST = 'get_work_student_list';
77
    public const PUT_WORK_STUDENT_ITEM_VISIBILITY = 'put_course_work_visibility';
78
    public const DELETE_WORK_STUDENT_ITEM = 'delete_work_student_item';
79
    public const DELETE_WORK_CORRECTIONS = 'delete_work_corrections';
80
    public const DOWNLOAD_WORK_FOLDER = 'download_work_folder';
81
    public const DOWNLOAD_WORK_COMMENT_ATTACHMENT = 'download_work_comment_attachment';
82
    public const DOWNLOAD_WORK = 'download_work';
83
84
    public const VIEW_DOCUMENT_IN_FRAME = 'view_document_in_frame';
85
86
    public const VIEW_QUIZ_TOOL = 'view_quiz_tool';
87
88
    public const VIEW_SURVEY_TOOL = 'view_survey_tool';
89
90
    public const CREATE_CAMPUS = 'add_campus';
91
    public const EDIT_CAMPUS = 'edit_campus';
92
    public const DELETE_CAMPUS = 'delete_campus';
93
94
    public const GET_USERS = 'get_users';
95
    public const GET_USER_INFO_FROM_USERNAME = 'get_user_info_from_username';
96
    public const USERNAME_EXIST = 'username_exist';
97
    public const SAVE_USER = 'save_user';
98
    public const SAVE_USER_GET_APIKEY = 'save_user_get_apikey';
99
    public const SAVE_USER_JSON = 'save_user_json';
100
    public const UPDATE_USER_FROM_USERNAME = 'update_user_from_username';
101
    public const UPDATE_USER_APIKEY = 'update_user_apikey';
102
    public const DELETE_USER = 'delete_user';
103
    public const GET_USERS_API_KEYS = 'get_users_api_keys';
104
    public const GET_USER_API_KEY = 'get_user_api_key';
105
    public const GET_USER_LAST_CONNEXION = 'get_user_last_connexion';
106
    public const GET_USER_TOTAL_CONNEXION_TIME = 'get_user_total_connexion_time';
107
    public const GET_USER_PROGRESS_AND_TIME_IN_SESSION = 'get_user_progress_and_time_in_session';
108
    public const GET_USER_SUB_GROUP = 'get_user_sub_group';
109
110
    public const GET_COURSES = 'get_courses';
111
    public const GET_COURSES_FROM_EXTRA_FIELD = 'get_courses_from_extra_field';
112
    public const SAVE_COURSE = 'save_course';
113
    public const DELETE_COURSE = 'delete_course';
114
    public const GET_SESSION_FROM_EXTRA_FIELD = 'get_session_from_extra_field';
115
    public const GET_SESSION_INFO_FROM_EXTRA_FIELD = 'get_session_info_from_extra_field';
116
    public const SAVE_SESSION = 'save_session';
117
    public const CREATE_SESSION_FROM_MODEL = 'create_session_from_model';
118
    public const UPDATE_SESSION = 'update_session';
119
    public const GET_SESSIONS = 'get_sessions';
120
121
    public const SUBSCRIBE_USER_TO_COURSE = 'subscribe_user_to_course';
122
    public const SUBSCRIBE_USER_TO_COURSE_PASSWORD = 'subscribe_user_to_course_password';
123
    public const UNSUBSCRIBE_USER_FROM_COURSE = 'unsubscribe_user_from_course';
124
    public const GET_USERS_SUBSCRIBED_TO_COURSE = 'get_users_subscribed_to_course';
125
126
    public const ADD_COURSES_SESSION = 'add_courses_session';
127
    public const ADD_USERS_SESSION = 'add_users_session';
128
    public const SUBSCRIBE_USER_TO_SESSION_FROM_USERNAME = 'subscribe_user_to_session_from_username';
129
    public const SUBSCRIBE_USERS_TO_SESSION = 'subscribe_users_to_session';
130
    public const UNSUBSCRIBE_USERS_FROM_SESSION = 'unsubscribe_users_from_session';
131
    public const GET_USERS_SUBSCRIBED_TO_SESSION = 'get_users_subscribed_to_session';
132
133
    public const GET_COURSE_QUIZ_MDL_COMPAT = 'get_course_quiz_mdl_compat';
134
135
    public const UPDATE_USER_PAUSE_TRAINING = 'update_user_pause_training';
136
137
    public const CHECK_CONDITIONAL_LOGIN = 'check_conditional_login';
138
    public const GET_LEGAL_CONDITIONS = 'get_legal_conditions';
139
    public const UPDATE_CONDITION_ACCEPTED = 'update_condition_accepted';
140
    public const GET_TEST_UPDATES_LIST = 'get_test_updates_list';
141
    public const GET_TEST_AVERAGE_RESULTS_LIST = 'get_test_average_results_list';
142
143
    public const GET_GROUPS = 'get_groups';
144
    public const GROUP_EXISTS = 'group_exists';
145
    public const ADD_GROUP = 'add_group';
146
    public const DELETE_GROUP = 'delete_group';
147
    public const GET_GROUP_SUB_USERS = 'get_group_sub_users';
148
    public const GET_GROUP_SUB_COURSES = 'get_group_sub_courses';
149
    public const GET_GROUP_SUB_SESSIONS = 'get_group_sub_sessions';
150
    public const ADD_GROUP_SUB_USER = 'add_group_sub_user';
151
    public const ADD_GROUP_SUB_COURSE = 'add_group_sub_course';
152
    public const ADD_GROUP_SUB_SESSION = 'add_group_sub_session';
153
    public const DELETE_GROUP_SUB_USER = 'delete_group_sub_user';
154
    public const DELETE_GROUP_SUB_COURSE = 'delete_group_sub_course';
155
    public const DELETE_GROUP_SUB_SESSION = 'delete_group_sub_session';
156
    public const GET_AUDIT_ITEMS = 'get_audit_items';
157
    public const SUBSCRIBE_COURSE_TO_SESSION_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