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

ZoomPlugin::createScheduleWebinar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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