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