Passed
Push — 1.11.x ( b4ce57...e435f5 )
by Julito
09:28
created

ZoomPlugin::getStartOrJoinMeetingURL()   C

Complexity

Conditions 13
Paths 22

Size

Total Lines 52
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 13
eloc 25
c 2
b 1
f 0
nc 22
nop 1
dl 0
loc 52
rs 6.6166

How to fix   Long Method    Complexity   

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