Passed
Push — 1.11.x ( 5dd04e...e08fc6 )
by Julito
21:38
created

ZoomPlugin::getMeetingRepository()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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