Passed
Pull Request — 1.11.x (#4151)
by Angel Fernando Quiroz
15:35 queued 07:02
created

ZoomPlugin::startInstantMeeting()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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