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

ZoomPlugin::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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