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

ZoomPlugin::getRegisterPresenterForm()   B

Complexity

Conditions 9
Paths 24

Size

Total Lines 52
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
c 0
b 0
f 0
dl 0
loc 52
rs 8.0555
cc 9
nc 24
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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