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