Passed
Pull Request — 1.11.x (#4160)
by Angel Fernando Quiroz
08:51
created

ZoomPlugin::createScheduleMeeting()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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