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

ZoomPlugin::createScheduleWebinar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\Session;
7
use Chamilo\CourseBundle\Entity\CGroupInfo;
8
use Chamilo\PluginBundle\Zoom\API\JWTClient;
9
use Chamilo\PluginBundle\Zoom\API\MeetingInfoGet;
10
use Chamilo\PluginBundle\Zoom\API\MeetingRegistrant;
11
use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
12
use Chamilo\PluginBundle\Zoom\API\RecordingFile;
13
use Chamilo\PluginBundle\Zoom\API\RecordingList;
14
use Chamilo\PluginBundle\Zoom\API\WebinarRegistrantSchema;
15
use Chamilo\PluginBundle\Zoom\API\WebinarSchema;
16
use Chamilo\PluginBundle\Zoom\API\WebinarSettings;
17
use Chamilo\PluginBundle\Zoom\Meeting;
18
use Chamilo\PluginBundle\Zoom\MeetingActivity;
19
use Chamilo\PluginBundle\Zoom\MeetingRepository;
20
use Chamilo\PluginBundle\Zoom\Recording;
21
use Chamilo\PluginBundle\Zoom\RecordingRepository;
22
use Chamilo\PluginBundle\Zoom\Registrant;
23
use Chamilo\PluginBundle\Zoom\RegistrantRepository;
24
use Chamilo\PluginBundle\Zoom\Webinar;
25
use Chamilo\UserBundle\Entity\User;
26
use Doctrine\ORM\EntityRepository;
27
use Doctrine\ORM\OptimisticLockException;
28
use Doctrine\ORM\Tools\SchemaTool;
29
use Doctrine\ORM\Tools\ToolsException;
30
31
/**
32
 * Class ZoomPlugin. Integrates Zoom meetings in courses.
33
 */
34
class ZoomPlugin extends Plugin
35
{
36
    const RECORDING_TYPE_CLOUD = 'cloud';
37
    const RECORDING_TYPE_LOCAL = 'local';
38
    const RECORDING_TYPE_NONE = 'none';
39
    public $isCoursePlugin = true;
40
41
    /**
42
     * @var JWTClient
43
     */
44
    private $jwtClient;
45
46
    /**
47
     * ZoomPlugin constructor.
48
     * {@inheritdoc}
49
     * Initializes the API JWT client and the entity repositories.
50
     */
51
    public function __construct()
52
    {
53
        parent::__construct(
54
            '0.4',
55
            'Sébastien Ducoulombier, Julio Montoya',
56
            [
57
                'tool_enable' => 'boolean',
58
                'apiKey' => 'text',
59
                'apiSecret' => 'text',
60
                'verificationToken' => 'text',
61
                'enableParticipantRegistration' => 'boolean',
62
                'enableCloudRecording' => [
63
                    'type' => 'select',
64
                    'options' => [
65
                        self::RECORDING_TYPE_CLOUD => 'Cloud',
66
                        self::RECORDING_TYPE_LOCAL => 'Local',
67
                        self::RECORDING_TYPE_NONE => get_lang('None'),
68
                    ],
69
                ],
70
                'enableGlobalConference' => 'boolean',
71
                'globalConferenceAllowRoles' => [
72
                    'type' => 'select',
73
                    'options' => [
74
                        PLATFORM_ADMIN => get_lang('Administrator'),
75
                        COURSEMANAGER => get_lang('Teacher'),
76
                        STUDENT => get_lang('Student'),
77
                        STUDENT_BOSS => get_lang('StudentBoss'),
78
                    ],
79
                    'attributes' => ['multiple' => 'multiple'],
80
                ],
81
                'accountSelector' => 'text',
82
            ]
83
        );
84
85
        $this->isAdminPlugin = true;
86
        $this->jwtClient = new JWTClient($this->get('apiKey'), $this->get('apiSecret'));
87
    }
88
89
    /**
90
     * Caches and returns an instance of this class.
91
     *
92
     * @return ZoomPlugin the instance to use
93
     */
94
    public static function create()
95
    {
96
        static $instance = null;
97
98
        return $instance ? $instance : $instance = new self();
99
    }
100
101
    /**
102
     * @return bool
103
     */
104
    public static function currentUserCanJoinGlobalMeeting()
105
    {
106
        $user = api_get_user_entity(api_get_user_id());
107
108
        if (null === $user) {
109
            return false;
110
        }
111
112
        //return 'true' === api_get_plugin_setting('zoom', 'enableGlobalConference') && api_user_is_login();
113
        return
114
            'true' === api_get_plugin_setting('zoom', 'enableGlobalConference')
115
            && in_array(
116
                (api_is_platform_admin() ? PLATFORM_ADMIN : $user->getStatus()),
117
                (array) api_get_plugin_setting('zoom', 'globalConferenceAllowRoles')
118
            );
119
    }
120
121
    /**
122
     * @return array
123
     */
124
    public function getProfileBlockItems()
125
    {
126
        $elements = $this->meetingsToWhichCurrentUserIsRegisteredComingSoon();
127
        $addMeetingLink = false;
128
        if (self::currentUserCanJoinGlobalMeeting()) {
129
            $addMeetingLink = true;
130
        }
131
132
        if ($addMeetingLink) {
133
            $elements[$this->get_lang('Meetings')] = api_get_path(WEB_PLUGIN_PATH).'zoom/meetings.php';
134
        }
135
136
        $items = [];
137
        foreach ($elements as $title => $link) {
138
            $items[] = [
139
                'class' => 'video-conference',
140
                'icon' => Display::return_icon(
141
                    'bbb.png',
142
                    get_lang('VideoConference')
143
                ),
144
                'link' => $link,
145
                'title' => $title,
146
            ];
147
        }
148
149
        return $items;
150
    }
151
152
    /**
153
     * @return array [ $title => $link ]
154
     */
155
    public function meetingsToWhichCurrentUserIsRegisteredComingSoon()
156
    {
157
        $linkTemplate = api_get_path(WEB_PLUGIN_PATH).'zoom/join_meeting.php?meetingId=%s';
158
        $user = api_get_user_entity(api_get_user_id());
159
        $meetings = self::getRegistrantRepository()->meetingsComingSoonRegistrationsForUser($user);
160
        $items = [];
161
        foreach ($meetings as $registrant) {
162
            $meeting = $registrant->getMeeting();
163
            $items[sprintf(
164
                $this->get_lang('DateMeetingTitle'),
165
                $meeting->formattedStartTime,
166
                $meeting->getMeetingInfoGet()->topic
167
            )] = sprintf($linkTemplate, $meeting->getId());
168
        }
169
170
        return $items;
171
    }
172
173
    /**
174
     * @return RegistrantRepository|EntityRepository
175
     */
176
    public static function getRegistrantRepository()
177
    {
178
        return Database::getManager()->getRepository(Registrant::class);
179
    }
180
181
    /**
182
     * Creates this plugin's related tables in the internal database.
183
     * Installs course fields in all courses.
184
     *
185
     * @throws ToolsException
186
     */
187
    public function install()
188
    {
189
        $schemaManager = Database::getManager()->getConnection()->getSchemaManager();
190
191
        $tablesExists = $schemaManager->tablesExist(
192
            [
193
                'plugin_zoom_meeting',
194
                'plugin_zoom_meeting_activity',
195
                'plugin_zoom_recording',
196
                'plugin_zoom_registrant',
197
            ]
198
        );
199
200
        if ($tablesExists) {
201
            return;
202
        }
203
204
        (new SchemaTool(Database::getManager()))->createSchema(
205
            [
206
                Database::getManager()->getClassMetadata(Meeting::class),
207
                Database::getManager()->getClassMetadata(Webinar::class),
208
                Database::getManager()->getClassMetadata(MeetingActivity::class),
209
                Database::getManager()->getClassMetadata(Recording::class),
210
                Database::getManager()->getClassMetadata(Registrant::class),
211
            ]
212
        );
213
214
        // Copy icons into the main/img/icons folder
215
        $iconName = 'zoom_meet';
216
        $iconsList = [
217
            '64/'.$iconName.'.png',
218
            '64/'.$iconName.'_na.png',
219
            '32/'.$iconName.'.png',
220
            '32/'.$iconName.'_na.png',
221
            '22/'.$iconName.'.png',
222
            '22/'.$iconName.'_na.png',
223
        ];
224
        $sourceDir = api_get_path(SYS_PLUGIN_PATH).'zoom/resources/img/';
225
        $destinationDir = api_get_path(SYS_CODE_PATH).'img/icons/';
226
        foreach ($iconsList as $icon) {
227
            $src = $sourceDir.$icon;
228
            $dest = $destinationDir.$icon;
229
            copy($src, $dest);
230
        }
231
232
        $this->install_course_fields_in_all_courses(true, 'zoom_meet.png');
233
    }
234
235
    /**
236
     * Drops this plugins' related tables from the internal database.
237
     * Uninstalls course fields in all courses().
238
     */
239
    public function uninstall()
240
    {
241
        (new SchemaTool(Database::getManager()))->dropSchema(
242
            [
243
                Database::getManager()->getClassMetadata(Meeting::class),
244
                Database::getManager()->getClassMetadata(Webinar::class),
245
                Database::getManager()->getClassMetadata(MeetingActivity::class),
246
                Database::getManager()->getClassMetadata(Recording::class),
247
                Database::getManager()->getClassMetadata(Registrant::class),
248
            ]
249
        );
250
        $this->uninstall_course_fields_in_all_courses();
251
252
        // Remove icons from the main/img/icons folder
253
        $iconName = 'zoom_meet';
254
        $iconsList = [
255
            '64/'.$iconName.'.png',
256
            '64/'.$iconName.'_na.png',
257
            '32/'.$iconName.'.png',
258
            '32/'.$iconName.'_na.png',
259
            '22/'.$iconName.'.png',
260
            '22/'.$iconName.'_na.png',
261
        ];
262
        $destinationDir = api_get_path(SYS_CODE_PATH).'img/icons/';
263
        foreach ($iconsList as $icon) {
264
            $dest = $destinationDir.$icon;
265
            if (is_file($dest)) {
266
                @unlink($dest);
267
            }
268
        }
269
    }
270
271
    /**
272
     * Generates the search form to include in the meeting list administration page.
273
     * The form has DatePickers 'start' and 'end' and Checkbox 'reloadRecordingLists'.
274
     *
275
     * @return FormValidator the form
276
     */
277
    public function getAdminSearchForm()
278
    {
279
        $form = new FormValidator('search');
280
        $form->addHeader($this->get_lang('SearchMeeting'));
281
        $form->addDatePicker('start', get_lang('StartDate'));
282
        $form->addDatePicker('end', get_lang('EndDate'));
283
        $form->addButtonSearch(get_lang('Search'));
284
        $oneMonth = new DateInterval('P1M');
285
        if ($form->validate()) {
286
            try {
287
                $start = new DateTime($form->getSubmitValue('start'));
288
            } catch (Exception $exception) {
289
                $start = new DateTime();
290
                $start->sub($oneMonth);
291
            }
292
            try {
293
                $end = new DateTime($form->getSubmitValue('end'));
294
            } catch (Exception $exception) {
295
                $end = new DateTime();
296
                $end->add($oneMonth);
297
            }
298
        } else {
299
            $start = new DateTime();
300
            $start->sub($oneMonth);
301
            $end = new DateTime();
302
            $end->add($oneMonth);
303
        }
304
        try {
305
            $form->setDefaults(
306
                [
307
                    'start' => $start->format('Y-m-d'),
308
                    'end' => $end->format('Y-m-d'),
309
                ]
310
            );
311
        } catch (Exception $exception) {
312
            error_log(join(':', [__FILE__, __LINE__, $exception]));
313
        }
314
315
        return $form;
316
    }
317
318
    /**
319
     * Generates a meeting edit form and updates the meeting on validation.
320
     *
321
     * @param Meeting $meeting the meeting
322
     *
323
     * @throws Exception
324
     *
325
     * @return FormValidator
326
     */
327
    public function getEditMeetingForm($meeting)
328
    {
329
        $meetingInfoGet = $meeting->getMeetingInfoGet();
330
        $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
331
        $form->addHeader($this->get_lang('UpdateMeeting'));
332
        $form->addText('topic', $this->get_lang('Topic'));
333
        if ($meeting->requiresDateAndDuration()) {
334
            $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
335
            $form->setRequired($startTimeDatePicker);
336
            $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
337
            $form->setRequired($durationNumeric);
338
        }
339
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
340
        //$form->addLabel(get_lang('Password'), $meeting->getMeetingInfoGet()->password);
341
        // $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
342
        $form->addButtonUpdate(get_lang('Update'));
343
        if ($form->validate()) {
344
            if ($meeting->requiresDateAndDuration()) {
345
                $meetingInfoGet->start_time = (new DateTime($form->getSubmitValue('startTime')))->format(
346
                    DATE_ATOM
347
                );
348
                $meetingInfoGet->timezone = date_default_timezone_get();
349
                $meetingInfoGet->duration = (int) $form->getSubmitValue('duration');
350
            }
351
            $meetingInfoGet->topic = $form->getSubmitValue('topic');
352
            $meetingInfoGet->agenda = $form->getSubmitValue('agenda');
353
            try {
354
                $meetingInfoGet->update();
355
                $meeting->setMeetingInfoGet($meetingInfoGet);
356
                Database::getManager()->persist($meeting);
357
                Database::getManager()->flush();
358
                Display::addFlash(
359
                    Display::return_message($this->get_lang('MeetingUpdated'), 'confirm')
360
                );
361
            } catch (Exception $exception) {
362
                Display::addFlash(
363
                    Display::return_message($exception->getMessage(), 'error')
364
                );
365
            }
366
        }
367
        $defaults = [
368
            'topic' => $meetingInfoGet->topic,
369
            'agenda' => $meetingInfoGet->agenda,
370
        ];
371
        if ($meeting->requiresDateAndDuration()) {
372
            $defaults['startTime'] = $meeting->startDateTime->format('Y-m-d H:i');
373
            $defaults['duration'] = $meetingInfoGet->duration;
374
        }
375
        $form->setDefaults($defaults);
376
377
        return $form;
378
    }
379
380
    /**
381
     * @throws Exception
382
     */
383
    public function getEditWebinarForm(Webinar $webinar): FormValidator
384
    {
385
        $schema = $webinar->getWebinarSchema();
386
        $requiresDateAndDuration = $webinar->requiresDateAndDuration();
387
388
        $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
389
        $form->addHeader($this->get_lang('UpdateWebinar'));
390
        $form->addText('topic', $this->get_lang('Topic'));
391
392
        if ($requiresDateAndDuration) {
393
            $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
394
            $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
395
396
            $form->setRequired($startTimeDatePicker);
397
            $form->setRequired($durationNumeric);
398
        }
399
400
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
401
        $form->addButtonUpdate(get_lang('Update'));
402
403
        if ($form->validate()) {
404
            $formValues = $form->exportValues();
405
406
            if ($requiresDateAndDuration) {
407
                $schema->start_time = (new DateTime($formValues['startTime']))
408
                    ->format(DATE_ATOM);
409
                $schema->timezone = date_default_timezone_get();
410
                $schema->duration = (int) $formValues['duration'];
411
            }
412
413
            $schema->topic = $formValues['topic'];
414
            $schema->agenda = $formValues['agenda'];
415
416
            try {
417
                $schema->update();
418
                $webinar->setWebinarSchema($schema);
419
420
                $em = Database::getManager();
421
                $em->persist($webinar);
422
                $em->flush();
423
424
                Display::addFlash(
425
                    Display::return_message($this->get_lang('WebinarUpdated'), 'success')
426
                );
427
            } catch (Exception $exception) {
428
                Display::addFlash(
429
                    Display::return_message($exception->getMessage(), 'error')
430
                );
431
            }
432
        }
433
434
        $defaults = [
435
            'topic' => $schema->topic,
436
            'agenda' => $schema->agenda,
437
        ];
438
439
        if ($requiresDateAndDuration) {
440
            $defaults['startTime'] = $webinar->startDateTime->format('Y-m-d H:i');
441
            $defaults['duration'] = $schema->duration;
442
        }
443
444
        $form->setDefaults($defaults);
445
446
        return $form;
447
    }
448
449
    /**
450
     * Generates a meeting delete form and deletes the meeting on validation.
451
     *
452
     * @param Meeting $meeting
453
     * @param string  $returnURL where to redirect to on successful deletion
454
     *
455
     * @throws Exception
456
     *
457
     * @return FormValidator
458
     */
459
    public function getDeleteMeetingForm($meeting, $returnURL)
460
    {
461
        $id = $meeting->getMeetingId();
462
        $form = new FormValidator('delete', 'post', api_get_self().'?meetingId='.$id);
463
        $form->addButtonDelete($this->get_lang('DeleteMeeting'));
464
        if ($form->validate()) {
465
            $this->deleteMeeting($meeting, $returnURL);
466
        }
467
468
        return $form;
469
    }
470
471
    public function getDeleteWebinarForm(Webinar $webinar, string $returnURL): FormValidator
472
    {
473
        $id = $webinar->getMeetingId();
474
        $form = new FormValidator('delete', 'post', api_get_self()."?meetingId=$id");
475
        $form->addButtonDelete($this->get_lang('DeleteWebinar'));
476
477
        if ($form->validate()) {
478
            $this->deleteWebinar($webinar, $returnURL);
479
        }
480
481
        return $form;
482
    }
483
484
    /**
485
     * @param Meeting $meeting
486
     * @param string  $returnURL
487
     *
488
     * @return false
489
     */
490
    public function deleteMeeting($meeting, $returnURL)
491
    {
492
        if (null === $meeting) {
493
            return false;
494
        }
495
496
        $em = Database::getManager();
497
        try {
498
            // No need to delete a instant meeting.
499
            if (\Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT != $meeting->getMeetingInfoGet()->type) {
500
                $meeting->getMeetingInfoGet()->delete();
501
            }
502
503
            $em->remove($meeting);
504
            $em->flush();
505
506
            Display::addFlash(
507
                Display::return_message($this->get_lang('MeetingDeleted'), 'confirm')
508
            );
509
            api_location($returnURL);
510
        } catch (Exception $exception) {
511
            $this->handleException($exception);
512
        }
513
    }
514
515
    public function deleteWebinar(Webinar $webinar, string $returnURL)
516
    {
517
        $em = Database::getManager();
518
519
        try {
520
            $webinar->getWebinarSchema()->delete();
521
522
            $em->remove($webinar);
523
            $em->flush();
524
525
            Display::addFlash(
526
                Display::return_message($this->get_lang('WebinarDeleted'), 'success')
527
            );
528
529
            api_location($returnURL);
530
        } catch (Exception $exception) {
531
            $this->handleException($exception);
532
        }
533
    }
534
535
    /**
536
     * @param Exception $exception
537
     */
538
    public function handleException($exception)
539
    {
540
        if ($exception instanceof Exception) {
0 ignored issues
show
introduced by
$exception is always a sub-type of Exception.
Loading history...
541
            $error = json_decode($exception->getMessage());
542
            $message = $exception->getMessage();
543
            if ($error->message) {
544
                $message = $error->message;
545
            }
546
            Display::addFlash(
547
                Display::return_message($message, 'error')
548
            );
549
        }
550
    }
551
552
    /**
553
     * Generates a registrant list update form listing course and session users.
554
     * Updates the list on validation.
555
     *
556
     * @param Meeting $meeting
557
     *
558
     * @throws Exception
559
     *
560
     * @return FormValidator
561
     */
562
    public function getRegisterParticipantForm($meeting)
563
    {
564
        $form = new FormValidator('register', 'post', $_SERVER['REQUEST_URI']);
565
        $userIdSelect = $form->addSelect('userIds', $this->get_lang('RegisteredUsers'));
566
        $userIdSelect->setMultiple(true);
567
        $form->addButtonSend($this->get_lang('UpdateRegisteredUserList'));
568
569
        $selfRegistrationUrl = api_get_path(WEB_PLUGIN_PATH)
570
            .'zoom/subscription.php?meetingId='.$meeting->getMeetingId();
571
572
        $form->addHtml(
573
            '<div class="form-group"><div class="col-sm-8 col-sm-offset-2">
574
                <hr style="margin-top: 0;">
575
                <label for="frm-registration__txt-self-registration">'
576
            .$this->get_lang('UrlForSelfRegistration').'</label>
577
                <div class="input-group">
578
                    <input type="text" class="form-control" id="frm-registration__txt-self-registration" value="'
579
            .$selfRegistrationUrl.'">
580
                    <span class="input-group-btn">
581
                        <button class="btn btn-default" type="button"
582
                         onclick="copyTextToClipBoard(\'frm-registration__txt-self-registration\');">'
583
            .$this->get_lang('CopyTextToClipboard').'</button>
584
                    </span>
585
                </div>
586
            </div></div>'
587
        );
588
589
        $users = $meeting->getRegistrableUsers();
590
        foreach ($users as $user) {
591
            $userIdSelect->addOption(
592
                api_get_person_name($user->getFirstname(), $user->getLastname()),
593
                $user->getId()
594
            );
595
        }
596
597
        if ($form->validate()) {
598
            $selectedUserIds = $form->getSubmitValue('userIds');
599
            $selectedUsers = [];
600
            if (!empty($selectedUserIds)) {
601
                foreach ($users as $user) {
602
                    if (in_array($user->getId(), $selectedUserIds)) {
603
                        $selectedUsers[] = $user;
604
                    }
605
                }
606
            }
607
608
            try {
609
                $this->updateRegistrantList($meeting, $selectedUsers);
610
                Display::addFlash(
611
                    Display::return_message($this->get_lang('RegisteredUserListWasUpdated'), 'confirm')
612
                );
613
            } catch (Exception $exception) {
614
                Display::addFlash(
615
                    Display::return_message($exception->getMessage(), 'error')
616
                );
617
            }
618
        }
619
        $registeredUserIds = [];
620
        foreach ($meeting->getRegistrants() as $registrant) {
621
            $registeredUserIds[] = $registrant->getUser()->getId();
622
        }
623
        $userIdSelect->setSelected($registeredUserIds);
624
625
        return $form;
626
    }
627
628
    /**
629
     * Generates a meeting recording files management form.
630
     * Takes action on validation.
631
     *
632
     * @param Meeting $meeting
633
     *
634
     * @throws Exception
635
     *
636
     * @return FormValidator
637
     */
638
    public function getFileForm($meeting, $returnURL)
639
    {
640
        $form = new FormValidator('fileForm', 'post', $_SERVER['REQUEST_URI']);
641
        if (!$meeting->getRecordings()->isEmpty()) {
642
            $fileIdSelect = $form->addSelect('fileIds', get_lang('Files'));
643
            $fileIdSelect->setMultiple(true);
644
            $recordingList = $meeting->getRecordings();
645
            foreach ($recordingList as &$recording) {
646
                // $recording->instanceDetails = $plugin->getPastMeetingInstanceDetails($instance->uuid);
647
                $options = [];
648
                $recordings = $recording->getRecordingMeeting()->recording_files;
649
                foreach ($recordings as $file) {
650
                    $options[] = [
651
                        'text' => sprintf(
652
                            '%s.%s (%s)',
653
                            $file->recording_type,
654
                            $file->file_type,
655
                            $file->file_size
656
                        ),
657
                        'value' => $file->id,
658
                    ];
659
                }
660
                $fileIdSelect->addOptGroup(
661
                    $options,
662
                    sprintf("%s (%s)", $recording->formattedStartTime, $recording->formattedDuration)
663
                );
664
            }
665
            $actions = [];
666
            if ($meeting->isCourseMeeting()) {
667
                $actions['CreateLinkInCourse'] = $this->get_lang('CreateLinkInCourse');
668
                $actions['CopyToCourse'] = $this->get_lang('CopyToCourse');
669
            }
670
            $actions['DeleteFile'] = $this->get_lang('DeleteFile');
671
            $form->addRadio(
672
                'action',
673
                get_lang('Action'),
674
                $actions
675
            );
676
            $form->addButtonUpdate($this->get_lang('DoIt'));
677
            if ($form->validate()) {
678
                $action = $form->getSubmitValue('action');
679
                $idList = $form->getSubmitValue('fileIds');
680
681
                foreach ($recordingList as $recording) {
682
                    $recordings = $recording->getRecordingMeeting()->recording_files;
683
684
                    foreach ($recordings as $file) {
685
                        if (in_array($file->id, $idList)) {
686
                            $name = sprintf(
687
                                $this->get_lang('XRecordingOfMeetingXFromXDurationXDotX'),
688
                                $file->recording_type,
689
                                $meeting->getId(),
690
                                $recording->formattedStartTime,
691
                                $recording->formattedDuration,
692
                                $file->file_type
693
                            );
694
                            if ('CreateLinkInCourse' === $action && $meeting->isCourseMeeting()) {
695
                                try {
696
                                    $this->createLinkToFileInCourse($meeting, $file, $name);
697
                                    Display::addFlash(
698
                                        Display::return_message(
699
                                            $this->get_lang('LinkToFileWasCreatedInCourse'),
700
                                            'success'
701
                                        )
702
                                    );
703
                                } catch (Exception $exception) {
704
                                    Display::addFlash(
705
                                        Display::return_message($exception->getMessage(), 'error')
706
                                    );
707
                                }
708
                            } elseif ('CopyToCourse' === $action && $meeting->isCourseMeeting()) {
709
                                try {
710
                                    $this->copyFileToCourse($meeting, $file, $name);
711
                                    Display::addFlash(
712
                                        Display::return_message($this->get_lang('FileWasCopiedToCourse'), 'confirm')
713
                                    );
714
                                } catch (Exception $exception) {
715
                                    Display::addFlash(
716
                                        Display::return_message($exception->getMessage(), 'error')
717
                                    );
718
                                }
719
                            } elseif ('DeleteFile' === $action) {
720
                                try {
721
                                    $name = $file->recording_type;
722
                                    $file->delete();
723
                                    Display::addFlash(
724
                                        Display::return_message($this->get_lang('FileWasDeleted').': '.$name, 'confirm')
725
                                    );
726
                                } catch (Exception $exception) {
727
                                    Display::addFlash(
728
                                        Display::return_message($exception->getMessage(), 'error')
729
                                    );
730
                                }
731
                            }
732
                        }
733
                    }
734
                }
735
                api_location($returnURL);
736
            }
737
        }
738
739
        return $form;
740
    }
741
742
    /**
743
     * Adds to the meeting course documents a link to a meeting instance recording file.
744
     *
745
     * @param Meeting       $meeting
746
     * @param RecordingFile $file
747
     * @param string        $name
748
     *
749
     * @throws Exception
750
     */
751
    public function createLinkToFileInCourse($meeting, $file, $name)
752
    {
753
        $course = $meeting->getCourse();
754
        if (null === $course) {
755
            throw new Exception('This meeting is not linked to a course');
756
        }
757
        $courseInfo = api_get_course_info_by_id($course->getId());
758
        if (empty($courseInfo)) {
759
            throw new Exception('This meeting is not linked to a valid course');
760
        }
761
        $path = '/zoom_meeting_recording_file_'.$file->id.'.'.$file->file_type;
762
        $docId = DocumentManager::addCloudLink($courseInfo, $path, $file->play_url, $name);
763
        if (!$docId) {
764
            throw new Exception(get_lang(DocumentManager::cloudLinkExists($courseInfo, $path, $file->play_url) ? 'UrlAlreadyExists' : 'ErrorAddCloudLink'));
765
        }
766
    }
767
768
    /**
769
     * Copies a recording file to a meeting's course.
770
     *
771
     * @param Meeting       $meeting
772
     * @param RecordingFile $file
773
     * @param string        $name
774
     *
775
     * @throws Exception
776
     */
777
    public function copyFileToCourse($meeting, $file, $name)
778
    {
779
        $course = $meeting->getCourse();
780
        if (null === $course) {
781
            throw new Exception('This meeting is not linked to a course');
782
        }
783
        $courseInfo = api_get_course_info_by_id($course->getId());
784
        if (empty($courseInfo)) {
785
            throw new Exception('This meeting is not linked to a valid course');
786
        }
787
        $tmpFile = tmpfile();
788
        if (false === $tmpFile) {
789
            throw new Exception('tmpfile() returned false');
790
        }
791
        $curl = curl_init($file->getFullDownloadURL($this->jwtClient->token));
792
        if (false === $curl) {
793
            throw new Exception('Could not init curl: '.curl_error($curl));
794
        }
795
        if (!curl_setopt_array(
796
            $curl,
797
            [
798
                CURLOPT_FILE => $tmpFile,
799
                CURLOPT_FOLLOWLOCATION => true,
800
                CURLOPT_MAXREDIRS => 10,
801
                CURLOPT_TIMEOUT => 120,
802
            ]
803
        )) {
804
            throw new Exception("Could not set curl options: ".curl_error($curl));
805
        }
806
        if (false === curl_exec($curl)) {
807
            throw new Exception("curl_exec failed: ".curl_error($curl));
808
        }
809
810
        $sessionId = 0;
811
        $session = $meeting->getSession();
812
        if (null !== $session) {
813
            $sessionId = $session->getId();
814
        }
815
816
        $groupId = 0;
817
        $group = $meeting->getGroup();
818
        if (null !== $group) {
819
            $groupId = $group->getIid();
820
        }
821
822
        $newPath = handle_uploaded_document(
823
            $courseInfo,
824
            [
825
                'name' => $name,
826
                'tmp_name' => stream_get_meta_data($tmpFile)['uri'],
827
                'size' => filesize(stream_get_meta_data($tmpFile)['uri']),
828
                'from_file' => true,
829
                'move_file' => true,
830
                'type' => $file->file_type,
831
            ],
832
            api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document',
833
            '/',
834
            api_get_user_id(),
835
            $groupId,
836
            null,
837
            0,
838
            'overwrite',
839
            true,
840
            false,
841
            null,
842
            $sessionId,
843
            true
844
        );
845
846
        fclose($tmpFile);
847
        if (false === $newPath) {
848
            throw new Exception('Could not handle uploaded document');
849
        }
850
    }
851
852
    /**
853
     * Generates a form to fast and easily create and start an instant meeting.
854
     * On validation, create it then redirect to it and exit.
855
     *
856
     * @return FormValidator
857
     */
858
    public function getCreateInstantMeetingForm(
859
        User $user,
860
        Course $course,
861
        CGroupInfo $group = null,
862
        Session $session = null
863
    ) {
864
        $extraUrl = '';
865
        if (!empty($course)) {
866
            $extraUrl = api_get_cidreq();
867
        }
868
        $form = new FormValidator('createInstantMeetingForm', 'post', api_get_self().'?'.$extraUrl, '_blank');
869
        $form->addButton('startButton', $this->get_lang('StartInstantMeeting'), 'video-camera', 'primary');
870
        if ($form->validate()) {
871
            try {
872
                $this->startInstantMeeting($this->get_lang('InstantMeeting'), $user, $course, $group, $session);
873
            } catch (Exception $exception) {
874
                Display::addFlash(
875
                    Display::return_message($exception->getMessage(), 'error')
876
                );
877
            }
878
        }
879
880
        return $form;
881
    }
882
883
    /**
884
     * Generates a form to schedule a meeting.
885
     * On validation, creates it and redirects to its page.
886
     *
887
     * @throws Exception
888
     *
889
     * @return FormValidator
890
     */
891
    public function getScheduleMeetingForm(User $user, Course $course = null, CGroupInfo $group = null, Session $session = null)
892
    {
893
        $extraUrl = '';
894
        if (!empty($course)) {
895
            $extraUrl = api_get_cidreq();
896
        }
897
        $form = new FormValidator('scheduleMeetingForm', 'post', api_get_self().'?'.$extraUrl);
898
        $form->addHeader($this->get_lang('ScheduleAMeeting'));
899
900
        $form->addSelect(
901
            'conference_type',
902
            $this->get_lang('ConferenceType'),
903
            [
904
                'meeting' => $this->get_lang('Meeting'),
905
                'webinar' => $this->get_lang('Webinar'),
906
            ]
907
        );
908
        $form->addRule('conference_type', get_lang('ThisFieldIsRequired'), 'required');
909
910
        $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
911
        $form->setRequired($startTimeDatePicker);
912
913
        $form->addText('topic', $this->get_lang('Topic'), true);
914
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
915
916
        $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
917
        $form->setRequired($durationNumeric);
918
919
        if (null === $course && 'true' === $this->get('enableGlobalConference')) {
920
            $options = [];
921
            $options['everyone'] = $this->get_lang('ForEveryone');
922
            $options['registered_users'] = $this->get_lang('SomeUsers');
923
            if (!empty($options)) {
924
                if (1 === count($options)) {
925
                    $form->addHidden('type', key($options));
926
                } else {
927
                    $form->addSelect('type', $this->get_lang('AudienceType'), $options);
928
                }
929
            }
930
        } else {
931
            // To course
932
            $form->addHidden('type', 'course');
933
        }
934
935
        /*
936
       // $passwordText = $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
937
       if (null !== $course) {
938
           $registrationOptions = [
939
               'RegisterAllCourseUsers' => $this->get_lang('RegisterAllCourseUsers'),
940
           ];
941
           $groups = GroupManager::get_groups();
942
           if (!empty($groups)) {
943
               $registrationOptions['RegisterTheseGroupMembers'] = get_lang('RegisterTheseGroupMembers');
944
           }
945
           $registrationOptions['RegisterNoUser'] = $this->get_lang('RegisterNoUser');
946
           $userRegistrationRadio = $form->addRadio(
947
               'userRegistration',
948
               $this->get_lang('UserRegistration'),
949
               $registrationOptions
950
           );
951
           $groupOptions = [];
952
           foreach ($groups as $group) {
953
               $groupOptions[$group['id']] = $group['name'];
954
           }
955
           $groupIdsSelect = $form->addSelect(
956
               'groupIds',
957
               $this->get_lang('RegisterTheseGroupMembers'),
958
               $groupOptions
959
           );
960
           $groupIdsSelect->setMultiple(true);
961
           if (!empty($groups)) {
962
               $jsCode = sprintf(
963
                   "getElementById('%s').parentNode.parentNode.parentNode.style.display = getElementById('%s').checked ? 'block' : 'none'",
964
                   $groupIdsSelect->getAttribute('id'),
965
                   $userRegistrationRadio->getelements()[1]->getAttribute('id')
966
               );
967
968
               $form->setAttribute('onchange', $jsCode);
969
           }
970
       }*/
971
972
        $accountEmails = $this->getAccountEmails();
973
974
        if (!empty($accountEmails)) {
975
            $form->addSelect('account_email', $this->get_lang('AccountEmail'), $accountEmails);
976
        }
977
978
        $form->addButtonCreate(get_lang('Save'));
979
980
        if ($form->validate()) {
981
            $formValues = $form->exportValues();
982
            $conferenceType = $formValues['conference_type'];
983
            $password = substr(uniqid('z', true), 0, 10);
984
985
            switch ($formValues['type']) {
986
                case 'everyone':
987
                    $user = null;
988
                    $group = null;
989
                    $course = null;
990
                    $session = null;
991
992
                    break;
993
                case 'registered_users':
994
                    //$user = null;
995
                    $course = null;
996
                    $session = null;
997
998
                    break;
999
                case 'course':
1000
                    $user = null;
1001
                    //$course = null;
1002
                    //$session = null;
1003
1004
                    break;
1005
            }
1006
1007
            $accountEmail = $formValues['account_email'] ?? null;
1008
            $accountEmail = $accountEmail && in_array($accountEmail, $accountEmails) ? $accountEmail : null;
1009
1010
            try {
1011
                $startTime = new DateTime($formValues['startTime']);
1012
1013
                if ('meeting' === $conferenceType) {
1014
                    $newMeeting = $this->createScheduleMeeting(
1015
                        $user,
1016
                        $course,
1017
                        $group,
1018
                        $session,
1019
                        $startTime,
1020
                        $formValues['duration'],
1021
                        $formValues['topic'],
1022
                        $formValues['agenda'],
1023
                        $password,
1024
                        $accountEmail
1025
                    );
1026
1027
                    Display::addFlash(
1028
                        Display::return_message($this->get_lang('NewWebinarCreated'))
1029
                    );
1030
                } elseif ('webinar' === $conferenceType) {
1031
                    $newMeeting = $this->createScheduleWebinar(
1032
                        $user,
1033
                        $course,
1034
                        $group,
1035
                        $session,
1036
                        $startTime,
1037
                        $formValues['duration'],
1038
                        $formValues['topic'],
1039
                        $formValues['agenda'],
1040
                        $password,
1041
                        $accountEmail
1042
                    );
1043
1044
                    Display::addFlash(
1045
                        Display::return_message($this->get_lang('NewMeetingCreated'))
1046
                    );
1047
                } else {
1048
                    throw new Exception('Invalid conference type');
1049
                }
1050
1051
                if ($newMeeting->isCourseMeeting()) {
1052
                    if ('RegisterAllCourseUsers' === $form->getSubmitValue('userRegistration')) {
1053
                        $this->registerAllCourseUsers($newMeeting);
1054
                        Display::addFlash(
1055
                            Display::return_message($this->get_lang('AllCourseUsersWereRegistered'))
1056
                        );
1057
                    } elseif ('RegisterTheseGroupMembers' === $form->getSubmitValue('userRegistration')) {
1058
                        $userIds = [];
1059
                        foreach ($form->getSubmitValue('groupIds') as $groupId) {
1060
                            $userIds = array_unique(array_merge($userIds, GroupManager::get_users($groupId)));
1061
                        }
1062
                        $users = Database::getManager()->getRepository('ChamiloUserBundle:User')->findBy(
1063
                            ['id' => $userIds]
1064
                        );
1065
                        $this->registerUsers($newMeeting, $users);
1066
                        Display::addFlash(
1067
                            Display::return_message($this->get_lang('GroupUsersWereRegistered'))
1068
                        );
1069
                    }
1070
                }
1071
                api_location('meeting.php?meetingId='.$newMeeting->getMeetingId().'&'.$extraUrl);
1072
            } catch (Exception $exception) {
1073
                Display::addFlash(
1074
                    Display::return_message($exception->getMessage(), 'error')
1075
                );
1076
            }
1077
        } else {
1078
            $form->setDefaults(
1079
                [
1080
                    'duration' => 60,
1081
                    'userRegistration' => 'RegisterAllCourseUsers',
1082
                ]
1083
            );
1084
        }
1085
1086
        return $form;
1087
    }
1088
1089
    /**
1090
     * Return the current global meeting (create it if needed).
1091
     *
1092
     * @throws Exception
1093
     *
1094
     * @return string
1095
     */
1096
    public function getGlobalMeeting()
1097
    {
1098
        foreach ($this->getMeetingRepository()->unfinishedGlobalMeetings() as $meeting) {
1099
            return $meeting;
1100
        }
1101
1102
        return $this->createGlobalMeeting();
1103
    }
1104
1105
    /**
1106
     * @return MeetingRepository|EntityRepository
1107
     */
1108
    public static function getMeetingRepository()
1109
    {
1110
        return Database::getManager()->getRepository(Meeting::class);
1111
    }
1112
1113
    /**
1114
     * Returns the URL to enter (start or join) a meeting or null if not possible to enter the meeting,
1115
     * The returned URL depends on the meeting current status (waiting, started or finished) and the current user.
1116
     *
1117
     * @param Meeting $meeting
1118
     *
1119
     * @throws OptimisticLockException
1120
     * @throws Exception
1121
     *
1122
     * @return string|null
1123
     */
1124
    public function getStartOrJoinMeetingURL(Meeting $meeting)
1125
    {
1126
        if ($meeting instanceof Webinar) {
1127
            $status = 'started';
1128
        } else {
1129
            $status = $meeting->getMeetingInfoGet()->status;
1130
        }
1131
1132
        $userId = api_get_user_id();
1133
        $currentUser = api_get_user_entity($userId);
1134
        $isGlobal = 'true' === $this->get('enableGlobalConference') && $meeting->isGlobalMeeting();
1135
1136
        switch ($status) {
1137
            case 'ended':
1138
                if ($this->userIsConferenceManager($meeting)) {
1139
                    return $meeting->getMeetingInfoGet()->start_url;
1140
                }
1141
                break;
1142
            case 'waiting':
1143
                // Zoom does not allow for a new meeting to be started on first participant join.
1144
                // It requires the host to start the meeting first.
1145
                // Therefore for global meetings we must make the first participant the host
1146
                // that is use start_url rather than join_url.
1147
                // the participant will not be registered and will appear as the Zoom user account owner.
1148
                // For course and user meetings, only the host can start the meeting.
1149
                if ($this->userIsConferenceManager($meeting)) {
1150
                    return $meeting->getMeetingInfoGet()->start_url;
1151
                }
1152
1153
                break;
1154
            case 'started':
1155
                // User per conference.
1156
                if ($currentUser === $meeting->getUser()) {
1157
                    return $meeting instanceof Webinar
1158
                        ? $meeting->getWebinarSchema()->start_url
1159
                        : $meeting->getMeetingInfoGet()->join_url;
1160
                }
1161
1162
                // The participant is not registered, he can join only the global meeting (automatic registration).
1163
                if ($isGlobal) {
1164
                    return $this->registerUser($meeting, $currentUser)->getCreatedRegistration()->join_url;
1165
                }
1166
1167
                if ($meeting->isCourseMeeting()) {
1168
                    if ($this->userIsCourseConferenceManager()) {
1169
                        return $meeting instanceof Webinar
1170
                            ? $meeting->getWebinarSchema()->start_url
1171
                            : $meeting->getMeetingInfoGet()->start_url;
1172
                    }
1173
1174
                    $sessionId = api_get_session_id();
1175
                    $courseCode = api_get_course_id();
1176
1177
                    if (empty($sessionId)) {
1178
                        $isSubscribed = CourseManager::is_user_subscribed_in_course(
1179
                            $userId,
1180
                            $courseCode,
1181
                            false
1182
                        );
1183
                    } else {
1184
                        $isSubscribed = CourseManager::is_user_subscribed_in_course(
1185
                            $userId,
1186
                            $courseCode,
1187
                            true,
1188
                            $sessionId
1189
                        );
1190
                    }
1191
1192
                    if ($isSubscribed) {
1193
                        if ($meeting->isCourseGroupMeeting()) {
1194
                            $groupInfo = GroupManager::get_group_properties($meeting->getGroup()->getIid(), true);
1195
                            $isInGroup = GroupManager::is_user_in_group($userId, $groupInfo);
1196
                            if (false === $isInGroup) {
1197
                                throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
1198
                            }
1199
                        }
1200
1201
                        if (!$meeting instanceof Webinar
1202
                            && \Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT == $meeting->getMeetingInfoGet()->type
1203
                        ) {
1204
                            return $meeting->getMeetingInfoGet()->join_url;
1205
                        }
1206
1207
                        return $this->registerUser($meeting, $currentUser)->getCreatedRegistration()->join_url;
1208
                    }
1209
1210
                    throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
1211
                }
1212
1213
                //if ('true' === $this->get('enableParticipantRegistration')) {
1214
                    //if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {
1215
                    // the participant must be registered
1216
                    $registrant = $meeting->getRegistrantByUser($currentUser);
1217
                    if (null == $registrant) {
1218
                        throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
1219
                    }
1220
1221
                    // the participant is registered
1222
                    return $registrant->getCreatedRegistration()->join_url;
1223
                //}
1224
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

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