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

ZoomPlugin::createScheduleWebinar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
dl 0
loc 31
rs 9.7998
c 1
b 0
f 0
cc 2
nc 2
nop 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\Session;
7
use Chamilo\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