Passed
Push — 1.11.x ( 0611ec...e69264 )
by Julito
13:55
created

ZoomPlugin::copyFileToCourse()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 57
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 41
nc 8
nop 3
dl 0
loc 57
rs 8.0195
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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