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

ZoomPlugin::getEditMeetingForm()   B

Complexity

Conditions 6
Paths 52

Size

Total Lines 61
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 45
c 0
b 0
f 0
dl 0
loc 61
rs 8.5777
cc 6
nc 52
nop 1

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\Session;
7
use Chamilo\CourseBundle\Entity\CGroupInfo;
8
use Chamilo\PluginBundle\Zoom\API\BaseMeetingTrait;
9
use Chamilo\PluginBundle\Zoom\API\JWTClient;
10
use Chamilo\PluginBundle\Zoom\API\MeetingInfoGet;
11
use Chamilo\PluginBundle\Zoom\API\MeetingRegistrant;
12
use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
13
use Chamilo\PluginBundle\Zoom\API\RecordingFile;
14
use Chamilo\PluginBundle\Zoom\API\RecordingList;
15
use Chamilo\PluginBundle\Zoom\API\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