Passed
Push — 1.11.x ( b08d33...fc80cf )
by Angel Fernando Quiroz
08:20 queued 14s
created

ZoomPlugin::createScheduleWebinar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
c 0
b 0
f 0
dl 0
loc 35
rs 9.7333
cc 2
nc 2
nop 12

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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