Passed
Pull Request — 1.11.x (#4151)
by Angel Fernando Quiroz
09:24
created

ZoomPlugin::createScheduleMeeting()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
c 0
b 0
f 0
dl 0
loc 32
rs 9.7
cc 2
nc 2
nop 11

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