Passed
Push — 1.11.x ( 97ce99...30a845 )
by Julito
13:32
created

ZoomPlugin::scheduleMeeting()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

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