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