Passed
Pull Request — 1.11.x (#4160)
by Angel Fernando Quiroz
10:39
created

ZoomPlugin::userIsCourseConferenceManager()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1189
        }
1190
1191
        return null;
1192
    }
1193
1194
    /**
1195
     * @param Meeting $meeting
1196
     *
1197
     * @return bool whether the logged-in user can manage conferences in this context, that is either
1198
     *              the current course or session coach, the platform admin or the current course admin
1199
     */
1200
    public function userIsConferenceManager($meeting)
1201
    {
1202
        if (null === $meeting) {
1203
            return false;
1204
        }
1205
1206
        if (api_is_coach() || api_is_platform_admin()) {
1207
            return true;
1208
        }
1209
1210
        if ($meeting->isCourseMeeting() && api_get_course_id() && api_is_course_admin()) {
1211
            return true;
1212
        }
1213
1214
        return $meeting->isUserMeeting() && $meeting->getUser()->getId() == api_get_user_id();
1215
    }
1216
1217
    /**
1218
     * @return bool whether the logged-in user can manage conferences in this context, that is either
1219
     *              the current course or session coach, the platform admin or the current course admin
1220
     */
1221
    public function userIsCourseConferenceManager()
1222
    {
1223
        if (api_is_coach() || api_is_platform_admin()) {
1224
            return true;
1225
        }
1226
1227
        if (api_get_course_id() && api_is_course_admin()) {
1228
            return true;
1229
        }
1230
1231
        return false;
1232
    }
1233
1234
    /**
1235
     * Update local recording list from remote Zoom server's version.
1236
     * Kept to implement a future administration button ("import existing data from zoom server").
1237
     *
1238
     * @param DateTime $startDate
1239
     * @param DateTime $endDate
1240
     *
1241
     * @throws OptimisticLockException
1242
     * @throws Exception
1243
     */
1244
    public function reloadPeriodRecordings($startDate, $endDate)
1245
    {
1246
        $em = Database::getManager();
1247
        $recordingRepo = $this->getRecordingRepository();
1248
        $meetingRepo = $this->getMeetingRepository();
1249
        $recordings = RecordingList::loadPeriodRecordings($startDate, $endDate);
1250
1251
        foreach ($recordings as $recordingMeeting) {
1252
            $recordingEntity = $recordingRepo->findOneBy(['uuid' => $recordingMeeting->uuid]);
1253
            if (null === $recordingEntity) {
1254
                $recordingEntity = new Recording();
1255
                $meeting = $meetingRepo->findOneBy(['meetingId' => $recordingMeeting->id]);
1256
                if (null === $meeting) {
1257
                    try {
1258
                        $meetingInfoGet = MeetingInfoGet::fromId($recordingMeeting->id);
1259
                    } catch (Exception $exception) {
1260
                        $meetingInfoGet = null; // deleted meeting with recordings
1261
                    }
1262
                    if (null !== $meetingInfoGet) {
1263
                        $meeting = $this->createMeetingFromMeeting(
1264
                            (new Meeting())->setMeetingInfoGet($meetingInfoGet)
1265
                        );
1266
                        $em->persist($meeting);
1267
                    }
1268
                }
1269
                if (null !== $meeting) {
1270
                    $recordingEntity->setMeeting($meeting);
1271
                }
1272
            }
1273
            $recordingEntity->setRecordingMeeting($recordingMeeting);
1274
            $em->persist($recordingEntity);
1275
        }
1276
        $em->flush();
1277
    }
1278
1279
    /**
1280
     * @return RecordingRepository|EntityRepository
1281
     */
1282
    public static function getRecordingRepository()
1283
    {
1284
        return Database::getManager()->getRepository(Recording::class);
1285
    }
1286
1287
    public function getToolbar($returnUrl = '')
1288
    {
1289
        if (!api_is_platform_admin()) {
1290
            return '';
1291
        }
1292
1293
        $actionsLeft = '';
1294
        $back = '';
1295
        $courseId = api_get_course_id();
1296
        if (empty($courseId)) {
1297
            $actionsLeft .=
1298
                Display::url(
1299
                    Display::return_icon('bbb.png', $this->get_lang('Meetings'), null, ICON_SIZE_MEDIUM),
1300
                    api_get_path(WEB_PLUGIN_PATH).'zoom/meetings.php'
1301
                );
1302
        } else {
1303
            $actionsLeft .=
1304
                Display::url(
1305
                    Display::return_icon('bbb.png', $this->get_lang('Meetings'), null, ICON_SIZE_MEDIUM),
1306
                    api_get_path(WEB_PLUGIN_PATH).'zoom/start.php?'.api_get_cidreq()
1307
                );
1308
        }
1309
1310
        if (!empty($returnUrl)) {
1311
            $back = Display::url(
1312
                Display::return_icon('back.png', get_lang('Back'), null, ICON_SIZE_MEDIUM),
1313
                $returnUrl
1314
            );
1315
        }
1316
1317
        if (api_is_platform_admin()) {
1318
            $actionsLeft .= Display::url(
1319
                Display::return_icon('agenda.png', get_lang('Calendar'), [], ICON_SIZE_MEDIUM),
1320
                'calendar.php'
1321
            );
1322
            $actionsLeft .=
1323
                Display::url(
1324
                    Display::return_icon('settings.png', get_lang('Settings'), null, ICON_SIZE_MEDIUM),
1325
                    api_get_path(WEB_CODE_PATH).'admin/configure_plugin.php?name=zoom'
1326
                ).$back;
1327
        }
1328
1329
        return Display::toolbarAction('toolbar', [$actionsLeft]);
1330
    }
1331
1332
    public function getRecordingSetting()
1333
    {
1334
        $recording = (string) $this->get('enableCloudRecording');
1335
1336
        if (in_array($recording, [self::RECORDING_TYPE_LOCAL, self::RECORDING_TYPE_CLOUD], true)) {
1337
            return $recording;
1338
        }
1339
1340
        return self::RECORDING_TYPE_NONE;
1341
    }
1342
1343
    public function hasRecordingAvailable()
1344
    {
1345
        $recording = $this->getRecordingSetting();
1346
1347
        return self::RECORDING_TYPE_NONE !== $recording;
1348
    }
1349
1350
    /**
1351
     * @throws Exception
1352
     */
1353
    public function createWebinarFromSchema(Webinar $webinar, WebinarSchema $schema): Webinar
1354
    {
1355
        $currentUser = api_get_user_entity(api_get_user_id());
1356
1357
        $schema->settings->contact_email = $currentUser->getEmail();
1358
        $schema->settings->contact_name = $currentUser->getFullname();
1359
        $schema->settings->auto_recording = $this->getRecordingSetting();
1360
        $schema->settings->registrants_email_notification = false;
1361
        $schema->settings->attendees_and_panelists_reminder_email_notification->enable = false;
1362
        $schema->settings->follow_up_attendees_email_notification->enable = false;
1363
        $schema->settings->follow_up_absentees_email_notification->enable = false;
1364
1365
        $schema = $schema->create($webinar->getAccountEmail());
1366
1367
        $webinar->setWebinarSchema($schema);
1368
1369
        $em = Database::getManager();
1370
        $em->persist($webinar);
1371
        $em->flush();
1372
1373
        return $webinar;
1374
    }
1375
1376
    public function getAccountEmails(): array
1377
    {
1378
        $currentValue = $this->get('accountSelector');
1379
1380
        if (empty($currentValue)) {
1381
            return [];
1382
        }
1383
1384
        $emails = explode(';', $currentValue);
1385
        $trimmed = array_map('trim', $emails);
1386
        $filtered = array_filter($trimmed);
1387
1388
        return array_combine($filtered, $filtered);
1389
    }
1390
1391
    /**
1392
     * Updates meeting registrants list. Adds the missing registrants and removes the extra.
1393
     *
1394
     * @param Meeting $meeting
1395
     * @param User[]  $users   list of users to be registered
1396
     *
1397
     * @throws Exception
1398
     */
1399
    private function updateRegistrantList($meeting, $users)
1400
    {
1401
        $usersToAdd = [];
1402
        foreach ($users as $user) {
1403
            $found = false;
1404
            foreach ($meeting->getRegistrants() as $registrant) {
1405
                if ($registrant->getUser() === $user) {
1406
                    $found = true;
1407
                    break;
1408
                }
1409
            }
1410
            if (!$found) {
1411
                $usersToAdd[] = $user;
1412
            }
1413
        }
1414
        $registrantsToRemove = [];
1415
        foreach ($meeting->getRegistrants() as $registrant) {
1416
            $found = false;
1417
            foreach ($users as $user) {
1418
                if ($registrant->getUser() === $user) {
1419
                    $found = true;
1420
                    break;
1421
                }
1422
            }
1423
            if (!$found) {
1424
                $registrantsToRemove[] = $registrant;
1425
            }
1426
        }
1427
        $this->registerUsers($meeting, $usersToAdd);
1428
        $this->unregister($meeting, $registrantsToRemove);
1429
    }
1430
1431
    /**
1432
     * Register users to a meeting.
1433
     *
1434
     * @param Meeting $meeting
1435
     * @param User[]  $users
1436
     *
1437
     * @throws OptimisticLockException
1438
     *
1439
     * @return User[] failed registrations [ user id => errorMessage ]
1440
     */
1441
    private function registerUsers($meeting, $users)
1442
    {
1443
        $failedUsers = [];
1444
        foreach ($users as $user) {
1445
            try {
1446
                $this->registerUser($meeting, $user, false);
1447
            } catch (Exception $exception) {
1448
                $failedUsers[$user->getId()] = $exception->getMessage();
1449
            }
1450
        }
1451
        Database::getManager()->flush();
1452
1453
        return $failedUsers;
1454
    }
1455
1456
    /**
1457
     * @throws Exception
1458
     * @throws OptimisticLockException
1459
     *
1460
     * @return Registrant
1461
     */
1462
    private function registerUser(Meeting $meeting, User $user, $andFlush = true)
1463
    {
1464
        if (empty($user->getEmail())) {
1465
            throw new Exception($this->get_lang('CannotRegisterWithoutEmailAddress'));
1466
        }
1467
1468
        $meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName(
1469
            $user->getEmail(),
1470
            $user->getFirstname(),
1471
            $user->getLastname()
1472
        );
1473
1474
        $registrantEntity = (new Registrant())
1475
            ->setMeeting($meeting)
1476
            ->setUser($user)
1477
            ->setMeetingRegistrant($meetingRegistrant)
1478
            ->setCreatedRegistration($meeting->getMeetingInfoGet()->addRegistrant($meetingRegistrant));
1479
        Database::getManager()->persist($registrantEntity);
1480
1481
        if ($andFlush) {
1482
            Database::getManager()->flush($registrantEntity);
1483
        }
1484
1485
        return $registrantEntity;
1486
    }
1487
1488
    /**
1489
     * Removes registrants from a meeting.
1490
     *
1491
     * @param Meeting      $meeting
1492
     * @param Registrant[] $registrants
1493
     *
1494
     * @throws Exception
1495
     */
1496
    private function unregister($meeting, $registrants)
1497
    {
1498
        $meetingRegistrants = [];
1499
        foreach ($registrants as $registrant) {
1500
            $meetingRegistrants[] = $registrant->getMeetingRegistrant();
1501
        }
1502
        $meeting->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
1503
        $em = Database::getManager();
1504
        foreach ($registrants as $registrant) {
1505
            $em->remove($registrant);
1506
        }
1507
        $em->flush();
1508
    }
1509
1510
    /**
1511
     * Starts a new instant meeting and redirects to its start url.
1512
     *
1513
     * @param string          $topic
1514
     * @param User|null       $user
1515
     * @param Course|null     $course
1516
     * @param CGroupInfo|null $group
1517
     * @param Session|null    $session
1518
     *
1519
     * @throws Exception
1520
     */
1521
    private function startInstantMeeting($topic, $user = null, $course = null, $group = null, $session = null)
1522
    {
1523
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_INSTANT);
1524
        //$meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
1525
        $meeting = $this->createMeetingFromMeeting(
1526
            (new Meeting())
1527
                ->setMeetingInfoGet($meetingInfoGet)
1528
                ->setUser($user)
1529
                ->setGroup($group)
1530
                ->setCourse($course)
1531
                ->setSession($session)
1532
        );
1533
        api_location($meeting->getMeetingInfoGet()->start_url);
1534
    }
1535
1536
    /**
1537
     * Creates a meeting on Zoom servers and stores it in the local database.
1538
     *
1539
     * @param Meeting $meeting a new, unsaved meeting with at least a type and a topic
1540
     *
1541
     * @throws Exception
1542
     *
1543
     * @return Meeting
1544
     */
1545
    private function createMeetingFromMeeting($meeting)
1546
    {
1547
        $currentUser = api_get_user_entity(api_get_user_id());
1548
1549
        $meeting->getMeetingInfoGet()->settings->contact_email = $currentUser->getEmail();
1550
        $meeting->getMeetingInfoGet()->settings->contact_name = $currentUser->getFullname();
1551
        $meeting->getMeetingInfoGet()->settings->auto_recording = $this->getRecordingSetting();
1552
        $meeting->getMeetingInfoGet()->settings->registrants_email_notification = false;
1553
1554
        //$meeting->getMeetingInfoGet()->host_email = $currentUser->getEmail();
1555
        //$meeting->getMeetingInfoGet()->settings->alternative_hosts = $currentUser->getEmail();
1556
1557
        // Send create to Zoom.
1558
        $meeting->setMeetingInfoGet(
1559
            $meeting->getMeetingInfoGet()->create(
1560
                $meeting->getAccountEmail()
1561
            )
1562
        );
1563
1564
        Database::getManager()->persist($meeting);
1565
        Database::getManager()->flush();
1566
1567
        return $meeting;
1568
    }
1569
1570
    /**
1571
     * @throws Exception
1572
     *
1573
     * @return Meeting
1574
     */
1575
    private function createGlobalMeeting()
1576
    {
1577
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType(
1578
            $this->get_lang('GlobalMeeting'),
1579
            MeetingInfoGet::TYPE_SCHEDULED
1580
        );
1581
        $meetingInfoGet->start_time = (new DateTime())->format(DATE_ATOM);
1582
        $meetingInfoGet->duration = 60;
1583
        $meetingInfoGet->settings->approval_type =
1584
            ('true' === $this->get('enableParticipantRegistration'))
1585
                ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE
1586
                : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
1587
        // $meetingInfoGet->settings->host_video = true;
1588
        $meetingInfoGet->settings->participant_video = true;
1589
        $meetingInfoGet->settings->join_before_host = true;
1590
        $meetingInfoGet->settings->registrants_email_notification = false;
1591
1592
        return $this->createMeetingFromMeeting((new Meeting())->setMeetingInfoGet($meetingInfoGet));
1593
    }
1594
1595
    /**
1596
     * Schedules a meeting and returns it.
1597
     * set $course, $session and $user to null in order to create a global meeting.
1598
     *
1599
     * @param DateTime $startTime meeting local start date-time (configure local timezone on your Zoom account)
1600
     * @param int      $duration  in minutes
1601
     * @param string   $topic     short title of the meeting, required
1602
     * @param string   $agenda    ordre du jour
1603
     * @param string   $password  meeting password
1604
     *
1605
     * @throws Exception
1606
     *
1607
     * @return Meeting meeting
1608
     */
1609
    private function createScheduleMeeting(
1610
        User $user = null,
1611
        Course $course = null,
1612
        CGroupInfo $group = null,
1613
        Session $session = null,
1614
        $startTime,
1615
        $duration,
1616
        $topic,
1617
        $agenda,
1618
        $password,
1619
        string $accountEmail = null
1620
    ) {
1621
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED);
1622
        $meetingInfoGet->duration = $duration;
1623
        $meetingInfoGet->start_time = $startTime->format(DATE_ATOM);
1624
        $meetingInfoGet->agenda = $agenda;
1625
        $meetingInfoGet->password = $password;
1626
        $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
1627
        if ('true' === $this->get('enableParticipantRegistration')) {
1628
            $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
1629
        }
1630
1631
        return $this->createMeetingFromMeeting(
1632
            (new Meeting())
1633
                ->setMeetingInfoGet($meetingInfoGet)
1634
                ->setUser($user)
1635
                ->setCourse($course)
1636
                ->setGroup($group)
1637
                ->setSession($session)
1638
                ->setAccountEmail($accountEmail)
1639
        );
1640
    }
1641
1642
    /**
1643
     * @throws Exception
1644
     */
1645
    private function createScheduleWebinar(
1646
        ?User $user,
1647
        ?Course $course,
1648
        ?CGroupInfo $group,
1649
        ?Session $session,
1650
        DateTime $startTime,
1651
        $duration,
1652
        $topic,
1653
        $agenda,
1654
        $password,
1655
        string $accountEmail = null
1656
    ): Webinar {
1657
        $webinarSchema = WebinarSchema::fromTopicAndType($topic);
1658
        $webinarSchema->duration = $duration;
1659
        $webinarSchema->start_time = $startTime->format(DATE_ATOM);
1660
        $webinarSchema->agenda = $agenda;
1661
        $webinarSchema->password = $password;
1662
1663
        if ('true' === $this->get('enableParticipantRegistration')) {
1664
            $webinarSchema->settings->approval_type = WebinarSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
1665
        }
1666
1667
        $webinar = (new Webinar())
1668
            ->setUser($user)
1669
            ->setCourse($course)
1670
            ->setGroup($group)
1671
            ->setSession($session)
1672
            ->setAccountEmail($accountEmail)
1673
        ;
1674
1675
        return $this->createWebinarFromSchema($webinar, $webinarSchema);
1676
    }
1677
1678
    /**
1679
     * Registers all the course users to a course meeting.
1680
     *
1681
     * @param Meeting $meeting
1682
     *
1683
     * @throws OptimisticLockException
1684
     */
1685
    private function registerAllCourseUsers($meeting)
1686
    {
1687
        $this->registerUsers($meeting, $meeting->getRegistrableUsers());
1688
    }
1689
}
1690