Passed
Push — 1.11.x ( 1b35e4...608c0e )
by Julito
14:08
created

ZoomPlugin::scheduleMeeting()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 14
c 1
b 1
f 0
nc 2
nop 8
dl 0
loc 18
rs 9.7998

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\Session;
7
use Chamilo\PluginBundle\Zoom\API\JWTClient;
8
use Chamilo\PluginBundle\Zoom\API\MeetingInfoGet;
9
use Chamilo\PluginBundle\Zoom\API\MeetingRegistrant;
10
use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
11
use Chamilo\PluginBundle\Zoom\API\RecordingFile;
12
use Chamilo\PluginBundle\Zoom\API\RecordingList;
13
use Chamilo\PluginBundle\Zoom\MeetingEntity;
14
use Chamilo\PluginBundle\Zoom\MeetingEntityRepository;
15
use Chamilo\PluginBundle\Zoom\RecordingEntity;
16
use Chamilo\PluginBundle\Zoom\RecordingEntityRepository;
17
use Chamilo\PluginBundle\Zoom\RegistrantEntity;
18
use Chamilo\PluginBundle\Zoom\RegistrantEntityRepository;
19
use Chamilo\UserBundle\Entity\User;
20
use Doctrine\ORM\EntityRepository;
21
use Doctrine\ORM\OptimisticLockException;
22
use Doctrine\ORM\Tools\SchemaTool;
23
use Doctrine\ORM\Tools\ToolsException;
24
25
/**
26
 * Class ZoomPlugin. Integrates Zoom meetings in courses.
27
 */
28
class ZoomPlugin extends Plugin
29
{
30
    public $isCoursePlugin = true;
31
32
    /**
33
     * @var JWTClient
34
     */
35
    private $jwtClient;
36
37
    /**
38
     * ZoomPlugin constructor.
39
     * {@inheritdoc}
40
     * Initializes the API JWT client and the entity repositories.
41
     */
42
    public function __construct()
43
    {
44
        parent::__construct(
45
            '0.2',
46
            'Sébastien Ducoulombier, Julio Montoya',
47
            [
48
                'tool_enable' => 'boolean',
49
                'apiKey' => 'text',
50
                'apiSecret' => 'text',
51
                'verificationToken' => 'text',
52
                'enableParticipantRegistration' => 'boolean',
53
                'enableCloudRecording' => 'boolean',
54
                'enableGlobalConference' => 'boolean',
55
                'enableGlobalConferencePerUser' => 'boolean',
56
                'globalConferenceAllowRoles' => [
57
                    'type' => 'select',
58
                    'options' => [
59
                        PLATFORM_ADMIN => get_lang('Administrator'),
60
                        COURSEMANAGER => get_lang('Teacher'),
61
                        STUDENT => get_lang('Student'),
62
                        STUDENT_BOSS => get_lang('StudentBoss'),
63
                    ],
64
                    'attributes' => ['multiple' => 'multiple'],
65
                ],
66
                'globalConferencePerUserAllowRoles' => [
67
                    'type' => 'select',
68
                    'options' => [
69
                        PLATFORM_ADMIN => get_lang('Administrator'),
70
                        COURSEMANAGER => get_lang('Teacher'),
71
                        STUDENT => get_lang('Student'),
72
                        STUDENT_BOSS => get_lang('StudentBoss'),
73
                    ],
74
                    'attributes' => ['multiple' => 'multiple'],
75
                ],
76
            ]
77
        );
78
79
        $this->isAdminPlugin = true;
80
        $this->jwtClient = new JWTClient($this->get('apiKey'), $this->get('apiSecret'));
81
    }
82
83
    /**
84
     * Caches and returns an instance of this class.
85
     *
86
     * @return ZoomPlugin the instance to use
87
     */
88
    public static function create()
89
    {
90
        static $instance = null;
91
92
        return $instance ? $instance : $instance = new self();
93
    }
94
95
    /**
96
     * @return bool
97
     */
98
    public static function currentUserCanJoinGlobalMeeting()
99
    {
100
        $user = api_get_user_entity(api_get_user_id());
101
102
        if (null === $user) {
103
            return false;
104
        }
105
106
        //return 'true' === api_get_plugin_setting('zoom', 'enableGlobalConference') && api_user_is_login();
107
        return
108
            'true' === api_get_plugin_setting('zoom', 'enableGlobalConference')
109
            && in_array(
110
                (api_is_platform_admin() ? PLATFORM_ADMIN : $user->getStatus()),
111
                (array) api_get_plugin_setting('zoom', 'globalConferenceAllowRoles')
112
            )
113
            ;
114
115
    }
116
117
    /**
118
     * @return bool
119
     */
120
    public static function currentUserCanCreateUserMeeting()
121
    {
122
        $user = api_get_user_entity(api_get_user_id());
123
124
        if (null === $user) {
125
            return false;
126
        }
127
128
        return
129
            'true' === api_get_plugin_setting('zoom', 'enableGlobalConferencePerUser')
130
            && in_array(
131
                (api_is_platform_admin() ? PLATFORM_ADMIN : $user->getStatus()),
132
                (array) api_get_plugin_setting('zoom', 'globalConferencePerUserAllowRoles')
133
            )
134
        ;
135
    }
136
137
    /**
138
     * @return array [ $title => $link ]
139
     */
140
    public function meetingsToWhichCurrentUserIsRegisteredComingSoon()
141
    {
142
        $linkTemplate = api_get_path(WEB_PLUGIN_PATH).'zoom/join_meeting.php?meetingId=%s';
143
        $user = api_get_user_entity(api_get_user_id());
144
        $meetings = self::getRegistrantRepository()->meetingsComingSoonRegistrationsForUser($user);
145
        $items = [];
146
        foreach ($meetings as $registrant) {
147
            $meeting = $registrant->getMeeting();
148
            $items[
149
                sprintf(
150
                    $this->get_lang('DateMeetingTitle'),
151
                    $meeting->formattedStartTime,
152
                    $meeting->getMeetingInfoGet()->topic
153
                )
154
            ] = sprintf($linkTemplate, $meeting->getId());
155
        }
156
157
        return $items;
158
    }
159
160
    /**
161
     * @return array
162
     */
163
    public function getProfileBlockItems()
164
    {
165
        $elements = $this->meetingsToWhichCurrentUserIsRegisteredComingSoon();
166
        if (self::currentUserCanJoinGlobalMeeting()) {
167
            $elements[$this->get_lang('CreateGlobalVideoConference')] = api_get_path(WEB_PLUGIN_PATH).'zoom/global.php';
168
        }
169
170
        if (self::currentUserCanCreateUserMeeting()) {
171
            $elements[$this->get_lang('CreateUserVideoConference')] = api_get_path(WEB_PLUGIN_PATH).'zoom/user.php';
172
        }
173
        $items = [];
174
        foreach ($elements as $title => $link) {
175
            $items[] = [
176
                'class' => 'video-conference',
177
                'icon' => Display::return_icon(
178
                    'zoom.png',
179
                    get_lang('VideoConference')
180
                ),
181
                'link' => $link,
182
                'title' => $title,
183
            ];
184
        }
185
186
        return $items;
187
    }
188
189
    /**
190
     * @return MeetingEntityRepository|EntityRepository
191
     */
192
    public static function getMeetingRepository()
193
    {
194
        return Database::getManager()->getRepository(MeetingEntity::class);
195
    }
196
197
    /**
198
     * @return RecordingEntityRepository|EntityRepository
199
     */
200
    public static function getRecordingRepository()
201
    {
202
        return Database::getManager()->getRepository(RecordingEntity::class);
203
    }
204
205
    /**
206
     * @return RegistrantEntityRepository|EntityRepository
207
     */
208
    public static function getRegistrantRepository()
209
    {
210
        return Database::getManager()->getRepository(RegistrantEntity::class);
211
    }
212
213
    /**
214
     * Creates this plugin's related tables in the internal database.
215
     * Installs course fields in all courses.
216
     *
217
     * @throws ToolsException
218
     */
219
    public function install()
220
    {
221
        (new SchemaTool(Database::getManager()))->createSchema([
222
            Database::getManager()->getClassMetadata(MeetingEntity::class),
223
            Database::getManager()->getClassMetadata(RecordingEntity::class),
224
            Database::getManager()->getClassMetadata(RegistrantEntity::class),
225
        ]);
226
        $this->install_course_fields_in_all_courses();
227
    }
228
229
    /**
230
     * Drops this plugins' related tables from the internal database.
231
     * Uninstalls course fields in all courses().
232
     */
233
    public function uninstall()
234
    {
235
        (new SchemaTool(Database::getManager()))->dropSchema([
236
            Database::getManager()->getClassMetadata(MeetingEntity::class),
237
            Database::getManager()->getClassMetadata(RecordingEntity::class),
238
            Database::getManager()->getClassMetadata(RegistrantEntity::class),
239
        ]);
240
        $this->uninstall_course_fields_in_all_courses();
241
    }
242
243
    /**
244
     * Generates the search form to include in the meeting list administration page.
245
     * The form has DatePickers 'start' and 'end' and Checkbox 'reloadRecordingLists'.
246
     *
247
     * @return FormValidator the form
248
     */
249
    public function getAdminSearchForm()
250
    {
251
        $form = new FormValidator('search');
252
        $form->addHeader($this->get_lang('SearchMeeting'));
253
        $form->addDatePicker('start', get_lang('StartDate'));
254
        $form->addDatePicker('end', get_lang('EndDate'));
255
        $form->addButtonSearch(get_lang('Search'));
256
        $oneMonth = new DateInterval('P1M');
257
        if ($form->validate()) {
258
            try {
259
                $start = new DateTime($form->getSubmitValue('start'));
260
            } catch (Exception $exception) {
261
                $start = new DateTime();
262
                $start->sub($oneMonth);
263
            }
264
            try {
265
                $end = new DateTime($form->getSubmitValue('end'));
266
            } catch (Exception $exception) {
267
                $end = new DateTime();
268
                $end->add($oneMonth);
269
            }
270
        } else {
271
            $start = new DateTime();
272
            $start->sub($oneMonth);
273
            $end = new DateTime();
274
            $end->add($oneMonth);
275
        }
276
        try {
277
            $form->setDefaults([
278
                'start' => $start->format('Y-m-d'),
279
                'end' => $end->format('Y-m-d'),
280
            ]);
281
        } catch (Exception $exception) {
282
            error_log(join(':', [__FILE__, __LINE__, $exception]));
283
        }
284
285
        return $form;
286
    }
287
288
    /**
289
     * Generates a meeting edit form and updates the meeting on validation.
290
     *
291
     * @param MeetingEntity $meetingEntity the meeting
292
     *
293
     * @throws Exception
294
     *
295
     * @return FormValidator
296
     */
297
    public function getEditMeetingForm($meetingEntity)
298
    {
299
        $meetingInfoGet = $meetingEntity->getMeetingInfoGet();
300
        $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
301
        $form->addHeader($this->get_lang('UpdateMeeting'));
302
        $form->addText('topic', $this->get_lang('Topic'));
303
        if ($meetingEntity->requiresDateAndDuration()) {
304
            $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
305
            $form->setRequired($startTimeDatePicker);
306
            $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
307
            $form->setRequired($durationNumeric);
308
        }
309
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
310
        // $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
311
        $form->addButtonUpdate(get_lang('Update'));
312
        if ($form->validate()) {
313
            if ($meetingEntity->requiresDateAndDuration()) {
314
                $meetingInfoGet->start_time = (new DateTime($form->getSubmitValue('startTime')))->format(
315
                    DateTimeInterface::ISO8601
316
                );
317
                $meetingInfoGet->timezone = date_default_timezone_get();
318
                $meetingInfoGet->duration = (int) $form->getSubmitValue('duration');
319
            }
320
            $meetingInfoGet->topic = $form->getSubmitValue('topic');
321
            $meetingInfoGet->agenda = $form->getSubmitValue('agenda');
322
            try {
323
                $meetingInfoGet->update();
324
                $meetingEntity->setMeetingInfoGet($meetingInfoGet);
325
                Database::getManager()->persist($meetingEntity);
326
                Database::getManager()->flush();
327
                Display::addFlash(
328
                    Display::return_message($this->get_lang('MeetingUpdated'), 'confirm')
329
                );
330
            } catch (Exception $exception) {
331
                Display::addFlash(
332
                    Display::return_message($exception->getMessage(), 'error')
333
                );
334
            }
335
        }
336
        $defaults = [
337
            'topic' => $meetingInfoGet->topic,
338
            'agenda' => $meetingInfoGet->agenda,
339
        ];
340
        if ($meetingEntity->requiresDateAndDuration()) {
341
            $defaults['startTime'] = $meetingEntity->startDateTime->format('Y-m-d H:i');
342
            $defaults['duration'] = $meetingInfoGet->duration;
343
        }
344
        $form->setDefaults($defaults);
345
346
        return $form;
347
    }
348
349
    /**
350
     * Generates a meeting delete form and deletes the meeting on validation.
351
     *
352
     * @param MeetingEntity $meetingEntity
353
     * @param string        $returnURL     where to redirect to on successful deletion
354
     *
355
     * @throws Exception
356
     *
357
     * @return FormValidator
358
     */
359
    public function getDeleteMeetingForm($meetingEntity, $returnURL)
360
    {
361
        $form = new FormValidator('delete', 'post', $_SERVER['REQUEST_URI']);
362
        $form->addButtonDelete($this->get_lang('DeleteMeeting'));
363
        if ($form->validate()) {
364
            try {
365
                $meetingEntity->getMeetingInfoGet()->delete();
366
                Database::getManager()->remove($meetingEntity);
367
                Database::getManager()->flush();
368
369
                Display::addFlash(
370
                    Display::return_message($this->get_lang('MeetingDeleted'), 'confirm')
371
                );
372
                api_location($returnURL);
373
            } catch (Exception $exception) {
374
                Display::addFlash(
375
                    Display::return_message($exception->getMessage(), 'error')
376
                );
377
            }
378
        }
379
380
        return $form;
381
    }
382
383
    /**
384
     * Generates a registrant list update form listing course and session users.
385
     * Updates the list on validation.
386
     *
387
     * @param MeetingEntity $meetingEntity
388
     *
389
     * @throws Exception
390
     *
391
     * @return FormValidator
392
     */
393
    public function getRegisterParticipantForm($meetingEntity)
394
    {
395
        $form = new FormValidator('register', 'post', $_SERVER['REQUEST_URI']);
396
        $userIdSelect = $form->addSelect('userIds', $this->get_lang('RegisteredUsers'));
397
        $userIdSelect->setMultiple(true);
398
        $form->addButtonSend($this->get_lang('UpdateRegisteredUserList'));
399
400
        $users = $meetingEntity->getRegistrableUsers();
401
        foreach ($users as $user) {
402
            $userIdSelect->addOption(
403
                api_get_person_name($user->getFirstname(), $user->getLastname()),
404
                $user->getId()
405
            );
406
        }
407
408
        if ($form->validate()) {
409
            $selectedUserIds = $form->getSubmitValue('userIds');
410
            $selectedUsers = [];
411
            foreach ($users as $user) {
412
                if (in_array($user->getId(), $selectedUserIds)) {
413
                    $selectedUsers[] = $user;
414
                }
415
            }
416
            try {
417
                $this->updateRegistrantList($meetingEntity, $selectedUsers);
418
                Display::addFlash(
419
                    Display::return_message($this->get_lang('RegisteredUserListWasUpdated'), 'confirm')
420
                );
421
            } catch (Exception $exception) {
422
                Display::addFlash(
423
                    Display::return_message($exception->getMessage(), 'error')
424
                );
425
            }
426
        }
427
        $registeredUserIds = [];
428
        foreach ($meetingEntity->getRegistrants() as $registrant) {
429
            $registeredUserIds[] = $registrant->getUser()->getId();
430
        }
431
        $userIdSelect->setSelected($registeredUserIds);
432
433
        return $form;
434
    }
435
436
    /**
437
     * Generates a meeting recording files management form.
438
     * Takes action on validation.
439
     *
440
     * @param MeetingEntity $meeting
441
     *
442
     * @throws Exception
443
     *
444
     * @return FormValidator
445
     */
446
    public function getFileForm($meeting)
447
    {
448
        $form = new FormValidator('fileForm', 'post', $_SERVER['REQUEST_URI']);
449
        if (!$meeting->getRecordings()->isEmpty()) {
450
            $fileIdSelect = $form->addSelect('fileIds', get_lang('Files'));
451
            $fileIdSelect->setMultiple(true);
452
            foreach ($meeting->getRecordings() as &$recording) {
453
                // $recording->instanceDetails = $plugin->getPastMeetingInstanceDetails($instance->uuid);
454
                $options = [];
455
                foreach ($recording->getRecordingMeeting()->recording_files as $file) {
456
                    $options[] = [
457
                        'text' => sprintf(
458
                            '%s.%s (%s)',
459
                            $file->recording_type,
460
                            $file->file_type,
461
                            //$file->formattedFileSize
462
                            $file->file_size
463
                        ),
464
                        'value' => $file->id,
465
                    ];
466
                }
467
                $fileIdSelect->addOptGroup(
468
                    $options,
469
                    sprintf("%s (%s)", $recording->formattedStartTime, $recording->formattedDuration)
470
                );
471
            }
472
            $actions = [];
473
            if ($meeting->isCourseMeeting()) {
474
                $actions['CreateLinkInCourse'] = $this->get_lang('CreateLinkInCourse');
475
                $actions['CopyToCourse'] = $this->get_lang('CopyToCourse');
476
            }
477
            $actions['DeleteFile'] = $this->get_lang('DeleteFile');
478
            $form->addRadio(
479
                'action',
480
                get_lang('Action'),
481
                $actions
482
            );
483
            $form->addButtonUpdate($this->get_lang('DoIt'));
484
            if ($form->validate()) {
485
                foreach ($meeting->getRecordings() as $recording) {
486
                    foreach ($recording->files as $file) {
487
                        if (in_array($file->id, $form->getSubmitValue('fileIds'))) {
488
                            $name = sprintf(
489
                                $this->get_lang('XRecordingOfMeetingXFromXDurationXDotX'),
490
                                $file->recording_type,
491
                                $meeting->getId(),
492
                                $recording->formattedStartTime,
493
                                $recording->formattedDuration,
494
                                $file->file_type
495
                            );
496
                            $action = $form->getSubmitValue('action');
497
                            if ('CreateLinkInCourse' === $action && $meeting->isCourseMeeting()) {
498
                                try {
499
                                    $this->createLinkToFileInCourse($meeting, $file, $name);
500
                                    Display::addFlash(
501
                                        Display::return_message($this->get_lang('LinkToFileWasCreatedInCourse'), 'success')
502
                                    );
503
                                } catch (Exception $exception) {
504
                                    Display::addFlash(
505
                                        Display::return_message($exception->getMessage(), 'error')
506
                                    );
507
                                }
508
                            } elseif ('CopyToCourse' === $action && $meeting->isCourseMeeting()) {
509
                                try {
510
                                    $this->copyFileToCourse($meeting, $file, $name);
511
                                    Display::addFlash(
512
                                        Display::return_message($this->get_lang('FileWasCopiedToCourse'), 'confirm')
513
                                    );
514
                                } catch (Exception $exception) {
515
                                    Display::addFlash(
516
                                        Display::return_message($exception->getMessage(), 'error')
517
                                    );
518
                                }
519
                            } elseif ('DeleteFile' === $action) {
520
                                try {
521
                                    $file->delete();
522
                                    Display::addFlash(
523
                                        Display::return_message($this->get_lang('FileWasDeleted'), 'confirm')
524
                                    );
525
                                } catch (Exception $exception) {
526
                                    Display::addFlash(
527
                                        Display::return_message($exception->getMessage(), 'error')
528
                                    );
529
                                }
530
                            }
531
                        }
532
                    }
533
                }
534
            }
535
        }
536
537
        return $form;
538
    }
539
540
    /**
541
     * Generates a form to fast and easily create and start an instant meeting.
542
     * On validation, create it then redirect to it and exit.
543
     *
544
     * @param User    $user
545
     * @param Course  $course
546
     * @param Session $session
547
     *
548
     * @return FormValidator
549
     */
550
    public function getCreateInstantMeetingForm($user, $course, $session)
551
    {
552
        $form = new FormValidator('createInstantMeetingForm', 'post', '', '_blank');
553
        $form->addButton('startButton', $this->get_lang('StartInstantMeeting'));
554
        if ($form->validate()) {
555
            try {
556
                $this->startInstantMeeting($this->get_lang('InstantMeeting'), $user, $course, $session);
557
            } catch (Exception $exception) {
558
                Display::addFlash(
559
                    Display::return_message($exception->getMessage(), 'error')
560
                );
561
            }
562
        }
563
564
        return $form;
565
    }
566
567
    /**
568
     * Generates a form to schedule a meeting.
569
     * On validation, creates it and redirects to its page.
570
     *
571
     * @param User|null    $user
572
     * @param Course|null  $course
573
     * @param Session|null $session
574
     *
575
     * @throws Exception
576
     *
577
     * @return FormValidator
578
     */
579
    public function getScheduleMeetingForm($user, $course = null, $session = null)
580
    {
581
        $form = new FormValidator('scheduleMeetingForm');
582
        $form->addHeader($this->get_lang('ScheduleTheMeeting'));
583
        $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
584
        $form->setRequired($startTimeDatePicker);
585
586
        $form->addText('topic', $this->get_lang('Topic'), true);
587
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
588
589
        $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
590
        $form->setRequired($durationNumeric);
591
592
        // $passwordText = $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
593
        if (!is_null($course)) {
594
            $registrationOptions = [
595
                'RegisterAllCourseUsers' => $this->get_lang('RegisterAllCourseUsers'),
596
            ];
597
            $groups = GroupManager::get_groups();
598
            if (!empty($groups)) {
599
                $registrationOptions['RegisterTheseGroupMembers'] = get_lang('RegisterTheseGroupMembers');
600
            }
601
            $registrationOptions['RegisterNoUser'] = $this->get_lang('RegisterNoUser');
602
            $userRegistrationRadio = $form->addRadio(
603
                'userRegistration',
604
                $this->get_lang('UserRegistration'),
605
                $registrationOptions
606
            );
607
            $groupOptions = [];
608
            foreach ($groups as $group) {
609
                $groupOptions[$group['id']] = $group['name'];
610
            }
611
            $groupIdsSelect = $form->addSelect(
612
                'groupIds',
613
                $this->get_lang('RegisterTheseGroupMembers'),
614
                $groupOptions
615
            );
616
            $groupIdsSelect->setMultiple(true);
617
            if (!empty($groups)) {
618
                $jsCode = sprintf(
619
                    "getElementById('%s').parentNode.parentNode.parentNode.style.display = getElementById('%s').checked ? 'block' : 'none'",
620
                    $groupIdsSelect->getAttribute('id'),
621
                    $userRegistrationRadio->getelements()[1]->getAttribute('id')
622
                );
623
624
                $form->setAttribute('onchange', $jsCode);
625
            }
626
        }
627
        $form->addButtonCreate(get_lang('Save'));
628
629
        if ($form->validate()) {
630
            try {
631
                $newMeeting = $this->scheduleMeeting(
632
                    $user,
633
                    $course,
634
                    $session,
635
                    new DateTime($form->getSubmitValue('startTime')),
636
                    $form->getSubmitValue('duration'),
637
                    $form->getSubmitValue('topic'),
638
                    $form->getSubmitValue('agenda'),
639
                    ''
640
                );
641
                Display::addFlash(
642
                    Display::return_message($this->get_lang('NewMeetingCreated'))
643
                );
644
                if ($newMeeting->isCourseMeeting()) {
645
                    if ('RegisterAllCourseUsers' === $form->getSubmitValue('userRegistration')) {
646
                        $this->registerAllCourseUsers($newMeeting);
647
                        Display::addFlash(
648
                            Display::return_message($this->get_lang('AllCourseUsersWereRegistered'))
649
                        );
650
                    } elseif ('RegisterTheseGroupMembers' === $form->getSubmitValue('userRegistration')) {
651
                        $userIds = [];
652
                        foreach ($form->getSubmitValue('groupIds') as $groupId) {
653
                            $userIds = array_unique(array_merge($userIds, GroupManager::get_users($groupId)));
654
                        }
655
                        $users = Database::getManager()->getRepository('ChamiloUserBundle:User')->findBy(
656
                            ['id' => $userIds]
657
                        );
658
                        $this->registerUsers($newMeeting, $users);
659
                        Display::addFlash(
660
                            Display::return_message($this->get_lang('GroupUsersWereRegistered'))
661
                        );
662
                    }
663
                    api_location('meeting_from_start.php?meetingId='.$newMeeting->getId());
664
                } elseif (!is_null($user)) {
665
                    api_location('meeting_from_user.php?meetingId='.$newMeeting->getId());
666
                }
667
            } catch (Exception $exception) {
668
                Display::addFlash(
669
                    Display::return_message($exception->getMessage(), 'error')
670
                );
671
            }
672
        } else {
673
            $form->setDefaults(
674
                [
675
                    'duration' => 60,
676
                    'userRegistration' => 'RegisterAllCourseUsers',
677
                ]
678
            );
679
        }
680
681
        return $form;
682
    }
683
684
    /**
685
     * @param MeetingEntity $meetingEntity
686
     *
687
     * @return bool whether the logged-in user can manage conferences in this context, that is either
688
     *              the current course or session coach, the platform admin or the current course admin
689
     */
690
    public function userIsConferenceManager($meetingEntity)
691
    {
692
        if (null === $meetingEntity) {
693
            return false;
694
        }
695
696
        return api_is_coach()
697
            || api_is_platform_admin()
698
            || $meetingEntity->isCourseMeeting() && api_get_course_id() && api_is_course_admin()
699
            || $meetingEntity->isUserMeeting() && $meetingEntity->getUser()->getId() == api_get_user_id();
700
    }
701
702
    /**
703
     * @param Course $course
704
     *
705
     * @return bool whether the logged-in user can manage conferences in this context, that is either
706
     *              the current course or session coach, the platform admin or the current course admin
707
     */
708
    public function userIsCourseConferenceManager($course)
709
    {
710
        return api_is_coach()
711
            || api_is_platform_admin()
712
            || api_get_course_id() && api_is_course_admin();
713
    }
714
715
    /**
716
     * Adds to the meeting course documents a link to a meeting instance recording file.
717
     *
718
     * @param MeetingEntity $meeting
719
     * @param RecordingFile $file
720
     * @param string        $name
721
     *
722
     * @throws Exception
723
     */
724
    public function createLinkToFileInCourse($meeting, $file, $name)
725
    {
726
        $course = $meeting->getCourse();
727
        if (is_null($course)) {
728
            throw new Exception('This meeting is not linked to a course');
729
        }
730
        $courseInfo = api_get_course_info_by_id($course->getId());
731
        if (empty($courseInfo)) {
732
            throw new Exception('This meeting is not linked to a valid course');
733
        }
734
        $path = '/zoom_meeting_recording_file_'.$file->id.'.'.$file->file_type;
735
        $docId = DocumentManager::addCloudLink($courseInfo, $path, $file->play_url, $name);
736
        if (!$docId) {
737
            throw new Exception(get_lang(DocumentManager::cloudLinkExists($courseInfo, $path, $file->play_url) ? 'UrlAlreadyExists' : 'ErrorAddCloudLink'));
738
        }
739
    }
740
741
    /**
742
     * Copies a recording file to a meeting's course.
743
     *
744
     * @param MeetingEntity $meeting
745
     * @param RecordingFile $file
746
     * @param string        $name
747
     *
748
     * @throws Exception
749
     */
750
    public function copyFileToCourse($meeting, $file, $name)
751
    {
752
        $course = $meeting->getCourse();
753
        if (is_null($course)) {
754
            throw new Exception('This meeting is not linked to a course');
755
        }
756
        $courseInfo = api_get_course_info_by_id($course->getId());
757
        if (empty($courseInfo)) {
758
            throw new Exception('This meeting is not linked to a valid course');
759
        }
760
        $tmpFile = tmpfile();
761
        if (false === $tmpFile) {
762
            throw new Exception('tmpfile() returned false');
763
        }
764
        $curl = curl_init($file->getFullDownloadURL($this->jwtClient->token));
765
        if (false === $curl) {
766
            throw new Exception('Could not init curl: '.curl_error($curl));
767
        }
768
        if (!curl_setopt_array(
769
            $curl,
770
            [
771
                CURLOPT_FILE => $tmpFile,
772
                CURLOPT_FOLLOWLOCATION => true,
773
                CURLOPT_MAXREDIRS => 10,
774
                CURLOPT_TIMEOUT => 120,
775
            ]
776
        )) {
777
            throw new Exception("Could not set curl options: ".curl_error($curl));
778
        }
779
        if (false === curl_exec($curl)) {
780
            throw new Exception("curl_exec failed: ".curl_error($curl));
781
        }
782
        $newPath = handle_uploaded_document(
783
            $courseInfo,
784
            [
785
                'name' => $name,
786
                'tmp_name' => stream_get_meta_data($tmpFile)['uri'],
787
                'size' => filesize(stream_get_meta_data($tmpFile)['uri']),
788
                'from_file' => true,
789
                'type' => $file->file_type,
790
            ],
791
            '/',
792
            api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document',
793
            api_get_user_id(),
794
            0,
795
            null,
796
            0,
797
            '',
798
            true,
799
            false,
800
            null,
801
            $meeting->getSession()->getId(),
802
            true
803
        );
804
        fclose($tmpFile);
805
        if (false === $newPath) {
806
            throw new Exception('could not handle uploaded document');
807
        }
808
    }
809
810
    /**
811
     * Return the current global meeting (create it if needed).
812
     *
813
     * @throws Exception
814
     *
815
     * @return string
816
     */
817
    public function getGlobalMeeting()
818
    {
819
        foreach ($this->getMeetingRepository()->unfinishedGlobalMeetings() as $meeting) {
820
            return $meeting;
821
        }
822
823
        return $this->createGlobalMeeting();
824
    }
825
826
    /**
827
     * Returns the URL to enter (start or join) a meeting or null if not possible to enter the meeting,
828
     * The returned URL depends on the meeting current status (waiting, started or finished) and the current user.
829
     *
830
     * @param MeetingEntity $meeting
831
     *
832
     * @throws Exception
833
     * @throws OptimisticLockException
834
     *
835
     * @return string|null
836
     */
837
    public function getStartOrJoinMeetingURL($meeting)
838
    {
839
        $status = $meeting->getMeetingInfoGet()->status;
840
841
        switch ($status) {
842
            case 'waiting':
843
                // Zoom does not allow for a new meeting to be started on first participant join.
844
                // It requires the host to start the meeting first.
845
                // Therefore for global meetings we must make the first participant the host
846
                // that is use start_url rather than join_url.
847
                // the participant will not be registered and will appear as the Zoom user account owner.
848
                // For course and user meetings, only the host can start the meeting.
849
                if ($meeting->isGlobalMeeting() && $this->get('enableGlobalConference')
850
                    || $meeting->getUser() === api_get_user_entity(api_get_user_id())) {
851
                    return $meeting->getMeetingInfoGet()->start_url;
852
                }
853
                break;
854
            case 'started':
855
                if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {
856
                    // the participant must be registered
857
                    $participant = api_get_user_entity(api_get_user_id());
858
                    $registrant = $meeting->getRegistrant($participant);
859
                    if (!is_null($registrant)) {
860
                        // the participant is registered
861
                        return $registrant->getCreatedRegistration()->join_url;
862
                    }
863
                    // the participant is not registered, he can join only the global meeting (automatic registration)
864
                    if ($meeting->isGlobalMeeting() && $this->get('enableGlobalConference')) {
865
                        return $this->registerUser($meeting, $participant)->getCreatedRegistration()->join_url;
866
                    }
867
                }
868
                break;
869
        }
870
871
        return null;
872
    }
873
874
    /**
875
     * Update local recording list from remote Zoom server's version.
876
     * Kept to implement a future administration button ("import existing data from zoom server").
877
     *
878
     * @param DateTime $startDate
879
     * @param DateTime $endDate
880
     *
881
     * @throws OptimisticLockException
882
     * @throws Exception
883
     */
884
    public function reloadPeriodRecordings($startDate, $endDate)
885
    {
886
        foreach (RecordingList::loadPeriodRecordings($startDate, $endDate) as $recordingMeeting) {
887
            $recordingEntity = $this->getRecordingRepository()->find($recordingMeeting->uuid);
888
            if (is_null($recordingEntity)) {
889
                $recordingEntity = new RecordingEntity();
890
                $meetingEntity = $this->getMeetingRepository()->find($recordingMeeting->id);
891
                if (is_null($meetingEntity)) {
892
                    try {
893
                        $meetingInfoGet = MeetingInfoGet::fromId($recordingMeeting->id);
894
                    } catch (Exception $exception) {
895
                        $meetingInfoGet = null; // deleted meeting with recordings
896
                    }
897
                    if (!is_null($meetingInfoGet)) {
898
                        $meetingEntity = $this->createMeetingFromMeetingEntity(
899
                            (new MeetingEntity())->setMeetingInfoGet($meetingInfoGet)
900
                        );
901
                        Database::getManager()->persist($meetingEntity);
902
                    }
903
                }
904
                if (!is_null($meetingEntity)) {
905
                    $recordingEntity->setMeeting($meetingEntity);
906
                }
907
            }
908
            $recordingEntity->setRecordingMeeting($recordingMeeting);
909
            Database::getManager()->persist($recordingEntity);
910
        }
911
        Database::getManager()->flush();
912
    }
913
914
    /**
915
     * Creates a meeting on Zoom servers and stores it in the local database.
916
     *
917
     * @param MeetingEntity $meeting a new, unsaved meeting with at least a type and a topic
918
     *
919
     * @throws Exception
920
     *
921
     * @return MeetingEntity
922
     */
923
    private function createMeetingFromMeetingEntity($meeting)
924
    {
925
        $approvalType = $meeting->getMeetingInfoGet()->settings->approval_type;
926
        $meeting->getMeetingInfoGet()->settings->auto_recording = 'true' === $this->get('enableCloudRecording')
927
            ? 'cloud'
928
            : 'local';
929
        $meeting->getMeetingInfoGet()->settings->registrants_email_notification = false;
930
        $meeting->setMeetingInfoGet($meeting->getMeetingInfoGet()->create());
931
932
        $meeting->getMeetingInfoGet()->settings->approval_type = $approvalType;
933
934
        Database::getManager()->persist($meeting);
935
        Database::getManager()->flush();
936
937
        return $meeting;
938
    }
939
940
    /**
941
     * @throws Exception
942
     *
943
     * @return MeetingEntity
944
     */
945
    private function createGlobalMeeting()
946
    {
947
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType(
948
            $this->get_lang('GlobalMeeting'),
949
            MeetingInfoGet::TYPE_SCHEDULED
950
        );
951
        $meetingInfoGet->start_time = (new DateTime())->format(DateTimeInterface::ISO8601);
952
        $meetingInfoGet->duration = 60;
953
        $meetingInfoGet->settings->approval_type =
954
            ('true' === $this->get('enableParticipantRegistration'))
955
                ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE
956
                : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
957
        // $meetingInfoGet->settings->host_video = true;
958
        $meetingInfoGet->settings->participant_video = true;
959
        $meetingInfoGet->settings->join_before_host = true;
960
        $meetingInfoGet->settings->registrants_email_notification = false;
961
962
        return $this->createMeetingFromMeetingEntity((new MeetingEntity())->setMeetingInfoGet($meetingInfoGet));
963
    }
964
965
    /**
966
     * Schedules a meeting and returns it.
967
     * set $course, $session and $user to null in order to create a global meeting.
968
     *
969
     * @param User|null    $user      the current user, for a course meeting or a user meeting
970
     * @param Course|null  $course    the course, for a course meeting
971
     * @param Session|null $session   the session, for a course meeting
972
     * @param DateTime     $startTime meeting local start date-time (configure local timezone on your Zoom account)
973
     * @param int          $duration  in minutes
974
     * @param string       $topic     short title of the meeting, required
975
     * @param string       $agenda    ordre du jour
976
     * @param string       $password  meeting password
977
     *
978
     * @throws Exception
979
     *
980
     * @return MeetingEntity meeting
981
     */
982
    private function scheduleMeeting($user, $course, $session, $startTime, $duration, $topic, $agenda, $password)
983
    {
984
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED);
985
        $meetingInfoGet->duration = $duration;
986
        $meetingInfoGet->start_time = $startTime->format(DateTimeInterface::ISO8601);
987
        $meetingInfoGet->agenda = $agenda;
988
        $meetingInfoGet->password = $password;
989
        $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
990
        if ('true' === $this->get('enableParticipantRegistration')) {
991
            $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
992
        }
993
994
        return $this->createMeetingFromMeetingEntity(
995
            (new MeetingEntity())
996
                ->setMeetingInfoGet($meetingInfoGet)
997
                ->setUser($user)
998
                ->setCourse($course)
999
                ->setSession($session)
1000
        );
1001
    }
1002
1003
    /**
1004
     * Starts a new instant meeting and redirects to its start url.
1005
     *
1006
     * @param string       $topic
1007
     * @param User|null    $user
1008
     * @param Course|null  $course
1009
     * @param Session|null $session
1010
     *
1011
     * @throws Exception
1012
     */
1013
    private function startInstantMeeting($topic, $user = null, $course = null, $session = null)
1014
    {
1015
        $meeting = $this->createMeetingFromMeetingEntity(
1016
            (new MeetingEntity())
1017
                ->setMeetingInfoGet(MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_INSTANT))
1018
                ->setUser($user)
1019
                ->setCourse($course)
1020
                ->setSession($session)
1021
        );
1022
        api_location($meeting->getMeetingInfoGet()->start_url);
1023
    }
1024
1025
    /**
1026
     * @param MeetingEntity $meetingEntity
1027
     * @param User          $user
1028
     * @param bool          $andFlush
1029
     *
1030
     * @throws OptimisticLockException
1031
     * @throws Exception
1032
     *
1033
     * @return RegistrantEntity
1034
     */
1035
    private function registerUser($meetingEntity, $user, $andFlush = true)
1036
    {
1037
        if (empty($user->getEmail())) {
1038
            throw new Exception($this->get_lang('CannotRegisterWithoutEmailAddress'));
1039
        }
1040
        $meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName(
1041
            $user->getEmail(),
1042
            $user->getFirstname(),
1043
            $user->getLastname()
1044
        );
1045
1046
        $registrantEntity = (new RegistrantEntity())
1047
            ->setMeeting($meetingEntity)
1048
            ->setUser($user)
1049
            ->setMeetingRegistrant($meetingRegistrant)
1050
            ->setCreatedRegistration($meetingEntity->getMeetingInfoGet()->addRegistrant($meetingRegistrant));
1051
        Database::getManager()->persist($registrantEntity);
1052
        if ($andFlush) {
1053
            Database::getManager()->flush($registrantEntity);
1054
        }
1055
1056
        return $registrantEntity;
1057
    }
1058
1059
    /**
1060
     * Register users to a meeting.
1061
     *
1062
     * @param MeetingEntity $meetingEntity
1063
     * @param User[]        $users
1064
     *
1065
     * @throws OptimisticLockException
1066
     *
1067
     * @return User[] failed registrations [ user id => errorMessage ]
1068
     */
1069
    private function registerUsers($meetingEntity, $users)
1070
    {
1071
        $failedUsers = [];
1072
        foreach ($users as $user) {
1073
            try {
1074
                $this->registerUser($meetingEntity, $user, false);
1075
            } catch (Exception $exception) {
1076
                $failedUsers[$user->getId()] = $exception->getMessage();
1077
            }
1078
        }
1079
        Database::getManager()->flush();
1080
1081
        return $failedUsers;
1082
    }
1083
1084
    /**
1085
     * Registers all the course users to a course meeting.
1086
     *
1087
     * @param MeetingEntity $meetingEntity
1088
     *
1089
     * @throws OptimisticLockException
1090
     */
1091
    private function registerAllCourseUsers($meetingEntity)
1092
    {
1093
        $this->registerUsers($meetingEntity, $meetingEntity->getRegistrableUsers());
1094
    }
1095
1096
    /**
1097
     * Removes registrants from a meeting.
1098
     *
1099
     * @param MeetingEntity      $meetingEntity
1100
     * @param RegistrantEntity[] $registrants
1101
     *
1102
     * @throws Exception
1103
     */
1104
    private function unregister($meetingEntity, $registrants)
1105
    {
1106
        $meetingRegistrants = [];
1107
        foreach ($registrants as $registrant) {
1108
            $meetingRegistrants[] = $registrant->getMeetingRegistrant();
1109
        }
1110
        $meetingEntity->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
1111
        foreach ($registrants as $registrant) {
1112
            Database::getManager()->remove($registrant);
1113
        }
1114
        Database::getManager()->flush();
1115
    }
1116
1117
    /**
1118
     * Updates meeting registrants list. Adds the missing registrants and removes the extra.
1119
     *
1120
     * @param MeetingEntity $meetingEntity
1121
     * @param User[]        $users         list of users to be registered
1122
     *
1123
     * @throws Exception
1124
     */
1125
    private function updateRegistrantList($meetingEntity, $users)
1126
    {
1127
        $usersToAdd = [];
1128
        foreach ($users as $user) {
1129
            $found = false;
1130
            foreach ($meetingEntity->getRegistrants() as $registrant) {
1131
                if ($registrant->getUser() === $user) {
1132
                    $found = true;
1133
                    break;
1134
                }
1135
            }
1136
            if (!$found) {
1137
                $usersToAdd[] = $user;
1138
            }
1139
        }
1140
        $registrantsToRemove = [];
1141
        foreach ($meetingEntity->getRegistrants() as $registrant) {
1142
            $found = false;
1143
            foreach ($users as $user) {
1144
                if ($registrant->getUser() === $user) {
1145
                    $found = true;
1146
                    break;
1147
                }
1148
            }
1149
            if (!$found) {
1150
                $registrantsToRemove[] = $registrant;
1151
            }
1152
        }
1153
        $this->registerUsers($meetingEntity, $usersToAdd);
1154
        $this->unregister($meetingEntity, $registrantsToRemove);
1155
    }
1156
1157
    public function getToolbar($returnUrl = '')
1158
    {
1159
        if (!api_is_platform_admin()) {
1160
            return '';
1161
        }
1162
        $back = '';
1163
        if (!empty($returnUrl)) {
1164
            $back = Display::url(
1165
                Display::return_icon('back.png', get_lang('Back'), null, ICON_SIZE_MEDIUM),
1166
                $returnUrl
1167
            );
1168
        }
1169
1170
        $actionsLeft =
1171
            Display::url(
1172
            Display::return_icon('settings.png', get_lang('Settings'), null, ICON_SIZE_MEDIUM),
1173
            api_get_path(WEB_CODE_PATH).'admin/configure_plugin.php?name=zoom'
1174
            ).$back
1175
        ;
1176
1177
        return Display::toolbarAction('toolbar', [$actionsLeft]);
1178
    }
1179
}
1180