Passed
Pull Request — 1.11.x (#4160)
by Angel Fernando Quiroz
16:08 queued 03:42
created

ZoomPlugin::createGlobalMeeting()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 2
nop 0
dl 0
loc 18
rs 9.8333
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\WebinarRegistrantSchema;
15
use Chamilo\PluginBundle\Zoom\API\WebinarSchema;
16
use Chamilo\PluginBundle\Zoom\API\WebinarSettings;
17
use Chamilo\PluginBundle\Zoom\Meeting;
18
use Chamilo\PluginBundle\Zoom\MeetingActivity;
19
use Chamilo\PluginBundle\Zoom\MeetingRepository;
20
use Chamilo\PluginBundle\Zoom\Recording;
21
use Chamilo\PluginBundle\Zoom\RecordingRepository;
22
use Chamilo\PluginBundle\Zoom\Registrant;
23
use Chamilo\PluginBundle\Zoom\RegistrantRepository;
24
use Chamilo\PluginBundle\Zoom\Webinar;
25
use Chamilo\UserBundle\Entity\User;
26
use Doctrine\ORM\EntityRepository;
27
use Doctrine\ORM\OptimisticLockException;
28
use Doctrine\ORM\Tools\SchemaTool;
29
use Doctrine\ORM\Tools\ToolsException;
30
31
/**
32
 * Class ZoomPlugin. Integrates Zoom meetings in courses.
33
 */
34
class ZoomPlugin extends Plugin
35
{
36
    const RECORDING_TYPE_CLOUD = 'cloud';
37
    const RECORDING_TYPE_LOCAL = 'local';
38
    const RECORDING_TYPE_NONE = 'none';
39
    public $isCoursePlugin = true;
40
41
    /**
42
     * @var JWTClient
43
     */
44
    private $jwtClient;
45
46
    /**
47
     * ZoomPlugin constructor.
48
     * {@inheritdoc}
49
     * Initializes the API JWT client and the entity repositories.
50
     */
51
    public function __construct()
52
    {
53
        parent::__construct(
54
            '0.4',
55
            'Sébastien Ducoulombier, Julio Montoya',
56
            [
57
                'tool_enable' => 'boolean',
58
                'apiKey' => 'text',
59
                'apiSecret' => 'text',
60
                'verificationToken' => 'text',
61
                'enableParticipantRegistration' => 'boolean',
62
                'enableCloudRecording' => [
63
                    'type' => 'select',
64
                    'options' => [
65
                        self::RECORDING_TYPE_CLOUD => 'Cloud',
66
                        self::RECORDING_TYPE_LOCAL => 'Local',
67
                        self::RECORDING_TYPE_NONE => get_lang('None'),
68
                    ],
69
                ],
70
                'enableGlobalConference' => 'boolean',
71
                'globalConferenceAllowRoles' => [
72
                    'type' => 'select',
73
                    'options' => [
74
                        PLATFORM_ADMIN => get_lang('Administrator'),
75
                        COURSEMANAGER => get_lang('Teacher'),
76
                        STUDENT => get_lang('Student'),
77
                        STUDENT_BOSS => get_lang('StudentBoss'),
78
                    ],
79
                    'attributes' => ['multiple' => 'multiple'],
80
                ],
81
                'accountSelector' => 'text',
82
            ]
83
        );
84
85
        $this->isAdminPlugin = true;
86
        $this->jwtClient = new JWTClient($this->get('apiKey'), $this->get('apiSecret'));
87
    }
88
89
    /**
90
     * Caches and returns an instance of this class.
91
     *
92
     * @return ZoomPlugin the instance to use
93
     */
94
    public static function create()
95
    {
96
        static $instance = null;
97
98
        return $instance ? $instance : $instance = new self();
99
    }
100
101
    /**
102
     * @return bool
103
     */
104
    public static function currentUserCanJoinGlobalMeeting()
105
    {
106
        $user = api_get_user_entity(api_get_user_id());
107
108
        if (null === $user) {
109
            return false;
110
        }
111
112
        //return 'true' === api_get_plugin_setting('zoom', 'enableGlobalConference') && api_user_is_login();
113
        return
114
            'true' === api_get_plugin_setting('zoom', 'enableGlobalConference')
115
            && in_array(
116
                (api_is_platform_admin() ? PLATFORM_ADMIN : $user->getStatus()),
117
                (array) api_get_plugin_setting('zoom', 'globalConferenceAllowRoles')
118
            );
119
    }
120
121
    /**
122
     * @return array
123
     */
124
    public function getProfileBlockItems()
125
    {
126
        $elements = $this->meetingsToWhichCurrentUserIsRegisteredComingSoon();
127
        $addMeetingLink = false;
128
        if (self::currentUserCanJoinGlobalMeeting()) {
129
            $addMeetingLink = true;
130
        }
131
132
        if ($addMeetingLink) {
133
            $elements[$this->get_lang('Meetings')] = api_get_path(WEB_PLUGIN_PATH).'zoom/meetings.php';
134
        }
135
136
        $items = [];
137
        foreach ($elements as $title => $link) {
138
            $items[] = [
139
                'class' => 'video-conference',
140
                'icon' => Display::return_icon(
141
                    'bbb.png',
142
                    get_lang('VideoConference')
143
                ),
144
                'link' => $link,
145
                'title' => $title,
146
            ];
147
        }
148
149
        return $items;
150
    }
151
152
    /**
153
     * @return array [ $title => $link ]
154
     */
155
    public function meetingsToWhichCurrentUserIsRegisteredComingSoon()
156
    {
157
        $linkTemplate = api_get_path(WEB_PLUGIN_PATH).'zoom/join_meeting.php?meetingId=%s';
158
        $user = api_get_user_entity(api_get_user_id());
159
        $meetings = self::getRegistrantRepository()->meetingsComingSoonRegistrationsForUser($user);
160
        $items = [];
161
        foreach ($meetings as $registrant) {
162
            $meeting = $registrant->getMeeting();
163
            $items[sprintf(
164
                $this->get_lang('DateMeetingTitle'),
165
                $meeting->formattedStartTime,
166
                $meeting->getMeetingInfoGet()->topic
167
            )] = sprintf($linkTemplate, $meeting->getId());
168
        }
169
170
        return $items;
171
    }
172
173
    /**
174
     * @return RegistrantRepository|EntityRepository
175
     */
176
    public static function getRegistrantRepository()
177
    {
178
        return Database::getManager()->getRepository(Registrant::class);
179
    }
180
181
    /**
182
     * Creates this plugin's related tables in the internal database.
183
     * Installs course fields in all courses.
184
     *
185
     * @throws ToolsException
186
     */
187
    public function install()
188
    {
189
        $schemaManager = Database::getManager()->getConnection()->getSchemaManager();
190
191
        $tablesExists = $schemaManager->tablesExist(
192
            [
193
                'plugin_zoom_meeting',
194
                'plugin_zoom_meeting_activity',
195
                'plugin_zoom_recording',
196
                'plugin_zoom_registrant',
197
            ]
198
        );
199
200
        if ($tablesExists) {
201
            return;
202
        }
203
204
        (new SchemaTool(Database::getManager()))->createSchema(
205
            [
206
                Database::getManager()->getClassMetadata(Meeting::class),
207
                Database::getManager()->getClassMetadata(Webinar::class),
208
                Database::getManager()->getClassMetadata(MeetingActivity::class),
209
                Database::getManager()->getClassMetadata(Recording::class),
210
                Database::getManager()->getClassMetadata(Registrant::class),
211
            ]
212
        );
213
214
        // Copy icons into the main/img/icons folder
215
        $iconName = 'zoom_meet';
216
        $iconsList = [
217
            '64/'.$iconName.'.png',
218
            '64/'.$iconName.'_na.png',
219
            '32/'.$iconName.'.png',
220
            '32/'.$iconName.'_na.png',
221
            '22/'.$iconName.'.png',
222
            '22/'.$iconName.'_na.png',
223
        ];
224
        $sourceDir = api_get_path(SYS_PLUGIN_PATH).'zoom/resources/img/';
225
        $destinationDir = api_get_path(SYS_CODE_PATH).'img/icons/';
226
        foreach ($iconsList as $icon) {
227
            $src = $sourceDir.$icon;
228
            $dest = $destinationDir.$icon;
229
            copy($src, $dest);
230
        }
231
232
        $this->install_course_fields_in_all_courses(true, 'zoom_meet.png');
233
    }
234
235
    /**
236
     * Drops this plugins' related tables from the internal database.
237
     * Uninstalls course fields in all courses().
238
     */
239
    public function uninstall()
240
    {
241
        (new SchemaTool(Database::getManager()))->dropSchema(
242
            [
243
                Database::getManager()->getClassMetadata(Meeting::class),
244
                Database::getManager()->getClassMetadata(Webinar::class),
245
                Database::getManager()->getClassMetadata(MeetingActivity::class),
246
                Database::getManager()->getClassMetadata(Recording::class),
247
                Database::getManager()->getClassMetadata(Registrant::class),
248
            ]
249
        );
250
        $this->uninstall_course_fields_in_all_courses();
251
252
        // Remove icons from the main/img/icons folder
253
        $iconName = 'zoom_meet';
254
        $iconsList = [
255
            '64/'.$iconName.'.png',
256
            '64/'.$iconName.'_na.png',
257
            '32/'.$iconName.'.png',
258
            '32/'.$iconName.'_na.png',
259
            '22/'.$iconName.'.png',
260
            '22/'.$iconName.'_na.png',
261
        ];
262
        $destinationDir = api_get_path(SYS_CODE_PATH).'img/icons/';
263
        foreach ($iconsList as $icon) {
264
            $dest = $destinationDir.$icon;
265
            if (is_file($dest)) {
266
                @unlink($dest);
267
            }
268
        }
269
    }
270
271
    /**
272
     * Generates the search form to include in the meeting list administration page.
273
     * The form has DatePickers 'start' and 'end' and Checkbox 'reloadRecordingLists'.
274
     *
275
     * @return FormValidator the form
276
     */
277
    public function getAdminSearchForm()
278
    {
279
        $form = new FormValidator('search');
280
        $form->addHeader($this->get_lang('SearchMeeting'));
281
        $form->addDatePicker('start', get_lang('StartDate'));
282
        $form->addDatePicker('end', get_lang('EndDate'));
283
        $form->addButtonSearch(get_lang('Search'));
284
        $oneMonth = new DateInterval('P1M');
285
        if ($form->validate()) {
286
            try {
287
                $start = new DateTime($form->getSubmitValue('start'));
288
            } catch (Exception $exception) {
289
                $start = new DateTime();
290
                $start->sub($oneMonth);
291
            }
292
            try {
293
                $end = new DateTime($form->getSubmitValue('end'));
294
            } catch (Exception $exception) {
295
                $end = new DateTime();
296
                $end->add($oneMonth);
297
            }
298
        } else {
299
            $start = new DateTime();
300
            $start->sub($oneMonth);
301
            $end = new DateTime();
302
            $end->add($oneMonth);
303
        }
304
        try {
305
            $form->setDefaults(
306
                [
307
                    'start' => $start->format('Y-m-d'),
308
                    'end' => $end->format('Y-m-d'),
309
                ]
310
            );
311
        } catch (Exception $exception) {
312
            error_log(join(':', [__FILE__, __LINE__, $exception]));
313
        }
314
315
        return $form;
316
    }
317
318
    /**
319
     * Generates a meeting edit form and updates the meeting on validation.
320
     *
321
     * @param Meeting $meeting the meeting
322
     *
323
     * @throws Exception
324
     *
325
     * @return FormValidator
326
     */
327
    public function getEditMeetingForm($meeting)
328
    {
329
        $meetingInfoGet = $meeting->getMeetingInfoGet();
330
        $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
331
        $form->addHeader($this->get_lang('UpdateMeeting'));
332
        $form->addText('topic', $this->get_lang('Topic'));
333
        if ($meeting->requiresDateAndDuration()) {
334
            $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
335
            $form->setRequired($startTimeDatePicker);
336
            $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
337
            $form->setRequired($durationNumeric);
338
        }
339
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
340
        //$form->addLabel(get_lang('Password'), $meeting->getMeetingInfoGet()->password);
341
        // $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
342
        $form->addButtonUpdate(get_lang('Update'));
343
        if ($form->validate()) {
344
            if ($meeting->requiresDateAndDuration()) {
345
                $meetingInfoGet->start_time = (new DateTime($form->getSubmitValue('startTime')))->format(
346
                    DATE_ATOM
347
                );
348
                $meetingInfoGet->timezone = date_default_timezone_get();
349
                $meetingInfoGet->duration = (int) $form->getSubmitValue('duration');
350
            }
351
            $meetingInfoGet->topic = $form->getSubmitValue('topic');
352
            $meetingInfoGet->agenda = $form->getSubmitValue('agenda');
353
            try {
354
                $meetingInfoGet->update();
355
                $meeting->setMeetingInfoGet($meetingInfoGet);
356
                Database::getManager()->persist($meeting);
357
                Database::getManager()->flush();
358
                Display::addFlash(
359
                    Display::return_message($this->get_lang('MeetingUpdated'), 'confirm')
360
                );
361
            } catch (Exception $exception) {
362
                Display::addFlash(
363
                    Display::return_message($exception->getMessage(), 'error')
364
                );
365
            }
366
        }
367
        $defaults = [
368
            'topic' => $meetingInfoGet->topic,
369
            'agenda' => $meetingInfoGet->agenda,
370
        ];
371
        if ($meeting->requiresDateAndDuration()) {
372
            $defaults['startTime'] = $meeting->startDateTime->format('Y-m-d H:i');
373
            $defaults['duration'] = $meetingInfoGet->duration;
374
        }
375
        $form->setDefaults($defaults);
376
377
        return $form;
378
    }
379
380
    /**
381
     * @throws Exception
382
     */
383
    public function getEditWebinarForm(Webinar $webinar): FormValidator
384
    {
385
        $schema = $webinar->getWebinarSchema();
386
        $requiresDateAndDuration = $webinar->requiresDateAndDuration();
387
388
        $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
389
        $form->addHeader($this->get_lang('UpdateWebinar'));
390
        $form->addText('topic', $this->get_lang('Topic'));
391
392
        if ($requiresDateAndDuration) {
393
            $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
394
            $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
395
396
            $form->setRequired($startTimeDatePicker);
397
            $form->setRequired($durationNumeric);
398
        }
399
400
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
401
        $form->addButtonUpdate(get_lang('Update'));
402
403
        if ($form->validate()) {
404
            $formValues = $form->exportValues();
405
406
            if ($requiresDateAndDuration) {
407
                $schema->start_time = (new DateTime($formValues['startTime']))
408
                    ->format(DATE_ATOM);
409
                $schema->timezone = date_default_timezone_get();
410
                $schema->duration = (int) $formValues['duration'];
411
            }
412
413
            $schema->topic = $formValues['topic'];
414
            $schema->agenda = $formValues['agenda'];
415
416
            try {
417
                $schema->update();
418
                $webinar->setWebinarSchema($schema);
419
420
                $em = Database::getManager();
421
                $em->persist($webinar);
422
                $em->flush();
423
424
                Display::addFlash(
425
                    Display::return_message($this->get_lang('WebinarUpdated'), 'success')
426
                );
427
            } catch (Exception $exception) {
428
                Display::addFlash(
429
                    Display::return_message($exception->getMessage(), 'error')
430
                );
431
            }
432
        }
433
434
        $defaults = [
435
            'topic' => $schema->topic,
436
            'agenda' => $schema->agenda,
437
        ];
438
439
        if ($requiresDateAndDuration) {
440
            $defaults['startTime'] = $webinar->startDateTime->format('Y-m-d H:i');
441
            $defaults['duration'] = $schema->duration;
442
        }
443
444
        $form->setDefaults($defaults);
445
446
        return $form;
447
    }
448
449
    /**
450
     * Generates a meeting delete form and deletes the meeting on validation.
451
     *
452
     * @param Meeting $meeting
453
     * @param string  $returnURL where to redirect to on successful deletion
454
     *
455
     * @throws Exception
456
     *
457
     * @return FormValidator
458
     */
459
    public function getDeleteMeetingForm($meeting, $returnURL)
460
    {
461
        $id = $meeting->getMeetingId();
462
        $form = new FormValidator('delete', 'post', api_get_self().'?meetingId='.$id);
463
        $form->addButtonDelete($this->get_lang('DeleteMeeting'));
464
        if ($form->validate()) {
465
            $this->deleteMeeting($meeting, $returnURL);
466
        }
467
468
        return $form;
469
    }
470
471
    public function getDeleteWebinarForm(Webinar $webinar, string $returnURL): FormValidator
472
    {
473
        $id = $webinar->getMeetingId();
474
        $form = new FormValidator('delete', 'post', api_get_self()."?meetingId=$id");
475
        $form->addButtonDelete($this->get_lang('DeleteWebinar'));
476
477
        if ($form->validate()) {
478
            $this->deleteWebinar($webinar, $returnURL);
479
        }
480
481
        return $form;
482
    }
483
484
    /**
485
     * @param Meeting $meeting
486
     * @param string  $returnURL
487
     *
488
     * @return false
489
     */
490
    public function deleteMeeting($meeting, $returnURL)
491
    {
492
        if (null === $meeting) {
493
            return false;
494
        }
495
496
        $em = Database::getManager();
497
        try {
498
            // No need to delete a instant meeting.
499
            if (\Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT != $meeting->getMeetingInfoGet()->type) {
500
                $meeting->getMeetingInfoGet()->delete();
501
            }
502
503
            $em->remove($meeting);
504
            $em->flush();
505
506
            Display::addFlash(
507
                Display::return_message($this->get_lang('MeetingDeleted'), 'confirm')
508
            );
509
            api_location($returnURL);
510
        } catch (Exception $exception) {
511
            $this->handleException($exception);
512
        }
513
    }
514
515
    public function deleteWebinar(Webinar $webinar, string $returnURL)
516
    {
517
        $em = Database::getManager();
518
519
        try {
520
            $webinar->getWebinarSchema()->delete();
521
522
            $em->remove($webinar);
523
            $em->flush();
524
525
            Display::addFlash(
526
                Display::return_message($this->get_lang('WebinarDeleted'), 'success')
527
            );
528
529
            api_location($returnURL);
530
        } catch (Exception $exception) {
531
            $this->handleException($exception);
532
        }
533
    }
534
535
    /**
536
     * @param Exception $exception
537
     */
538
    public function handleException($exception)
539
    {
540
        if ($exception instanceof Exception) {
0 ignored issues
show
introduced by
$exception is always a sub-type of Exception.
Loading history...
541
            $error = json_decode($exception->getMessage());
542
            $message = $exception->getMessage();
543
            if ($error->message) {
544
                $message = $error->message;
545
            }
546
            Display::addFlash(
547
                Display::return_message($message, 'error')
548
            );
549
        }
550
    }
551
552
    /**
553
     * Generates a registrant list update form listing course and session users.
554
     * Updates the list on validation.
555
     *
556
     * @param Meeting $meeting
557
     *
558
     * @throws Exception
559
     *
560
     * @return FormValidator
561
     */
562
    public function getRegisterParticipantForm($meeting)
563
    {
564
        $form = new FormValidator('register', 'post', $_SERVER['REQUEST_URI']);
565
        $userIdSelect = $form->addSelect('userIds', $this->get_lang('RegisteredUsers'));
566
        $userIdSelect->setMultiple(true);
567
        $form->addButtonSend($this->get_lang('UpdateRegisteredUserList'));
568
569
        $selfRegistrationUrl = api_get_path(WEB_PLUGIN_PATH)
570
            .'zoom/subscription.php?meetingId='.$meeting->getMeetingId();
571
572
        $form->addHtml(
573
            '<div class="form-group"><div class="col-sm-8 col-sm-offset-2">
574
                <hr style="margin-top: 0;">
575
                <label for="frm-registration__txt-self-registration">'
576
            .$this->get_lang('UrlForSelfRegistration').'</label>
577
                <div class="input-group">
578
                    <input type="text" class="form-control" id="frm-registration__txt-self-registration" value="'
579
            .$selfRegistrationUrl.'">
580
                    <span class="input-group-btn">
581
                        <button class="btn btn-default" type="button"
582
                         onclick="copyTextToClipBoard(\'frm-registration__txt-self-registration\');">'
583
            .$this->get_lang('CopyTextToClipboard').'</button>
584
                    </span>
585
                </div>
586
            </div></div>'
587
        );
588
589
        $users = $meeting->getRegistrableUsers();
590
        foreach ($users as $user) {
591
            $userIdSelect->addOption(
592
                api_get_person_name($user->getFirstname(), $user->getLastname()),
593
                $user->getId()
594
            );
595
        }
596
597
        if ($form->validate()) {
598
            $selectedUserIds = $form->getSubmitValue('userIds');
599
            $selectedUsers = [];
600
            if (!empty($selectedUserIds)) {
601
                foreach ($users as $user) {
602
                    if (in_array($user->getId(), $selectedUserIds)) {
603
                        $selectedUsers[] = $user;
604
                    }
605
                }
606
            }
607
608
            try {
609
                $this->updateRegistrantList($meeting, $selectedUsers);
610
                Display::addFlash(
611
                    Display::return_message($this->get_lang('RegisteredUserListWasUpdated'), 'confirm')
612
                );
613
            } catch (Exception $exception) {
614
                Display::addFlash(
615
                    Display::return_message($exception->getMessage(), 'error')
616
                );
617
            }
618
        }
619
        $registeredUserIds = [];
620
        foreach ($meeting->getRegistrants() as $registrant) {
621
            $registeredUserIds[] = $registrant->getUser()->getId();
622
        }
623
        $userIdSelect->setSelected($registeredUserIds);
624
625
        return $form;
626
    }
627
628
    /**
629
     * Generates a meeting recording files management form.
630
     * Takes action on validation.
631
     *
632
     * @param Meeting $meeting
633
     *
634
     * @throws Exception
635
     *
636
     * @return FormValidator
637
     */
638
    public function getFileForm($meeting, $returnURL)
639
    {
640
        $form = new FormValidator('fileForm', 'post', $_SERVER['REQUEST_URI']);
641
        if (!$meeting->getRecordings()->isEmpty()) {
642
            $fileIdSelect = $form->addSelect('fileIds', get_lang('Files'));
643
            $fileIdSelect->setMultiple(true);
644
            $recordingList = $meeting->getRecordings();
645
            foreach ($recordingList as &$recording) {
646
                // $recording->instanceDetails = $plugin->getPastMeetingInstanceDetails($instance->uuid);
647
                $options = [];
648
                $recordings = $recording->getRecordingMeeting()->recording_files;
649
                foreach ($recordings as $file) {
650
                    $options[] = [
651
                        'text' => sprintf(
652
                            '%s.%s (%s)',
653
                            $file->recording_type,
654
                            $file->file_type,
655
                            $file->file_size
656
                        ),
657
                        'value' => $file->id,
658
                    ];
659
                }
660
                $fileIdSelect->addOptGroup(
661
                    $options,
662
                    sprintf("%s (%s)", $recording->formattedStartTime, $recording->formattedDuration)
663
                );
664
            }
665
            $actions = [];
666
            if ($meeting->isCourseMeeting()) {
667
                $actions['CreateLinkInCourse'] = $this->get_lang('CreateLinkInCourse');
668
                $actions['CopyToCourse'] = $this->get_lang('CopyToCourse');
669
            }
670
            $actions['DeleteFile'] = $this->get_lang('DeleteFile');
671
            $form->addRadio(
672
                'action',
673
                get_lang('Action'),
674
                $actions
675
            );
676
            $form->addButtonUpdate($this->get_lang('DoIt'));
677
            if ($form->validate()) {
678
                $action = $form->getSubmitValue('action');
679
                $idList = $form->getSubmitValue('fileIds');
680
681
                foreach ($recordingList as $recording) {
682
                    $recordings = $recording->getRecordingMeeting()->recording_files;
683
684
                    foreach ($recordings as $file) {
685
                        if (in_array($file->id, $idList)) {
686
                            $name = sprintf(
687
                                $this->get_lang('XRecordingOfMeetingXFromXDurationXDotX'),
688
                                $file->recording_type,
689
                                $meeting->getId(),
690
                                $recording->formattedStartTime,
691
                                $recording->formattedDuration,
692
                                $file->file_type
693
                            );
694
                            if ('CreateLinkInCourse' === $action && $meeting->isCourseMeeting()) {
695
                                try {
696
                                    $this->createLinkToFileInCourse($meeting, $file, $name);
697
                                    Display::addFlash(
698
                                        Display::return_message(
699
                                            $this->get_lang('LinkToFileWasCreatedInCourse'),
700
                                            'success'
701
                                        )
702
                                    );
703
                                } catch (Exception $exception) {
704
                                    Display::addFlash(
705
                                        Display::return_message($exception->getMessage(), 'error')
706
                                    );
707
                                }
708
                            } elseif ('CopyToCourse' === $action && $meeting->isCourseMeeting()) {
709
                                try {
710
                                    $this->copyFileToCourse($meeting, $file, $name);
711
                                    Display::addFlash(
712
                                        Display::return_message($this->get_lang('FileWasCopiedToCourse'), 'confirm')
713
                                    );
714
                                } catch (Exception $exception) {
715
                                    Display::addFlash(
716
                                        Display::return_message($exception->getMessage(), 'error')
717
                                    );
718
                                }
719
                            } elseif ('DeleteFile' === $action) {
720
                                try {
721
                                    $name = $file->recording_type;
722
                                    $file->delete();
723
                                    Display::addFlash(
724
                                        Display::return_message($this->get_lang('FileWasDeleted').': '.$name, 'confirm')
725
                                    );
726
                                } catch (Exception $exception) {
727
                                    Display::addFlash(
728
                                        Display::return_message($exception->getMessage(), 'error')
729
                                    );
730
                                }
731
                            }
732
                        }
733
                    }
734
                }
735
                api_location($returnURL);
736
            }
737
        }
738
739
        return $form;
740
    }
741
742
    /**
743
     * Adds to the meeting course documents a link to a meeting instance recording file.
744
     *
745
     * @param Meeting       $meeting
746
     * @param RecordingFile $file
747
     * @param string        $name
748
     *
749
     * @throws Exception
750
     */
751
    public function createLinkToFileInCourse($meeting, $file, $name)
752
    {
753
        $course = $meeting->getCourse();
754
        if (null === $course) {
755
            throw new Exception('This meeting is not linked to a course');
756
        }
757
        $courseInfo = api_get_course_info_by_id($course->getId());
758
        if (empty($courseInfo)) {
759
            throw new Exception('This meeting is not linked to a valid course');
760
        }
761
        $path = '/zoom_meeting_recording_file_'.$file->id.'.'.$file->file_type;
762
        $docId = DocumentManager::addCloudLink($courseInfo, $path, $file->play_url, $name);
763
        if (!$docId) {
764
            throw new Exception(get_lang(DocumentManager::cloudLinkExists($courseInfo, $path, $file->play_url) ? 'UrlAlreadyExists' : 'ErrorAddCloudLink'));
765
        }
766
    }
767
768
    /**
769
     * Copies a recording file to a meeting's course.
770
     *
771
     * @param Meeting       $meeting
772
     * @param RecordingFile $file
773
     * @param string        $name
774
     *
775
     * @throws Exception
776
     */
777
    public function copyFileToCourse($meeting, $file, $name)
778
    {
779
        $course = $meeting->getCourse();
780
        if (null === $course) {
781
            throw new Exception('This meeting is not linked to a course');
782
        }
783
        $courseInfo = api_get_course_info_by_id($course->getId());
784
        if (empty($courseInfo)) {
785
            throw new Exception('This meeting is not linked to a valid course');
786
        }
787
        $tmpFile = tmpfile();
788
        if (false === $tmpFile) {
789
            throw new Exception('tmpfile() returned false');
790
        }
791
        $curl = curl_init($file->getFullDownloadURL($this->jwtClient->token));
792
        if (false === $curl) {
793
            throw new Exception('Could not init curl: '.curl_error($curl));
794
        }
795
        if (!curl_setopt_array(
796
            $curl,
797
            [
798
                CURLOPT_FILE => $tmpFile,
799
                CURLOPT_FOLLOWLOCATION => true,
800
                CURLOPT_MAXREDIRS => 10,
801
                CURLOPT_TIMEOUT => 120,
802
            ]
803
        )) {
804
            throw new Exception("Could not set curl options: ".curl_error($curl));
805
        }
806
        if (false === curl_exec($curl)) {
807
            throw new Exception("curl_exec failed: ".curl_error($curl));
808
        }
809
810
        $sessionId = 0;
811
        $session = $meeting->getSession();
812
        if (null !== $session) {
813
            $sessionId = $session->getId();
814
        }
815
816
        $groupId = 0;
817
        $group = $meeting->getGroup();
818
        if (null !== $group) {
819
            $groupId = $group->getIid();
820
        }
821
822
        $newPath = handle_uploaded_document(
823
            $courseInfo,
824
            [
825
                'name' => $name,
826
                'tmp_name' => stream_get_meta_data($tmpFile)['uri'],
827
                'size' => filesize(stream_get_meta_data($tmpFile)['uri']),
828
                'from_file' => true,
829
                'move_file' => true,
830
                'type' => $file->file_type,
831
            ],
832
            api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document',
833
            '/',
834
            api_get_user_id(),
835
            $groupId,
836
            null,
837
            0,
838
            'overwrite',
839
            true,
840
            false,
841
            null,
842
            $sessionId,
843
            true
844
        );
845
846
        fclose($tmpFile);
847
        if (false === $newPath) {
848
            throw new Exception('Could not handle uploaded document');
849
        }
850
    }
851
852
    /**
853
     * Generates a form to fast and easily create and start an instant meeting.
854
     * On validation, create it then redirect to it and exit.
855
     *
856
     * @return FormValidator
857
     */
858
    public function getCreateInstantMeetingForm(
859
        User $user,
860
        Course $course,
861
        CGroupInfo $group = null,
862
        Session $session = null
863
    ) {
864
        $extraUrl = '';
865
        if (!empty($course)) {
866
            $extraUrl = api_get_cidreq();
867
        }
868
        $form = new FormValidator('createInstantMeetingForm', 'post', api_get_self().'?'.$extraUrl, '_blank');
869
        $form->addButton('startButton', $this->get_lang('StartInstantMeeting'), 'video-camera', 'primary');
870
        if ($form->validate()) {
871
            try {
872
                $this->startInstantMeeting($this->get_lang('InstantMeeting'), $user, $course, $group, $session);
873
            } catch (Exception $exception) {
874
                Display::addFlash(
875
                    Display::return_message($exception->getMessage(), 'error')
876
                );
877
            }
878
        }
879
880
        return $form;
881
    }
882
883
    /**
884
     * Generates a form to schedule a meeting.
885
     * On validation, creates it and redirects to its page.
886
     *
887
     * @throws Exception
888
     *
889
     * @return FormValidator
890
     */
891
    public function getScheduleMeetingForm(User $user, Course $course = null, CGroupInfo $group = null, Session $session = null)
892
    {
893
        $extraUrl = '';
894
        if (!empty($course)) {
895
            $extraUrl = api_get_cidreq();
896
        }
897
        $form = new FormValidator('scheduleMeetingForm', 'post', api_get_self().'?'.$extraUrl);
898
        $form->addHeader($this->get_lang('ScheduleAMeeting'));
899
900
        $form->addSelect(
901
            'conference_type',
902
            $this->get_lang('ConferenceType'),
903
            [
904
                'meeting' => $this->get_lang('Meeting'),
905
                'webinar' => $this->get_lang('Webinar'),
906
            ]
907
        );
908
        $form->addRule('conference_type', get_lang('ThisFieldIsRequired'), 'required');
909
910
        $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
911
        $form->setRequired($startTimeDatePicker);
912
913
        $form->addText('topic', $this->get_lang('Topic'), true);
914
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
915
916
        $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
917
        $form->setRequired($durationNumeric);
918
919
        if (null === $course && 'true' === $this->get('enableGlobalConference')) {
920
            $options = [];
921
            $options['everyone'] = $this->get_lang('ForEveryone');
922
            $options['registered_users'] = $this->get_lang('SomeUsers');
923
            if (!empty($options)) {
924
                if (1 === count($options)) {
925
                    $form->addHidden('type', key($options));
926
                } else {
927
                    $form->addSelect('type', $this->get_lang('AudienceType'), $options);
928
                }
929
            }
930
        } else {
931
            // To course
932
            $form->addHidden('type', 'course');
933
        }
934
935
        /*
936
       // $passwordText = $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
937
       if (null !== $course) {
938
           $registrationOptions = [
939
               'RegisterAllCourseUsers' => $this->get_lang('RegisterAllCourseUsers'),
940
           ];
941
           $groups = GroupManager::get_groups();
942
           if (!empty($groups)) {
943
               $registrationOptions['RegisterTheseGroupMembers'] = get_lang('RegisterTheseGroupMembers');
944
           }
945
           $registrationOptions['RegisterNoUser'] = $this->get_lang('RegisterNoUser');
946
           $userRegistrationRadio = $form->addRadio(
947
               'userRegistration',
948
               $this->get_lang('UserRegistration'),
949
               $registrationOptions
950
           );
951
           $groupOptions = [];
952
           foreach ($groups as $group) {
953
               $groupOptions[$group['id']] = $group['name'];
954
           }
955
           $groupIdsSelect = $form->addSelect(
956
               'groupIds',
957
               $this->get_lang('RegisterTheseGroupMembers'),
958
               $groupOptions
959
           );
960
           $groupIdsSelect->setMultiple(true);
961
           if (!empty($groups)) {
962
               $jsCode = sprintf(
963
                   "getElementById('%s').parentNode.parentNode.parentNode.style.display = getElementById('%s').checked ? 'block' : 'none'",
964
                   $groupIdsSelect->getAttribute('id'),
965
                   $userRegistrationRadio->getelements()[1]->getAttribute('id')
966
               );
967
968
               $form->setAttribute('onchange', $jsCode);
969
           }
970
       }*/
971
972
        $accountEmails = $this->getAccountEmails();
973
974
        if (!empty($accountEmails)) {
975
            $form->addSelect('account_email', $this->get_lang('AccountEmail'), $accountEmails);
976
        }
977
978
        $form->addButtonCreate(get_lang('Save'));
979
980
        if ($form->validate()) {
981
            $formValues = $form->exportValues();
982
            $conferenceType = $formValues['conference_type'];
983
            $password = substr(uniqid('z', true), 0, 10);
984
985
            switch ($formValues['type']) {
986
                case 'everyone':
987
                    $user = null;
988
                    $group = null;
989
                    $course = null;
990
                    $session = null;
991
992
                    break;
993
                case 'registered_users':
994
                    //$user = null;
995
                    $course = null;
996
                    $session = null;
997
998
                    break;
999
                case 'course':
1000
                    $user = null;
1001
                    //$course = null;
1002
                    //$session = null;
1003
1004
                    break;
1005
            }
1006
1007
            $accountEmail = $formValues['account_email'] ?? null;
1008
            $accountEmail = $accountEmail && in_array($accountEmail, $accountEmails) ? $accountEmail : null;
1009
1010
            try {
1011
                $startTime = new DateTime($formValues['startTime']);
1012
1013
                if ('meeting' === $conferenceType) {
1014
                    $newMeeting = $this->createScheduleMeeting(
1015
                        $user,
1016
                        $course,
1017
                        $group,
1018
                        $session,
1019
                        $startTime,
1020
                        $formValues['duration'],
1021
                        $formValues['topic'],
1022
                        $formValues['agenda'],
1023
                        $password,
1024
                        $accountEmail
1025
                    );
1026
1027
                    Display::addFlash(
1028
                        Display::return_message($this->get_lang('NewWebinarCreated'))
1029
                    );
1030
                } elseif ('webinar' === $conferenceType) {
1031
                    $newMeeting = $this->createScheduleWebinar(
1032
                        $user,
1033
                        $course,
1034
                        $group,
1035
                        $session,
1036
                        $startTime,
1037
                        $formValues['duration'],
1038
                        $formValues['topic'],
1039
                        $formValues['agenda'],
1040
                        $password,
1041
                        $accountEmail
1042
                    );
1043
1044
                    Display::addFlash(
1045
                        Display::return_message($this->get_lang('NewMeetingCreated'))
1046
                    );
1047
                } else {
1048
                    throw new Exception('Invalid conference type');
1049
                }
1050
1051
                if ($newMeeting->isCourseMeeting()) {
1052
                    if ('RegisterAllCourseUsers' === $form->getSubmitValue('userRegistration')) {
1053
                        $this->registerAllCourseUsers($newMeeting);
1054
                        Display::addFlash(
1055
                            Display::return_message($this->get_lang('AllCourseUsersWereRegistered'))
1056
                        );
1057
                    } elseif ('RegisterTheseGroupMembers' === $form->getSubmitValue('userRegistration')) {
1058
                        $userIds = [];
1059
                        foreach ($form->getSubmitValue('groupIds') as $groupId) {
1060
                            $userIds = array_unique(array_merge($userIds, GroupManager::get_users($groupId)));
1061
                        }
1062
                        $users = Database::getManager()->getRepository('ChamiloUserBundle:User')->findBy(
1063
                            ['id' => $userIds]
1064
                        );
1065
                        $this->registerUsers($newMeeting, $users);
1066
                        Display::addFlash(
1067
                            Display::return_message($this->get_lang('GroupUsersWereRegistered'))
1068
                        );
1069
                    }
1070
                }
1071
                api_location('meeting.php?meetingId='.$newMeeting->getMeetingId().'&'.$extraUrl);
1072
            } catch (Exception $exception) {
1073
                Display::addFlash(
1074
                    Display::return_message($exception->getMessage(), 'error')
1075
                );
1076
            }
1077
        } else {
1078
            $form->setDefaults(
1079
                [
1080
                    'duration' => 60,
1081
                    'userRegistration' => 'RegisterAllCourseUsers',
1082
                ]
1083
            );
1084
        }
1085
1086
        return $form;
1087
    }
1088
1089
    /**
1090
     * Return the current global meeting (create it if needed).
1091
     *
1092
     * @throws Exception
1093
     *
1094
     * @return string
1095
     */
1096
    public function getGlobalMeeting()
1097
    {
1098
        foreach ($this->getMeetingRepository()->unfinishedGlobalMeetings() as $meeting) {
1099
            return $meeting;
1100
        }
1101
1102
        return $this->createGlobalMeeting();
1103
    }
1104
1105
    /**
1106
     * @return MeetingRepository|EntityRepository
1107
     */
1108
    public static function getMeetingRepository()
1109
    {
1110
        return Database::getManager()->getRepository(Meeting::class);
1111
    }
1112
1113
    /**
1114
     * Returns the URL to enter (start or join) a meeting or null if not possible to enter the meeting,
1115
     * The returned URL depends on the meeting current status (waiting, started or finished) and the current user.
1116
     *
1117
     * @param Meeting $meeting
1118
     *
1119
     * @throws OptimisticLockException
1120
     * @throws Exception
1121
     *
1122
     * @return string|null
1123
     */
1124
    public function getStartOrJoinMeetingURL(Meeting $meeting)
1125
    {
1126
        if ($meeting instanceof Webinar) {
1127
            $status = 'started';
1128
        } else {
1129
            $status = $meeting->getMeetingInfoGet()->status;
1130
        }
1131
1132
        $userId = api_get_user_id();
1133
        $currentUser = api_get_user_entity($userId);
1134
        $isGlobal = 'true' === $this->get('enableGlobalConference') && $meeting->isGlobalMeeting();
1135
1136
        switch ($status) {
1137
            case 'ended':
1138
                if ($this->userIsConferenceManager($meeting)) {
1139
                    return $meeting->getMeetingInfoGet()->start_url;
1140
                }
1141
                break;
1142
            case 'waiting':
1143
                // Zoom does not allow for a new meeting to be started on first participant join.
1144
                // It requires the host to start the meeting first.
1145
                // Therefore for global meetings we must make the first participant the host
1146
                // that is use start_url rather than join_url.
1147
                // the participant will not be registered and will appear as the Zoom user account owner.
1148
                // For course and user meetings, only the host can start the meeting.
1149
                if ($this->userIsConferenceManager($meeting)) {
1150
                    return $meeting->getMeetingInfoGet()->start_url;
1151
                }
1152
1153
                break;
1154
            case 'started':
1155
                // User per conference.
1156
                if ($currentUser === $meeting->getUser()) {
1157
                    return $meeting instanceof Webinar
1158
                        ? $meeting->getWebinarSchema()->start_url
1159
                        : $meeting->getMeetingInfoGet()->join_url;
1160
                }
1161
1162
                // The participant is not registered, he can join only the global meeting (automatic registration).
1163
                if ($isGlobal) {
1164
                    return $this->registerUser($meeting, $currentUser)->getCreatedRegistration()->join_url;
1165
                }
1166
1167
                if ($meeting->isCourseMeeting()) {
1168
                    if ($this->userIsCourseConferenceManager()) {
1169
                        return $meeting instanceof Webinar
1170
                            ? $meeting->getWebinarSchema()->start_url
1171
                            : $meeting->getMeetingInfoGet()->start_url;
1172
                    }
1173
1174
                    $sessionId = api_get_session_id();
1175
                    $courseCode = api_get_course_id();
1176
1177
                    if (empty($sessionId)) {
1178
                        $isSubscribed = CourseManager::is_user_subscribed_in_course(
1179
                            $userId,
1180
                            $courseCode,
1181
                            false
1182
                        );
1183
                    } else {
1184
                        $isSubscribed = CourseManager::is_user_subscribed_in_course(
1185
                            $userId,
1186
                            $courseCode,
1187
                            true,
1188
                            $sessionId
1189
                        );
1190
                    }
1191
1192
                    if ($isSubscribed) {
1193
                        if ($meeting->isCourseGroupMeeting()) {
1194
                            $groupInfo = GroupManager::get_group_properties($meeting->getGroup()->getIid(), true);
1195
                            $isInGroup = GroupManager::is_user_in_group($userId, $groupInfo);
1196
                            if (false === $isInGroup) {
1197
                                throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
1198
                            }
1199
                        }
1200
1201
                        if (!$meeting instanceof Webinar
1202
                            && \Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT == $meeting->getMeetingInfoGet()->type
1203
                        ) {
1204
                            return $meeting->getMeetingInfoGet()->join_url;
1205
                        }
1206
1207
                        return $this->registerUser($meeting, $currentUser)->getCreatedRegistration()->join_url;
1208
                    }
1209
1210
                    throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
1211
                }
1212
1213
                //if ('true' === $this->get('enableParticipantRegistration')) {
1214
                    //if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {
1215
                    // the participant must be registered
1216
                    $registrant = $meeting->getRegistrantByUser($currentUser);
1217
                    if (null == $registrant) {
1218
                        throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
1219
                    }
1220
1221
                    // the participant is registered
1222
                    return $registrant->getCreatedRegistration()->join_url;
1223
                //}
1224
                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...
1225
        }
1226
1227
        return null;
1228
    }
1229
1230
    /**
1231
     * @param Meeting $meeting
1232
     *
1233
     * @return bool whether the logged-in user can manage conferences in this context, that is either
1234
     *              the current course or session coach, the platform admin or the current course admin
1235
     */
1236
    public function userIsConferenceManager($meeting)
1237
    {
1238
        if (null === $meeting) {
1239
            return false;
1240
        }
1241
1242
        if (api_is_coach() || api_is_platform_admin()) {
1243
            return true;
1244
        }
1245
1246
        if ($meeting->isCourseMeeting() && api_get_course_id() && api_is_course_admin()) {
1247
            return true;
1248
        }
1249
1250
        return $meeting->isUserMeeting() && $meeting->getUser()->getId() == api_get_user_id();
1251
    }
1252
1253
    /**
1254
     * @return bool whether the logged-in user can manage conferences in this context, that is either
1255
     *              the current course or session coach, the platform admin or the current course admin
1256
     */
1257
    public function userIsCourseConferenceManager()
1258
    {
1259
        if (api_is_coach() || api_is_platform_admin()) {
1260
            return true;
1261
        }
1262
1263
        if (api_get_course_id() && api_is_course_admin()) {
1264
            return true;
1265
        }
1266
1267
        return false;
1268
    }
1269
1270
    /**
1271
     * Update local recording list from remote Zoom server's version.
1272
     * Kept to implement a future administration button ("import existing data from zoom server").
1273
     *
1274
     * @param DateTime $startDate
1275
     * @param DateTime $endDate
1276
     *
1277
     * @throws OptimisticLockException
1278
     * @throws Exception
1279
     */
1280
    public function reloadPeriodRecordings($startDate, $endDate)
1281
    {
1282
        $em = Database::getManager();
1283
        $recordingRepo = $this->getRecordingRepository();
1284
        $meetingRepo = $this->getMeetingRepository();
1285
        $recordings = RecordingList::loadPeriodRecordings($startDate, $endDate);
1286
1287
        foreach ($recordings as $recordingMeeting) {
1288
            $recordingEntity = $recordingRepo->findOneBy(['uuid' => $recordingMeeting->uuid]);
1289
            if (null === $recordingEntity) {
1290
                $recordingEntity = new Recording();
1291
                $meeting = $meetingRepo->findOneBy(['meetingId' => $recordingMeeting->id]);
1292
                if (null === $meeting) {
1293
                    try {
1294
                        $meetingInfoGet = MeetingInfoGet::fromId($recordingMeeting->id);
1295
                    } catch (Exception $exception) {
1296
                        $meetingInfoGet = null; // deleted meeting with recordings
1297
                    }
1298
                    if (null !== $meetingInfoGet) {
1299
                        $meeting = $this->createMeetingFromMeeting(
1300
                            (new Meeting())->setMeetingInfoGet($meetingInfoGet)
1301
                        );
1302
                        $em->persist($meeting);
1303
                    }
1304
                }
1305
                if (null !== $meeting) {
1306
                    $recordingEntity->setMeeting($meeting);
1307
                }
1308
            }
1309
            $recordingEntity->setRecordingMeeting($recordingMeeting);
1310
            $em->persist($recordingEntity);
1311
        }
1312
        $em->flush();
1313
    }
1314
1315
    /**
1316
     * @return RecordingRepository|EntityRepository
1317
     */
1318
    public static function getRecordingRepository()
1319
    {
1320
        return Database::getManager()->getRepository(Recording::class);
1321
    }
1322
1323
    public function getToolbar($returnUrl = '')
1324
    {
1325
        if (!api_is_platform_admin()) {
1326
            return '';
1327
        }
1328
1329
        $actionsLeft = '';
1330
        $back = '';
1331
        $courseId = api_get_course_id();
1332
        if (empty($courseId)) {
1333
            $actionsLeft .=
1334
                Display::url(
1335
                    Display::return_icon('bbb.png', $this->get_lang('Meetings'), null, ICON_SIZE_MEDIUM),
1336
                    api_get_path(WEB_PLUGIN_PATH).'zoom/meetings.php'
1337
                );
1338
        } else {
1339
            $actionsLeft .=
1340
                Display::url(
1341
                    Display::return_icon('bbb.png', $this->get_lang('Meetings'), null, ICON_SIZE_MEDIUM),
1342
                    api_get_path(WEB_PLUGIN_PATH).'zoom/start.php?'.api_get_cidreq()
1343
                );
1344
        }
1345
1346
        if (!empty($returnUrl)) {
1347
            $back = Display::url(
1348
                Display::return_icon('back.png', get_lang('Back'), null, ICON_SIZE_MEDIUM),
1349
                $returnUrl
1350
            );
1351
        }
1352
1353
        if (api_is_platform_admin()) {
1354
            $actionsLeft .= Display::url(
1355
                Display::return_icon('agenda.png', get_lang('Calendar'), [], ICON_SIZE_MEDIUM),
1356
                'calendar.php'
1357
            );
1358
            $actionsLeft .=
1359
                Display::url(
1360
                    Display::return_icon('settings.png', get_lang('Settings'), null, ICON_SIZE_MEDIUM),
1361
                    api_get_path(WEB_CODE_PATH).'admin/configure_plugin.php?name=zoom'
1362
                ).$back;
1363
        }
1364
1365
        return Display::toolbarAction('toolbar', [$actionsLeft]);
1366
    }
1367
1368
    public function getRecordingSetting()
1369
    {
1370
        $recording = (string) $this->get('enableCloudRecording');
1371
1372
        if (in_array($recording, [self::RECORDING_TYPE_LOCAL, self::RECORDING_TYPE_CLOUD], true)) {
1373
            return $recording;
1374
        }
1375
1376
        return self::RECORDING_TYPE_NONE;
1377
    }
1378
1379
    public function hasRecordingAvailable()
1380
    {
1381
        $recording = $this->getRecordingSetting();
1382
1383
        return self::RECORDING_TYPE_NONE !== $recording;
1384
    }
1385
1386
    /**
1387
     * @throws Exception
1388
     */
1389
    public function createWebinarFromSchema(Webinar $webinar, WebinarSchema $schema): Webinar
1390
    {
1391
        $currentUser = api_get_user_entity(api_get_user_id());
1392
1393
        $schema->settings->contact_email = $currentUser->getEmail();
1394
        $schema->settings->contact_name = $currentUser->getFullname();
1395
        $schema->settings->auto_recording = $this->getRecordingSetting();
1396
        $schema->settings->registrants_email_notification = false;
1397
        $schema->settings->attendees_and_panelists_reminder_email_notification->enable = false;
1398
        $schema->settings->follow_up_attendees_email_notification->enable = false;
1399
        $schema->settings->follow_up_absentees_email_notification->enable = false;
1400
1401
        $schema = $schema->create($webinar->getAccountEmail());
1402
1403
        $webinar->setWebinarSchema($schema);
1404
1405
        $em = Database::getManager();
1406
        $em->persist($webinar);
1407
        $em->flush();
1408
1409
        return $webinar;
1410
    }
1411
1412
    public function getAccountEmails(): array
1413
    {
1414
        $currentValue = $this->get('accountSelector');
1415
1416
        if (empty($currentValue)) {
1417
            return [];
1418
        }
1419
1420
        $emails = explode(';', $currentValue);
1421
        $trimmed = array_map('trim', $emails);
1422
        $filtered = array_filter($trimmed);
1423
1424
        return array_combine($filtered, $filtered);
1425
    }
1426
1427
    /**
1428
     * Updates meeting registrants list. Adds the missing registrants and removes the extra.
1429
     *
1430
     * @param Meeting $meeting
1431
     * @param User[]  $users   list of users to be registered
1432
     *
1433
     * @throws Exception
1434
     */
1435
    private function updateRegistrantList($meeting, $users)
1436
    {
1437
        $usersToAdd = [];
1438
        foreach ($users as $user) {
1439
            $found = false;
1440
            foreach ($meeting->getRegistrants() as $registrant) {
1441
                if ($registrant->getUser() === $user) {
1442
                    $found = true;
1443
                    break;
1444
                }
1445
            }
1446
            if (!$found) {
1447
                $usersToAdd[] = $user;
1448
            }
1449
        }
1450
        $registrantsToRemove = [];
1451
        foreach ($meeting->getRegistrants() as $registrant) {
1452
            $found = false;
1453
            foreach ($users as $user) {
1454
                if ($registrant->getUser() === $user) {
1455
                    $found = true;
1456
                    break;
1457
                }
1458
            }
1459
            if (!$found) {
1460
                $registrantsToRemove[] = $registrant;
1461
            }
1462
        }
1463
        $this->registerUsers($meeting, $usersToAdd);
1464
        $this->unregister($meeting, $registrantsToRemove);
1465
    }
1466
1467
    /**
1468
     * Register users to a meeting.
1469
     *
1470
     * @param Meeting $meeting
1471
     * @param User[]  $users
1472
     *
1473
     * @throws OptimisticLockException
1474
     *
1475
     * @return User[] failed registrations [ user id => errorMessage ]
1476
     */
1477
    public function registerUsers(Meeting $meeting, array $users)
1478
    {
1479
        $failedUsers = [];
1480
        foreach ($users as $user) {
1481
            try {
1482
                $this->registerUser($meeting, $user, false);
1483
            } catch (Exception $exception) {
1484
                $failedUsers[$user->getId()] = $exception->getMessage();
1485
            }
1486
        }
1487
        Database::getManager()->flush();
1488
1489
        return $failedUsers;
1490
    }
1491
1492
    /**
1493
     * @throws Exception
1494
     * @throws OptimisticLockException
1495
     *
1496
     * @return Registrant
1497
     */
1498
    private function registerUser(Meeting $meeting, User $user, $andFlush = true)
1499
    {
1500
        if (empty($user->getEmail())) {
1501
            throw new Exception($this->get_lang('CannotRegisterWithoutEmailAddress'));
1502
        }
1503
1504
        if ($meeting instanceof Webinar) {
1505
            $meetingRegistrant = WebinarRegistrantSchema::fromEmailAndFirstName(
1506
                $user->getEmail(),
1507
                $user->getFirstname(),
1508
                $user->getLastname()
1509
            );
1510
        } else {
1511
            $meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName(
1512
                $user->getEmail(),
1513
                $user->getFirstname(),
1514
                $user->getLastname()
1515
            );
1516
        }
1517
1518
        $registrantEntity = (new Registrant())
1519
            ->setMeeting($meeting)
1520
            ->setUser($user)
1521
            ->setMeetingRegistrant($meetingRegistrant)
1522
        ;
1523
1524
        if ($meeting instanceof Webinar) {
1525
            $registrantEntity->setCreatedRegistration($meeting->getWebinarSchema()->addRegistrant($meetingRegistrant));
1526
        } else {
1527
            $registrantEntity->setCreatedRegistration($meeting->getMeetingInfoGet()->addRegistrant($meetingRegistrant));
1528
        }
1529
1530
        Database::getManager()->persist($registrantEntity);
1531
1532
        if ($andFlush) {
1533
            Database::getManager()->flush($registrantEntity);
1534
        }
1535
1536
        return $registrantEntity;
1537
    }
1538
1539
    /**
1540
     * Removes registrants from a meeting.
1541
     *
1542
     * @param Meeting      $meeting
1543
     * @param Registrant[] $registrants
1544
     *
1545
     * @throws Exception
1546
     */
1547
    public function unregister(Meeting $meeting, array $registrants)
1548
    {
1549
        $meetingRegistrants = [];
1550
        foreach ($registrants as $registrant) {
1551
            $meetingRegistrants[] = $registrant->getMeetingRegistrant();
1552
        }
1553
1554
        if ($meeting instanceof Webinar) {
1555
            $meeting->getWebinarSchema()->removeRegistrants($meetingRegistrants);
1556
        } else {
1557
            $meeting->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
1558
        }
1559
1560
        $em = Database::getManager();
1561
        foreach ($registrants as $registrant) {
1562
            $em->remove($registrant);
1563
        }
1564
        $em->flush();
1565
    }
1566
1567
    /**
1568
     * Starts a new instant meeting and redirects to its start url.
1569
     *
1570
     * @param string          $topic
1571
     * @param User|null       $user
1572
     * @param Course|null     $course
1573
     * @param CGroupInfo|null $group
1574
     * @param Session|null    $session
1575
     *
1576
     * @throws Exception
1577
     */
1578
    private function startInstantMeeting($topic, $user = null, $course = null, $group = null, $session = null)
1579
    {
1580
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_INSTANT);
1581
        //$meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
1582
        $meeting = $this->createMeetingFromMeeting(
1583
            (new Meeting())
1584
                ->setMeetingInfoGet($meetingInfoGet)
1585
                ->setUser($user)
1586
                ->setGroup($group)
1587
                ->setCourse($course)
1588
                ->setSession($session)
1589
        );
1590
        api_location($meeting->getMeetingInfoGet()->start_url);
1591
    }
1592
1593
    /**
1594
     * Creates a meeting on Zoom servers and stores it in the local database.
1595
     *
1596
     * @param Meeting $meeting a new, unsaved meeting with at least a type and a topic
1597
     *
1598
     * @throws Exception
1599
     *
1600
     * @return Meeting
1601
     */
1602
    private function createMeetingFromMeeting($meeting)
1603
    {
1604
        $currentUser = api_get_user_entity(api_get_user_id());
1605
1606
        $meeting->getMeetingInfoGet()->settings->contact_email = $currentUser->getEmail();
1607
        $meeting->getMeetingInfoGet()->settings->contact_name = $currentUser->getFullname();
1608
        $meeting->getMeetingInfoGet()->settings->auto_recording = $this->getRecordingSetting();
1609
        $meeting->getMeetingInfoGet()->settings->registrants_email_notification = false;
1610
1611
        //$meeting->getMeetingInfoGet()->host_email = $currentUser->getEmail();
1612
        //$meeting->getMeetingInfoGet()->settings->alternative_hosts = $currentUser->getEmail();
1613
1614
        // Send create to Zoom.
1615
        $meeting->setMeetingInfoGet(
1616
            $meeting->getMeetingInfoGet()->create(
1617
                $meeting->getAccountEmail()
1618
            )
1619
        );
1620
1621
        Database::getManager()->persist($meeting);
1622
        Database::getManager()->flush();
1623
1624
        return $meeting;
1625
    }
1626
1627
    /**
1628
     * @throws Exception
1629
     *
1630
     * @return Meeting
1631
     */
1632
    private function createGlobalMeeting()
1633
    {
1634
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType(
1635
            $this->get_lang('GlobalMeeting'),
1636
            MeetingInfoGet::TYPE_SCHEDULED
1637
        );
1638
        $meetingInfoGet->start_time = (new DateTime())->format(DATE_ATOM);
1639
        $meetingInfoGet->duration = 60;
1640
        $meetingInfoGet->settings->approval_type =
1641
            ('true' === $this->get('enableParticipantRegistration'))
1642
                ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE
1643
                : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
1644
        // $meetingInfoGet->settings->host_video = true;
1645
        $meetingInfoGet->settings->participant_video = true;
1646
        $meetingInfoGet->settings->join_before_host = true;
1647
        $meetingInfoGet->settings->registrants_email_notification = false;
1648
1649
        return $this->createMeetingFromMeeting((new Meeting())->setMeetingInfoGet($meetingInfoGet));
1650
    }
1651
1652
    /**
1653
     * Schedules a meeting and returns it.
1654
     * set $course, $session and $user to null in order to create a global meeting.
1655
     *
1656
     * @param DateTime $startTime meeting local start date-time (configure local timezone on your Zoom account)
1657
     * @param int      $duration  in minutes
1658
     * @param string   $topic     short title of the meeting, required
1659
     * @param string   $agenda    ordre du jour
1660
     * @param string   $password  meeting password
1661
     *
1662
     * @throws Exception
1663
     *
1664
     * @return Meeting meeting
1665
     */
1666
    private function createScheduleMeeting(
1667
        User $user = null,
1668
        Course $course = null,
1669
        CGroupInfo $group = null,
1670
        Session $session = null,
1671
        $startTime,
1672
        $duration,
1673
        $topic,
1674
        $agenda,
1675
        $password,
1676
        string $accountEmail = null
1677
    ) {
1678
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED);
1679
        $meetingInfoGet->duration = $duration;
1680
        $meetingInfoGet->start_time = $startTime->format(DATE_ATOM);
1681
        $meetingInfoGet->agenda = $agenda;
1682
        $meetingInfoGet->password = $password;
1683
        $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
1684
        if ('true' === $this->get('enableParticipantRegistration')) {
1685
            $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
1686
        }
1687
1688
        return $this->createMeetingFromMeeting(
1689
            (new Meeting())
1690
                ->setMeetingInfoGet($meetingInfoGet)
1691
                ->setUser($user)
1692
                ->setCourse($course)
1693
                ->setGroup($group)
1694
                ->setSession($session)
1695
                ->setAccountEmail($accountEmail)
1696
        );
1697
    }
1698
1699
    /**
1700
     * @throws Exception
1701
     */
1702
    private function createScheduleWebinar(
1703
        ?User $user,
1704
        ?Course $course,
1705
        ?CGroupInfo $group,
1706
        ?Session $session,
1707
        DateTime $startTime,
1708
        $duration,
1709
        $topic,
1710
        $agenda,
1711
        $password,
1712
        string $accountEmail = null
1713
    ): Webinar {
1714
        $webinarSchema = WebinarSchema::fromTopicAndType($topic);
1715
        $webinarSchema->duration = $duration;
1716
        $webinarSchema->start_time = $startTime->format(DATE_ATOM);
1717
        $webinarSchema->agenda = $agenda;
1718
        $webinarSchema->password = $password;
1719
1720
        if ('true' === $this->get('enableParticipantRegistration')) {
1721
            $webinarSchema->settings->approval_type = WebinarSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
1722
        }
1723
1724
        $webinar = (new Webinar())
1725
            ->setUser($user)
1726
            ->setCourse($course)
1727
            ->setGroup($group)
1728
            ->setSession($session)
1729
            ->setAccountEmail($accountEmail)
1730
        ;
1731
1732
        return $this->createWebinarFromSchema($webinar, $webinarSchema);
1733
    }
1734
1735
    /**
1736
     * Registers all the course users to a course meeting.
1737
     *
1738
     * @param Meeting $meeting
1739
     *
1740
     * @throws OptimisticLockException
1741
     */
1742
    private function registerAllCourseUsers($meeting)
1743
    {
1744
        $this->registerUsers($meeting, $meeting->getRegistrableUsers());
1745
    }
1746
}
1747