Passed
Pull Request — 1.11.x (#4151)
by Angel Fernando Quiroz
09:19
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.3',
52
            'Sébastien Ducoulombier, Julio Montoya',
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