Passed
Push — 1.11.x ( 0611ec...e69264 )
by Julito
13:55
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
     *
354
     * @throws Exception
355
     *
356
     * @return FormValidator
357
     */
358
    public function getDeleteMeetingForm($meetingEntity, $returnURL)
359
    {
360
        $form = new FormValidator('delete', 'post', Security::remove_XSS($_SERVER['REQUEST_URI']));
361
        $form->addButtonDelete($this->get_lang('DeleteMeeting'));
362
        if ($form->validate()) {
363
            $this->deleteMeeting($meetingEntity, $returnURL);
364
        }
365
366
        return $form;
367
    }
368
369
    /**
370
     * @param MeetingEntity $meetingEntity
371
     * @param string        $returnURL
372
     *
373
     * @return false
374
     */
375
    public function deleteMeeting($meetingEntity, $returnURL)
376
    {
377
        if (null === $meetingEntity) {
378
            return false;
379
        }
380
381
        try {
382
            $meetingEntity->getMeetingInfoGet()->delete();
383
            Database::getManager()->remove($meetingEntity);
384
            Database::getManager()->flush();
385
386
            Display::addFlash(
387
                Display::return_message($this->get_lang('MeetingDeleted'), 'confirm')
388
            );
389
            api_location($returnURL);
390
        } catch (Exception $exception) {
391
            Display::addFlash(
392
                Display::return_message($exception->getMessage(), 'error')
393
            );
394
        }
395
    }
396
397
    /**
398
     * Generates a registrant list update form listing course and session users.
399
     * Updates the list on validation.
400
     *
401
     * @param MeetingEntity $meetingEntity
402
     *
403
     * @throws Exception
404
     *
405
     * @return FormValidator
406
     */
407
    public function getRegisterParticipantForm($meetingEntity)
408
    {
409
        $form = new FormValidator('register', 'post', $_SERVER['REQUEST_URI']);
410
        $userIdSelect = $form->addSelect('userIds', $this->get_lang('RegisteredUsers'));
411
        $userIdSelect->setMultiple(true);
412
        $form->addButtonSend($this->get_lang('UpdateRegisteredUserList'));
413
414
        $users = $meetingEntity->getRegistrableUsers();
415
        foreach ($users as $user) {
416
            $userIdSelect->addOption(
417
                api_get_person_name($user->getFirstname(), $user->getLastname()),
418
                $user->getId()
419
            );
420
        }
421
422
        if ($form->validate()) {
423
            $selectedUserIds = $form->getSubmitValue('userIds');
424
            $selectedUsers = [];
425
            foreach ($users as $user) {
426
                if (in_array($user->getId(), $selectedUserIds)) {
427
                    $selectedUsers[] = $user;
428
                }
429
            }
430
            try {
431
                $this->updateRegistrantList($meetingEntity, $selectedUsers);
432
                Display::addFlash(
433
                    Display::return_message($this->get_lang('RegisteredUserListWasUpdated'), 'confirm')
434
                );
435
            } catch (Exception $exception) {
436
                Display::addFlash(
437
                    Display::return_message($exception->getMessage(), 'error')
438
                );
439
            }
440
        }
441
        $registeredUserIds = [];
442
        foreach ($meetingEntity->getRegistrants() as $registrant) {
443
            $registeredUserIds[] = $registrant->getUser()->getId();
444
        }
445
        $userIdSelect->setSelected($registeredUserIds);
446
447
        return $form;
448
    }
449
450
    /**
451
     * Generates a meeting recording files management form.
452
     * Takes action on validation.
453
     *
454
     * @param MeetingEntity $meeting
455
     *
456
     * @throws Exception
457
     *
458
     * @return FormValidator
459
     */
460
    public function getFileForm($meeting)
461
    {
462
        $form = new FormValidator('fileForm', 'post', $_SERVER['REQUEST_URI']);
463
        if (!$meeting->getRecordings()->isEmpty()) {
464
            $fileIdSelect = $form->addSelect('fileIds', get_lang('Files'));
465
            $fileIdSelect->setMultiple(true);
466
            foreach ($meeting->getRecordings() as &$recording) {
467
                // $recording->instanceDetails = $plugin->getPastMeetingInstanceDetails($instance->uuid);
468
                $options = [];
469
                foreach ($recording->getRecordingMeeting()->recording_files as $file) {
470
                    $options[] = [
471
                        'text' => sprintf(
472
                            '%s.%s (%s)',
473
                            $file->recording_type,
474
                            $file->file_type,
475
                            //$file->formattedFileSize
476
                            $file->file_size
477
                        ),
478
                        'value' => $file->id,
479
                    ];
480
                }
481
                $fileIdSelect->addOptGroup(
482
                    $options,
483
                    sprintf("%s (%s)", $recording->formattedStartTime, $recording->formattedDuration)
484
                );
485
            }
486
            $actions = [];
487
            if ($meeting->isCourseMeeting()) {
488
                $actions['CreateLinkInCourse'] = $this->get_lang('CreateLinkInCourse');
489
                $actions['CopyToCourse'] = $this->get_lang('CopyToCourse');
490
            }
491
            $actions['DeleteFile'] = $this->get_lang('DeleteFile');
492
            $form->addRadio(
493
                'action',
494
                get_lang('Action'),
495
                $actions
496
            );
497
            $form->addButtonUpdate($this->get_lang('DoIt'));
498
            if ($form->validate()) {
499
                foreach ($meeting->getRecordings() as $recording) {
500
                    foreach ($recording->files as $file) {
501
                        if (in_array($file->id, $form->getSubmitValue('fileIds'))) {
502
                            $name = sprintf(
503
                                $this->get_lang('XRecordingOfMeetingXFromXDurationXDotX'),
504
                                $file->recording_type,
505
                                $meeting->getId(),
506
                                $recording->formattedStartTime,
507
                                $recording->formattedDuration,
508
                                $file->file_type
509
                            );
510
                            $action = $form->getSubmitValue('action');
511
                            if ('CreateLinkInCourse' === $action && $meeting->isCourseMeeting()) {
512
                                try {
513
                                    $this->createLinkToFileInCourse($meeting, $file, $name);
514
                                    Display::addFlash(
515
                                        Display::return_message($this->get_lang('LinkToFileWasCreatedInCourse'), 'success')
516
                                    );
517
                                } catch (Exception $exception) {
518
                                    Display::addFlash(
519
                                        Display::return_message($exception->getMessage(), 'error')
520
                                    );
521
                                }
522
                            } elseif ('CopyToCourse' === $action && $meeting->isCourseMeeting()) {
523
                                try {
524
                                    $this->copyFileToCourse($meeting, $file, $name);
525
                                    Display::addFlash(
526
                                        Display::return_message($this->get_lang('FileWasCopiedToCourse'), 'confirm')
527
                                    );
528
                                } catch (Exception $exception) {
529
                                    Display::addFlash(
530
                                        Display::return_message($exception->getMessage(), 'error')
531
                                    );
532
                                }
533
                            } elseif ('DeleteFile' === $action) {
534
                                try {
535
                                    $file->delete();
536
                                    Display::addFlash(
537
                                        Display::return_message($this->get_lang('FileWasDeleted'), 'confirm')
538
                                    );
539
                                } catch (Exception $exception) {
540
                                    Display::addFlash(
541
                                        Display::return_message($exception->getMessage(), 'error')
542
                                    );
543
                                }
544
                            }
545
                        }
546
                    }
547
                }
548
            }
549
        }
550
551
        return $form;
552
    }
553
554
    /**
555
     * Generates a form to fast and easily create and start an instant meeting.
556
     * On validation, create it then redirect to it and exit.
557
     *
558
     * @param User    $user
559
     * @param Course  $course
560
     * @param Session $session
561
     *
562
     * @return FormValidator
563
     */
564
    public function getCreateInstantMeetingForm($user, $course, $session)
565
    {
566
        $form = new FormValidator('createInstantMeetingForm', 'post', '', '_blank');
567
        $form->addButton('startButton', $this->get_lang('StartInstantMeeting'), 'video-camera', 'primary');
568
        if ($form->validate()) {
569
            try {
570
                $this->startInstantMeeting($this->get_lang('InstantMeeting'), $user, $course, $session);
571
            } catch (Exception $exception) {
572
                Display::addFlash(
573
                    Display::return_message($exception->getMessage(), 'error')
574
                );
575
            }
576
        }
577
578
        return $form;
579
    }
580
581
    /**
582
     * Generates a form to schedule a meeting.
583
     * On validation, creates it and redirects to its page.
584
     *
585
     * @param User|null    $user
586
     * @param Course|null  $course
587
     * @param Session|null $session
588
     *
589
     * @throws Exception
590
     *
591
     * @return FormValidator
592
     */
593
    public function getScheduleMeetingForm($user, $course = null, $session = null)
594
    {
595
        $form = new FormValidator('scheduleMeetingForm');
596
        $form->addHeader($this->get_lang('ScheduleTheMeeting'));
597
        $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
598
        $form->setRequired($startTimeDatePicker);
599
600
        $form->addText('topic', $this->get_lang('Topic'), true);
601
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
602
603
        $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
604
        $form->setRequired($durationNumeric);
605
606
        // $passwordText = $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
607
        if (null !== $course) {
608
            $registrationOptions = [
609
                'RegisterAllCourseUsers' => $this->get_lang('RegisterAllCourseUsers'),
610
            ];
611
            $groups = GroupManager::get_groups();
612
            if (!empty($groups)) {
613
                $registrationOptions['RegisterTheseGroupMembers'] = get_lang('RegisterTheseGroupMembers');
614
            }
615
            $registrationOptions['RegisterNoUser'] = $this->get_lang('RegisterNoUser');
616
            $userRegistrationRadio = $form->addRadio(
617
                'userRegistration',
618
                $this->get_lang('UserRegistration'),
619
                $registrationOptions
620
            );
621
            $groupOptions = [];
622
            foreach ($groups as $group) {
623
                $groupOptions[$group['id']] = $group['name'];
624
            }
625
            $groupIdsSelect = $form->addSelect(
626
                'groupIds',
627
                $this->get_lang('RegisterTheseGroupMembers'),
628
                $groupOptions
629
            );
630
            $groupIdsSelect->setMultiple(true);
631
            if (!empty($groups)) {
632
                $jsCode = sprintf(
633
                    "getElementById('%s').parentNode.parentNode.parentNode.style.display = getElementById('%s').checked ? 'block' : 'none'",
634
                    $groupIdsSelect->getAttribute('id'),
635
                    $userRegistrationRadio->getelements()[1]->getAttribute('id')
636
                );
637
638
                $form->setAttribute('onchange', $jsCode);
639
            }
640
        }
641
        $form->addButtonCreate(get_lang('Save'));
642
643
        if ($form->validate()) {
644
            try {
645
                $newMeeting = $this->scheduleMeeting(
646
                    $user,
647
                    $course,
648
                    $session,
649
                    new DateTime($form->getSubmitValue('startTime')),
650
                    $form->getSubmitValue('duration'),
651
                    $form->getSubmitValue('topic'),
652
                    $form->getSubmitValue('agenda'),
653
                    ''
654
                );
655
                Display::addFlash(
656
                    Display::return_message($this->get_lang('NewMeetingCreated'))
657
                );
658
                if ($newMeeting->isCourseMeeting()) {
659
                    if ('RegisterAllCourseUsers' === $form->getSubmitValue('userRegistration')) {
660
                        $this->registerAllCourseUsers($newMeeting);
661
                        Display::addFlash(
662
                            Display::return_message($this->get_lang('AllCourseUsersWereRegistered'))
663
                        );
664
                    } elseif ('RegisterTheseGroupMembers' === $form->getSubmitValue('userRegistration')) {
665
                        $userIds = [];
666
                        foreach ($form->getSubmitValue('groupIds') as $groupId) {
667
                            $userIds = array_unique(array_merge($userIds, GroupManager::get_users($groupId)));
668
                        }
669
                        $users = Database::getManager()->getRepository('ChamiloUserBundle:User')->findBy(
670
                            ['id' => $userIds]
671
                        );
672
                        $this->registerUsers($newMeeting, $users);
673
                        Display::addFlash(
674
                            Display::return_message($this->get_lang('GroupUsersWereRegistered'))
675
                        );
676
                    }
677
                    api_location('meeting_from_start.php?meetingId='.$newMeeting->getId());
678
                } elseif (!is_null($user)) {
679
                    api_location('meeting_from_user.php?meetingId='.$newMeeting->getId());
680
                }
681
            } catch (Exception $exception) {
682
                Display::addFlash(
683
                    Display::return_message($exception->getMessage(), 'error')
684
                );
685
            }
686
        } else {
687
            $form->setDefaults(
688
                [
689
                    'duration' => 60,
690
                    'userRegistration' => 'RegisterAllCourseUsers',
691
                ]
692
            );
693
        }
694
695
        return $form;
696
    }
697
698
    /**
699
     * @param MeetingEntity $meetingEntity
700
     *
701
     * @return bool whether the logged-in user can manage conferences in this context, that is either
702
     *              the current course or session coach, the platform admin or the current course admin
703
     */
704
    public function userIsConferenceManager($meetingEntity)
705
    {
706
        if (null === $meetingEntity) {
707
            return false;
708
        }
709
710
        return api_is_coach()
711
            || api_is_platform_admin()
712
            || $meetingEntity->isCourseMeeting() && api_get_course_id() && api_is_course_admin()
713
            || $meetingEntity->isUserMeeting() && $meetingEntity->getUser()->getId() == api_get_user_id();
714
    }
715
716
    /**
717
     * @param Course $course
718
     *
719
     * @return bool whether the logged-in user can manage conferences in this context, that is either
720
     *              the current course or session coach, the platform admin or the current course admin
721
     */
722
    public function userIsCourseConferenceManager($course)
723
    {
724
        return api_is_coach()
725
            || api_is_platform_admin()
726
            || api_get_course_id() && api_is_course_admin();
727
    }
728
729
    /**
730
     * Adds to the meeting course documents a link to a meeting instance recording file.
731
     *
732
     * @param MeetingEntity $meeting
733
     * @param RecordingFile $file
734
     * @param string        $name
735
     *
736
     * @throws Exception
737
     */
738
    public function createLinkToFileInCourse($meeting, $file, $name)
739
    {
740
        $course = $meeting->getCourse();
741
        if (is_null($course)) {
742
            throw new Exception('This meeting is not linked to a course');
743
        }
744
        $courseInfo = api_get_course_info_by_id($course->getId());
745
        if (empty($courseInfo)) {
746
            throw new Exception('This meeting is not linked to a valid course');
747
        }
748
        $path = '/zoom_meeting_recording_file_'.$file->id.'.'.$file->file_type;
749
        $docId = DocumentManager::addCloudLink($courseInfo, $path, $file->play_url, $name);
750
        if (!$docId) {
751
            throw new Exception(get_lang(DocumentManager::cloudLinkExists($courseInfo, $path, $file->play_url) ? 'UrlAlreadyExists' : 'ErrorAddCloudLink'));
752
        }
753
    }
754
755
    /**
756
     * Copies a recording file to a meeting's course.
757
     *
758
     * @param MeetingEntity $meeting
759
     * @param RecordingFile $file
760
     * @param string        $name
761
     *
762
     * @throws Exception
763
     */
764
    public function copyFileToCourse($meeting, $file, $name)
765
    {
766
        $course = $meeting->getCourse();
767
        if (is_null($course)) {
768
            throw new Exception('This meeting is not linked to a course');
769
        }
770
        $courseInfo = api_get_course_info_by_id($course->getId());
771
        if (empty($courseInfo)) {
772
            throw new Exception('This meeting is not linked to a valid course');
773
        }
774
        $tmpFile = tmpfile();
775
        if (false === $tmpFile) {
776
            throw new Exception('tmpfile() returned false');
777
        }
778
        $curl = curl_init($file->getFullDownloadURL($this->jwtClient->token));
779
        if (false === $curl) {
780
            throw new Exception('Could not init curl: '.curl_error($curl));
781
        }
782
        if (!curl_setopt_array(
783
            $curl,
784
            [
785
                CURLOPT_FILE => $tmpFile,
786
                CURLOPT_FOLLOWLOCATION => true,
787
                CURLOPT_MAXREDIRS => 10,
788
                CURLOPT_TIMEOUT => 120,
789
            ]
790
        )) {
791
            throw new Exception("Could not set curl options: ".curl_error($curl));
792
        }
793
        if (false === curl_exec($curl)) {
794
            throw new Exception("curl_exec failed: ".curl_error($curl));
795
        }
796
        $newPath = handle_uploaded_document(
797
            $courseInfo,
798
            [
799
                'name' => $name,
800
                'tmp_name' => stream_get_meta_data($tmpFile)['uri'],
801
                'size' => filesize(stream_get_meta_data($tmpFile)['uri']),
802
                'from_file' => true,
803
                'type' => $file->file_type,
804
            ],
805
            '/',
806
            api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document',
807
            api_get_user_id(),
808
            0,
809
            null,
810
            0,
811
            '',
812
            true,
813
            false,
814
            null,
815
            $meeting->getSession()->getId(),
816
            true
817
        );
818
        fclose($tmpFile);
819
        if (false === $newPath) {
820
            throw new Exception('could not handle uploaded document');
821
        }
822
    }
823
824
    /**
825
     * Return the current global meeting (create it if needed).
826
     *
827
     * @throws Exception
828
     *
829
     * @return string
830
     */
831
    public function getGlobalMeeting()
832
    {
833
        foreach ($this->getMeetingRepository()->unfinishedGlobalMeetings() as $meeting) {
834
            return $meeting;
835
        }
836
837
        return $this->createGlobalMeeting();
838
    }
839
840
    /**
841
     * Returns the URL to enter (start or join) a meeting or null if not possible to enter the meeting,
842
     * The returned URL depends on the meeting current status (waiting, started or finished) and the current user.
843
     *
844
     * @param MeetingEntity $meeting
845
     *
846
     * @throws Exception
847
     * @throws OptimisticLockException
848
     *
849
     * @return string|null
850
     */
851
    public function getStartOrJoinMeetingURL($meeting)
852
    {
853
        $status = $meeting->getMeetingInfoGet()->status;
854
855
        switch ($status) {
856
            case 'waiting':
857
                // Zoom does not allow for a new meeting to be started on first participant join.
858
                // It requires the host to start the meeting first.
859
                // Therefore for global meetings we must make the first participant the host
860
                // that is use start_url rather than join_url.
861
                // the participant will not be registered and will appear as the Zoom user account owner.
862
                // For course and user meetings, only the host can start the meeting.
863
                if ($meeting->isGlobalMeeting() && $this->get('enableGlobalConference')
864
                    || $meeting->getUser() === api_get_user_entity(api_get_user_id())) {
865
                    return $meeting->getMeetingInfoGet()->start_url;
866
                }
867
                break;
868
            case 'started':
869
                if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {
870
                    // the participant must be registered
871
                    $participant = api_get_user_entity(api_get_user_id());
872
                    $registrant = $meeting->getRegistrant($participant);
873
                    if (null != $registrant) {
874
                        // the participant is registered
875
                        return $registrant->getCreatedRegistration()->join_url;
876
                    }
877
                    // the participant is not registered, he can join only the global meeting (automatic registration)
878
                    if ($meeting->isGlobalMeeting() && $this->get('enableGlobalConference')) {
879
                        return $this->registerUser($meeting, $participant)->getCreatedRegistration()->join_url;
880
                    }
881
                }
882
                break;
883
        }
884
885
        return null;
886
    }
887
888
    /**
889
     * Update local recording list from remote Zoom server's version.
890
     * Kept to implement a future administration button ("import existing data from zoom server").
891
     *
892
     * @param DateTime $startDate
893
     * @param DateTime $endDate
894
     *
895
     * @throws OptimisticLockException
896
     * @throws Exception
897
     */
898
    public function reloadPeriodRecordings($startDate, $endDate)
899
    {
900
        foreach (RecordingList::loadPeriodRecordings($startDate, $endDate) as $recordingMeeting) {
901
            $recordingEntity = $this->getRecordingRepository()->find($recordingMeeting->uuid);
902
            if (is_null($recordingEntity)) {
903
                $recordingEntity = new RecordingEntity();
904
                $meetingEntity = $this->getMeetingRepository()->find($recordingMeeting->id);
905
                if (is_null($meetingEntity)) {
906
                    try {
907
                        $meetingInfoGet = MeetingInfoGet::fromId($recordingMeeting->id);
908
                    } catch (Exception $exception) {
909
                        $meetingInfoGet = null; // deleted meeting with recordings
910
                    }
911
                    if (!is_null($meetingInfoGet)) {
912
                        $meetingEntity = $this->createMeetingFromMeetingEntity(
913
                            (new MeetingEntity())->setMeetingInfoGet($meetingInfoGet)
914
                        );
915
                        Database::getManager()->persist($meetingEntity);
916
                    }
917
                }
918
                if (!is_null($meetingEntity)) {
919
                    $recordingEntity->setMeeting($meetingEntity);
920
                }
921
            }
922
            $recordingEntity->setRecordingMeeting($recordingMeeting);
923
            Database::getManager()->persist($recordingEntity);
924
        }
925
        Database::getManager()->flush();
926
    }
927
928
    public function getToolbar($returnUrl = '')
929
    {
930
        if (!api_is_platform_admin()) {
931
            return '';
932
        }
933
934
        $actionsLeft = '';
935
        $back = '';
936
937
        if ('true' === api_get_plugin_setting('zoom', 'enableGlobalConference')) {
938
            $actionsLeft .=
939
                Display::url(
940
                    Display::return_icon('links.png', $this->get_lang('GlobalMeeting'), null, ICON_SIZE_MEDIUM),
941
                    api_get_path(WEB_PLUGIN_PATH).'zoom/admin.php'
942
                )
943
            ;
944
        }
945
946
        if ('true' === api_get_plugin_setting('zoom', 'enableGlobalConferencePerUser')) {
947
            $actionsLeft .=
948
                Display::url(
949
                    Display::return_icon('user.png', $this->get_lang('GlobalMeetingPerUser'), null, ICON_SIZE_MEDIUM),
950
                    api_get_path(WEB_PLUGIN_PATH).'zoom/user.php'
951
                )
952
            ;
953
        }
954
955
        if (!empty($returnUrl)) {
956
            $back = Display::url(
957
                Display::return_icon('back.png', get_lang('Back'), null, ICON_SIZE_MEDIUM),
958
                $returnUrl
959
            );
960
        }
961
962
        $actionsLeft .=
963
            Display::url(
964
            Display::return_icon('settings.png', get_lang('Settings'), null, ICON_SIZE_MEDIUM),
965
            api_get_path(WEB_CODE_PATH).'admin/configure_plugin.php?name=zoom'
966
            ).$back
967
        ;
968
969
        return Display::toolbarAction('toolbar', [$actionsLeft]);
970
    }
971
972
    /**
973
     * Creates a meeting on Zoom servers and stores it in the local database.
974
     *
975
     * @param MeetingEntity $meeting a new, unsaved meeting with at least a type and a topic
976
     *
977
     * @throws Exception
978
     *
979
     * @return MeetingEntity
980
     */
981
    private function createMeetingFromMeetingEntity($meeting)
982
    {
983
        $approvalType = $meeting->getMeetingInfoGet()->settings->approval_type;
984
        $meeting->getMeetingInfoGet()->settings->auto_recording = 'true' === $this->get('enableCloudRecording')
985
            ? 'cloud'
986
            : 'local';
987
        $meeting->getMeetingInfoGet()->settings->registrants_email_notification = false;
988
        $meeting->setMeetingInfoGet($meeting->getMeetingInfoGet()->create());
989
990
        $meeting->getMeetingInfoGet()->settings->approval_type = $approvalType;
991
992
        Database::getManager()->persist($meeting);
993
        Database::getManager()->flush();
994
995
        return $meeting;
996
    }
997
998
    /**
999
     * @throws Exception
1000
     *
1001
     * @return MeetingEntity
1002
     */
1003
    private function createGlobalMeeting()
1004
    {
1005
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType(
1006
            $this->get_lang('GlobalMeeting'),
1007
            MeetingInfoGet::TYPE_SCHEDULED
1008
        );
1009
        $meetingInfoGet->start_time = (new DateTime())->format(DateTimeInterface::ISO8601);
1010
        $meetingInfoGet->duration = 60;
1011
        $meetingInfoGet->settings->approval_type =
1012
            ('true' === $this->get('enableParticipantRegistration'))
1013
                ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE
1014
                : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
1015
        // $meetingInfoGet->settings->host_video = true;
1016
        $meetingInfoGet->settings->participant_video = true;
1017
        $meetingInfoGet->settings->join_before_host = true;
1018
        $meetingInfoGet->settings->registrants_email_notification = false;
1019
1020
        return $this->createMeetingFromMeetingEntity((new MeetingEntity())->setMeetingInfoGet($meetingInfoGet));
1021
    }
1022
1023
    /**
1024
     * Schedules a meeting and returns it.
1025
     * set $course, $session and $user to null in order to create a global meeting.
1026
     *
1027
     * @param User|null    $user      the current user, for a course meeting or a user meeting
1028
     * @param Course|null  $course    the course, for a course meeting
1029
     * @param Session|null $session   the session, for a course meeting
1030
     * @param DateTime     $startTime meeting local start date-time (configure local timezone on your Zoom account)
1031
     * @param int          $duration  in minutes
1032
     * @param string       $topic     short title of the meeting, required
1033
     * @param string       $agenda    ordre du jour
1034
     * @param string       $password  meeting password
1035
     *
1036
     * @throws Exception
1037
     *
1038
     * @return MeetingEntity meeting
1039
     */
1040
    private function scheduleMeeting($user, $course, $session, $startTime, $duration, $topic, $agenda, $password)
1041
    {
1042
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED);
1043
        $meetingInfoGet->duration = $duration;
1044
        $meetingInfoGet->start_time = $startTime->format(DateTimeInterface::ISO8601);
1045
        $meetingInfoGet->agenda = $agenda;
1046
        $meetingInfoGet->password = $password;
1047
        $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
1048
        if ('true' === $this->get('enableParticipantRegistration')) {
1049
            $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
1050
        }
1051
1052
        return $this->createMeetingFromMeetingEntity(
1053
            (new MeetingEntity())
1054
                ->setMeetingInfoGet($meetingInfoGet)
1055
                ->setUser($user)
1056
                ->setCourse($course)
1057
                ->setSession($session)
1058
        );
1059
    }
1060
1061
    /**
1062
     * Starts a new instant meeting and redirects to its start url.
1063
     *
1064
     * @param string       $topic
1065
     * @param User|null    $user
1066
     * @param Course|null  $course
1067
     * @param Session|null $session
1068
     *
1069
     * @throws Exception
1070
     */
1071
    private function startInstantMeeting($topic, $user = null, $course = null, $session = null)
1072
    {
1073
        $meeting = $this->createMeetingFromMeetingEntity(
1074
            (new MeetingEntity())
1075
                ->setMeetingInfoGet(MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_INSTANT))
1076
                ->setUser($user)
1077
                ->setCourse($course)
1078
                ->setSession($session)
1079
        );
1080
        api_location($meeting->getMeetingInfoGet()->start_url);
1081
    }
1082
1083
    /**
1084
     * @param MeetingEntity $meetingEntity
1085
     * @param User          $user
1086
     * @param bool          $andFlush
1087
     *
1088
     * @throws OptimisticLockException
1089
     * @throws Exception
1090
     *
1091
     * @return RegistrantEntity
1092
     */
1093
    private function registerUser($meetingEntity, $user, $andFlush = true)
1094
    {
1095
        if (empty($user->getEmail())) {
1096
            throw new Exception($this->get_lang('CannotRegisterWithoutEmailAddress'));
1097
        }
1098
        $meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName(
1099
            $user->getEmail(),
1100
            $user->getFirstname(),
1101
            $user->getLastname()
1102
        );
1103
1104
        $registrantEntity = (new RegistrantEntity())
1105
            ->setMeeting($meetingEntity)
1106
            ->setUser($user)
1107
            ->setMeetingRegistrant($meetingRegistrant)
1108
            ->setCreatedRegistration($meetingEntity->getMeetingInfoGet()->addRegistrant($meetingRegistrant));
1109
        Database::getManager()->persist($registrantEntity);
1110
        if ($andFlush) {
1111
            Database::getManager()->flush($registrantEntity);
1112
        }
1113
1114
        return $registrantEntity;
1115
    }
1116
1117
    /**
1118
     * Register users to a meeting.
1119
     *
1120
     * @param MeetingEntity $meetingEntity
1121
     * @param User[]        $users
1122
     *
1123
     * @throws OptimisticLockException
1124
     *
1125
     * @return User[] failed registrations [ user id => errorMessage ]
1126
     */
1127
    private function registerUsers($meetingEntity, $users)
1128
    {
1129
        $failedUsers = [];
1130
        foreach ($users as $user) {
1131
            try {
1132
                $this->registerUser($meetingEntity, $user, false);
1133
            } catch (Exception $exception) {
1134
                $failedUsers[$user->getId()] = $exception->getMessage();
1135
            }
1136
        }
1137
        Database::getManager()->flush();
1138
1139
        return $failedUsers;
1140
    }
1141
1142
    /**
1143
     * Registers all the course users to a course meeting.
1144
     *
1145
     * @param MeetingEntity $meetingEntity
1146
     *
1147
     * @throws OptimisticLockException
1148
     */
1149
    private function registerAllCourseUsers($meetingEntity)
1150
    {
1151
        $this->registerUsers($meetingEntity, $meetingEntity->getRegistrableUsers());
1152
    }
1153
1154
    /**
1155
     * Removes registrants from a meeting.
1156
     *
1157
     * @param MeetingEntity      $meetingEntity
1158
     * @param RegistrantEntity[] $registrants
1159
     *
1160
     * @throws Exception
1161
     */
1162
    private function unregister($meetingEntity, $registrants)
1163
    {
1164
        $meetingRegistrants = [];
1165
        foreach ($registrants as $registrant) {
1166
            $meetingRegistrants[] = $registrant->getMeetingRegistrant();
1167
        }
1168
        $meetingEntity->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
1169
        foreach ($registrants as $registrant) {
1170
            Database::getManager()->remove($registrant);
1171
        }
1172
        Database::getManager()->flush();
1173
    }
1174
1175
    /**
1176
     * Updates meeting registrants list. Adds the missing registrants and removes the extra.
1177
     *
1178
     * @param MeetingEntity $meetingEntity
1179
     * @param User[]        $users         list of users to be registered
1180
     *
1181
     * @throws Exception
1182
     */
1183
    private function updateRegistrantList($meetingEntity, $users)
1184
    {
1185
        $usersToAdd = [];
1186
        foreach ($users as $user) {
1187
            $found = false;
1188
            foreach ($meetingEntity->getRegistrants() as $registrant) {
1189
                if ($registrant->getUser() === $user) {
1190
                    $found = true;
1191
                    break;
1192
                }
1193
            }
1194
            if (!$found) {
1195
                $usersToAdd[] = $user;
1196
            }
1197
        }
1198
        $registrantsToRemove = [];
1199
        foreach ($meetingEntity->getRegistrants() as $registrant) {
1200
            $found = false;
1201
            foreach ($users as $user) {
1202
                if ($registrant->getUser() === $user) {
1203
                    $found = true;
1204
                    break;
1205
                }
1206
            }
1207
            if (!$found) {
1208
                $registrantsToRemove[] = $registrant;
1209
            }
1210
        }
1211
        $this->registerUsers($meetingEntity, $usersToAdd);
1212
        $this->unregister($meetingEntity, $registrantsToRemove);
1213
    }
1214
}
1215