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

ZoomPlugin::createScheduleWebinar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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