Passed
Push — 1.11.x ( 30a845...b02ac1 )
by Julito
10:23
created

ZoomPlugin::userIsConferenceManager()   B

Complexity

Conditions 8
Paths 16

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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