chamilo/chamilo-lms

View on GitHub
public/plugin/zoom/lib/ZoomPlugin.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

/* For licensing terms, see /license.txt */

use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CourseBundle\Entity\CGroup;
use Chamilo\PluginBundle\Zoom\API\JWTClient;
use Chamilo\PluginBundle\Zoom\API\MeetingInfoGet;
use Chamilo\PluginBundle\Zoom\API\MeetingRegistrant;
use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
use Chamilo\PluginBundle\Zoom\API\RecordingFile;
use Chamilo\PluginBundle\Zoom\API\RecordingList;
use Chamilo\PluginBundle\Zoom\Meeting;
use Chamilo\PluginBundle\Zoom\MeetingActivity;
use Chamilo\PluginBundle\Zoom\MeetingRepository;
use Chamilo\PluginBundle\Zoom\Recording;
use Chamilo\PluginBundle\Zoom\RecordingRepository;
use Chamilo\PluginBundle\Zoom\Registrant;
use Chamilo\PluginBundle\Zoom\RegistrantRepository;
use Chamilo\UserBundle\Entity\User;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\ToolsException;
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
use Chamilo\CoreBundle\Component\Utils\ToolIcon;

/**
 * Class ZoomPlugin. Integrates Zoom meetings in courses.
 */
class ZoomPlugin extends Plugin
{
    const RECORDING_TYPE_CLOUD = 'cloud';
    const RECORDING_TYPE_LOCAL = 'local';
    const RECORDING_TYPE_NONE = 'none';
    public $isCoursePlugin = true;

    /**
     * @var JWTClient
     */
    private $jwtClient;

    /**
     * ZoomPlugin constructor.
     * {@inheritdoc}
     * Initializes the API JWT client and the entity repositories.
     */
    public function __construct()
    {
        parent::__construct(
            '0.3',
            'Sébastien Ducoulombier, Julio Montoya',
            [
                'tool_enable' => 'boolean',
                'apiKey' => 'text',
                'apiSecret' => 'text',
                'verificationToken' => 'text',
                'enableParticipantRegistration' => 'boolean',
                'enableCloudRecording' => [
                    'type' => 'select',
                    'options' => [
                        self::RECORDING_TYPE_CLOUD => 'Cloud',
                        self::RECORDING_TYPE_LOCAL => 'Local',
                        self::RECORDING_TYPE_NONE => get_lang('None'),
                    ],
                ],
                'enableGlobalConference' => 'boolean',
                'globalConferenceAllowRoles' => [
                    'type' => 'select',
                    'options' => [
                        PLATFORM_ADMIN => get_lang('Administrator'),
                        COURSEMANAGER => get_lang('Teacher'),
                        STUDENT => get_lang('Student'),
                        STUDENT_BOSS => get_lang('StudentBoss'),
                    ],
                    'attributes' => ['multiple' => 'multiple'],
                ],
            ]
        );

        $this->isAdminPlugin = true;

        if ($this->isEnabled(true)) {
            $this->jwtClient = new JWTClient($this->get('apiKey'), $this->get('apiSecret'));
        }
    }

    /**
     * Caches and returns an instance of this class.
     *
     * @return ZoomPlugin the instance to use
     */
    public static function create()
    {
        static $instance = null;

        return $instance ? $instance : $instance = new self();
    }

    /**
     * @return bool
     */
    public static function currentUserCanJoinGlobalMeeting()
    {
        $user = api_get_user_entity(api_get_user_id());

        if (null === $user) {
            return false;
        }

        return
            'true' === api_get_plugin_setting('zoom', 'enableGlobalConference')
            && in_array(
                (api_is_platform_admin() ? PLATFORM_ADMIN : $user->getStatus()),
                (array) api_get_plugin_setting('zoom', 'globalConferenceAllowRoles')
            );
    }

    /**
     * @return array
     */
    public function getProfileBlockItems()
    {
        $elements = $this->meetingsToWhichCurrentUserIsRegisteredComingSoon();
        $addMeetingLink = false;
        if (self::currentUserCanJoinGlobalMeeting()) {
            $addMeetingLink = true;
        }

        if ($addMeetingLink) {
            $elements[$this->get_lang('Meetings')] = api_get_path(WEB_PLUGIN_PATH).'zoom/meetings.php';
        }

        $items = [];
        foreach ($elements as $title => $link) {
            $items[] = [
                'class' => 'video-conference',
                'icon' => Display::getMdiIcon(ToolIcon::VIDEOCONFERENCE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('VideoConference')),
                'link' => $link,
                'title' => $title,
            ];
        }

        return $items;
    }

    /**
     * @return array [ $title => $link ]
     */
    public function meetingsToWhichCurrentUserIsRegisteredComingSoon()
    {
        $linkTemplate = api_get_path(WEB_PLUGIN_PATH).'zoom/join_meeting.php?meetingId=%s';
        $user = api_get_user_entity(api_get_user_id());
        $meetings = self::getRegistrantRepository()->meetingsComingSoonRegistrationsForUser($user);
        $items = [];
        foreach ($meetings as $registrant) {
            $meeting = $registrant->getMeeting();
            $items[sprintf(
                $this->get_lang('DateMeetingTitle'),
                $meeting->formattedStartTime,
                $meeting->getMeetingInfoGet()->topic
            )] = sprintf($linkTemplate, $meeting->getId());
        }

        return $items;
    }

    /**
     * @return RegistrantRepository|EntityRepository
     */
    public static function getRegistrantRepository()
    {
        return Database::getManager()->getRepository(Registrant::class);
    }

    /**
     * Creates this plugin's related tables in the internal database.
     * Installs course fields in all courses.
     *
     * @throws ToolsException
     */
    public function install()
    {
        $schemaManager = Database::getManager()->getConnection()->getSchemaManager();

        $tablesExists = $schemaManager->tablesExist(
            [
                'plugin_zoom_meeting',
                'plugin_zoom_meeting_activity',
                'plugin_zoom_recording',
                'plugin_zoom_registrant',
            ]
        );

        if ($tablesExists) {
            return;
        }
        (new SchemaTool(Database::getManager()))->createSchema(
            [
                Database::getManager()->getClassMetadata(Meeting::class),
                Database::getManager()->getClassMetadata(MeetingActivity::class),
                Database::getManager()->getClassMetadata(Recording::class),
                Database::getManager()->getClassMetadata(Registrant::class),
            ]
        );
        $this->install_course_fields_in_all_courses();
    }

    /**
     * Drops this plugins' related tables from the internal database.
     * Uninstalls course fields in all courses().
     */
    public function uninstall()
    {
        (new SchemaTool(Database::getManager()))->dropSchema(
            [
                Database::getManager()->getClassMetadata(Meeting::class),
                Database::getManager()->getClassMetadata(MeetingActivity::class),
                Database::getManager()->getClassMetadata(Recording::class),
                Database::getManager()->getClassMetadata(Registrant::class),
            ]
        );
        $this->uninstall_course_fields_in_all_courses();
    }

    /**
     * Generates the search form to include in the meeting list administration page.
     * The form has DatePickers 'start' and 'end' and Checkbox 'reloadRecordingLists'.
     *
     * @return FormValidator the form
     */
    public function getAdminSearchForm()
    {
        $form = new FormValidator('search');
        $form->addHeader($this->get_lang('SearchMeeting'));
        $form->addDatePicker('start', get_lang('StartDate'));
        $form->addDatePicker('end', get_lang('EndDate'));
        $form->addButtonSearch(get_lang('Search'));
        $oneMonth = new DateInterval('P1M');
        if ($form->validate()) {
            try {
                $start = new DateTime($form->getSubmitValue('start'));
            } catch (Exception $exception) {
                $start = new DateTime();
                $start->sub($oneMonth);
            }
            try {
                $end = new DateTime($form->getSubmitValue('end'));
            } catch (Exception $exception) {
                $end = new DateTime();
                $end->add($oneMonth);
            }
        } else {
            $start = new DateTime();
            $start->sub($oneMonth);
            $end = new DateTime();
            $end->add($oneMonth);
        }
        try {
            $form->setDefaults(
                [
                    'start' => $start->format('Y-m-d'),
                    'end' => $end->format('Y-m-d'),
                ]
            );
        } catch (Exception $exception) {
            error_log(join(':', [__FILE__, __LINE__, $exception]));
        }

        return $form;
    }

    /**
     * Generates a meeting edit form and updates the meeting on validation.
     *
     * @param Meeting $meeting the meeting
     *
     * @throws Exception
     *
     * @return FormValidator
     */
    public function getEditMeetingForm($meeting)
    {
        $meetingInfoGet = $meeting->getMeetingInfoGet();
        $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
        $form->addHeader($this->get_lang('UpdateMeeting'));
        $form->addText('topic', $this->get_lang('Topic'));
        if ($meeting->requiresDateAndDuration()) {
            $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
            $form->setRequired($startTimeDatePicker);
            $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
            $form->setRequired($durationNumeric);
        }
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
        //$form->addLabel(get_lang('Password'), $meeting->getMeetingInfoGet()->password);
        // $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
        $form->addButtonUpdate(get_lang('Update'));
        if ($form->validate()) {
            if ($meeting->requiresDateAndDuration()) {
                $meetingInfoGet->start_time = (new DateTime($form->getSubmitValue('startTime')))->format(
                    DateTimeInterface::ISO8601
                );
                $meetingInfoGet->timezone = date_default_timezone_get();
                $meetingInfoGet->duration = (int) $form->getSubmitValue('duration');
            }
            $meetingInfoGet->topic = $form->getSubmitValue('topic');
            $meetingInfoGet->agenda = $form->getSubmitValue('agenda');
            try {
                $meetingInfoGet->update();
                $meeting->setMeetingInfoGet($meetingInfoGet);
                Database::getManager()->persist($meeting);
                Database::getManager()->flush();
                Display::addFlash(
                    Display::return_message($this->get_lang('MeetingUpdated'), 'confirm')
                );
            } catch (Exception $exception) {
                Display::addFlash(
                    Display::return_message($exception->getMessage(), 'error')
                );
            }
        }
        $defaults = [
            'topic' => $meetingInfoGet->topic,
            'agenda' => $meetingInfoGet->agenda,
        ];
        if ($meeting->requiresDateAndDuration()) {
            $defaults['startTime'] = $meeting->startDateTime->format('Y-m-d H:i');
            $defaults['duration'] = $meetingInfoGet->duration;
        }
        $form->setDefaults($defaults);

        return $form;
    }

    /**
     * Generates a meeting delete form and deletes the meeting on validation.
     *
     * @param Meeting $meeting
     * @param string  $returnURL where to redirect to on successful deletion
     *
     * @throws Exception
     *
     * @return FormValidator
     */
    public function getDeleteMeetingForm($meeting, $returnURL)
    {
        $id = $meeting->getMeetingId();
        $form = new FormValidator('delete', 'post', api_get_self().'?meetingId='.$id);
        $form->addButtonDelete($this->get_lang('DeleteMeeting'));
        if ($form->validate()) {
            $this->deleteMeeting($meeting, $returnURL);
        }

        return $form;
    }

    /**
     * @param Meeting $meeting
     * @param string  $returnURL
     *
     * @return false
     */
    public function deleteMeeting($meeting, $returnURL)
    {
        if (null === $meeting) {
            return false;
        }

        $em = Database::getManager();
        try {
            // No need to delete a instant meeting.
            if (\Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT != $meeting->getMeetingInfoGet()->type) {
                $meeting->getMeetingInfoGet()->delete();
            }

            $em->remove($meeting);
            $em->flush();

            Display::addFlash(
                Display::return_message($this->get_lang('MeetingDeleted'), 'confirm')
            );
            api_location($returnURL);
        } catch (Exception $exception) {
            $this->handleException($exception);
        }
    }

    /**
     * @param Exception $exception
     */
    public function handleException($exception)
    {
        if ($exception instanceof Exception) {
            $error = json_decode($exception->getMessage());
            $message = $exception->getMessage();
            if ($error->message) {
                $message = $error->message;
            }
            Display::addFlash(
                Display::return_message($message, 'error')
            );
        }
    }

    /**
     * Generates a registrant list update form listing course and session users.
     * Updates the list on validation.
     *
     * @param Meeting $meeting
     *
     * @throws Exception
     *
     * @return FormValidator
     */
    public function getRegisterParticipantForm($meeting)
    {
        $form = new FormValidator('register', 'post', $_SERVER['REQUEST_URI']);
        $userIdSelect = $form->addSelect('userIds', $this->get_lang('RegisteredUsers'));
        $userIdSelect->setMultiple(true);
        $form->addButtonSend($this->get_lang('UpdateRegisteredUserList'));

        $users = $meeting->getRegistrableUsers();
        foreach ($users as $user) {
            $userIdSelect->addOption(
                api_get_person_name($user->getFirstname(), $user->getLastname()),
                $user->getId()
            );
        }

        if ($form->validate()) {
            $selectedUserIds = $form->getSubmitValue('userIds');
            $selectedUsers = [];
            if (!empty($selectedUserIds)) {
                foreach ($users as $user) {
                    if (in_array($user->getId(), $selectedUserIds)) {
                        $selectedUsers[] = $user;
                    }
                }
            }

            try {
                $this->updateRegistrantList($meeting, $selectedUsers);
                Display::addFlash(
                    Display::return_message($this->get_lang('RegisteredUserListWasUpdated'), 'confirm')
                );
            } catch (Exception $exception) {
                Display::addFlash(
                    Display::return_message($exception->getMessage(), 'error')
                );
            }
        }
        $registeredUserIds = [];
        foreach ($meeting->getRegistrants() as $registrant) {
            $registeredUserIds[] = $registrant->getUser()->getId();
        }
        $userIdSelect->setSelected($registeredUserIds);

        return $form;
    }

    /**
     * Generates a meeting recording files management form.
     * Takes action on validation.
     *
     * @param Meeting $meeting
     *
     * @throws Exception
     *
     * @return FormValidator
     */
    public function getFileForm($meeting, $returnURL)
    {
        $form = new FormValidator('fileForm', 'post', $_SERVER['REQUEST_URI']);
        if (!$meeting->getRecordings()->isEmpty()) {
            $fileIdSelect = $form->addSelect('fileIds', get_lang('Files'));
            $fileIdSelect->setMultiple(true);
            $recordingList = $meeting->getRecordings();
            foreach ($recordingList as &$recording) {
                // $recording->instanceDetails = $plugin->getPastMeetingInstanceDetails($instance->uuid);
                $options = [];
                $recordings = $recording->getRecordingMeeting()->recording_files;
                foreach ($recordings as $file) {
                    $options[] = [
                        'text' => sprintf(
                            '%s.%s (%s)',
                            $file->recording_type,
                            $file->file_type,
                            $file->file_size
                        ),
                        'value' => $file->id,
                    ];
                }
                $fileIdSelect->addOptGroup(
                    $options,
                    sprintf("%s (%s)", $recording->formattedStartTime, $recording->formattedDuration)
                );
            }
            $actions = [];
            if ($meeting->isCourseMeeting()) {
                $actions['CreateLinkInCourse'] = $this->get_lang('CreateLinkInCourse');
                $actions['CopyToCourse'] = $this->get_lang('CopyToCourse');
            }
            $actions['DeleteFile'] = $this->get_lang('DeleteFile');
            $form->addRadio(
                'action',
                get_lang('Action'),
                $actions
            );
            $form->addButtonUpdate($this->get_lang('DoIt'));
            if ($form->validate()) {
                $action = $form->getSubmitValue('action');
                $idList = $form->getSubmitValue('fileIds');

                foreach ($recordingList as $recording) {
                    $recordings = $recording->getRecordingMeeting()->recording_files;

                    foreach ($recordings as $file) {
                        if (in_array($file->id, $idList)) {
                            $name = sprintf(
                                $this->get_lang('XRecordingOfMeetingXFromXDurationXDotX'),
                                $file->recording_type,
                                $meeting->getId(),
                                $recording->formattedStartTime,
                                $recording->formattedDuration,
                                $file->file_type
                            );
                            if ('CreateLinkInCourse' === $action && $meeting->isCourseMeeting()) {
                                try {
                                    $this->createLinkToFileInCourse($meeting, $file, $name);
                                    Display::addFlash(
                                        Display::return_message(
                                            $this->get_lang('LinkToFileWasCreatedInCourse'),
                                            'success'
                                        )
                                    );
                                } catch (Exception $exception) {
                                    Display::addFlash(
                                        Display::return_message($exception->getMessage(), 'error')
                                    );
                                }
                            } elseif ('CopyToCourse' === $action && $meeting->isCourseMeeting()) {
                                try {
                                    $this->copyFileToCourse($meeting, $file, $name);
                                    Display::addFlash(
                                        Display::return_message($this->get_lang('FileWasCopiedToCourse'), 'confirm')
                                    );
                                } catch (Exception $exception) {
                                    Display::addFlash(
                                        Display::return_message($exception->getMessage(), 'error')
                                    );
                                }
                            } elseif ('DeleteFile' === $action) {
                                try {
                                    $name = $file->recording_type;
                                    $file->delete();
                                    Display::addFlash(
                                        Display::return_message($this->get_lang('FileWasDeleted').': '.$name, 'confirm')
                                    );
                                } catch (Exception $exception) {
                                    Display::addFlash(
                                        Display::return_message($exception->getMessage(), 'error')
                                    );
                                }
                            }
                        }
                    }
                }
                api_location($returnURL);
            }
        }

        return $form;
    }

    /**
     * Adds to the meeting course documents a link to a meeting instance recording file.
     *
     * @param Meeting       $meeting
     * @param RecordingFile $file
     * @param string        $name
     *
     * @throws Exception
     */
    public function createLinkToFileInCourse($meeting, $file, $name)
    {
        $course = $meeting->getCourse();
        if (null === $course) {
            throw new Exception('This meeting is not linked to a course');
        }
        $courseInfo = api_get_course_info_by_id($course->getId());
        if (empty($courseInfo)) {
            throw new Exception('This meeting is not linked to a valid course');
        }
        /*$path = '/zoom_meeting_recording_file_'.$file->id.'.'.$file->file_type;
        $docId = DocumentManager::addCloudLink($courseInfo, $path, $file->play_url, $name);
        if (!$docId) {
            throw new Exception(get_lang(DocumentManager::cloudLinkExists($courseInfo, $path, $file->play_url) ? 'UrlAlreadyExists' : 'ErrorAddCloudLink'));
        }*/
    }

    /**
     * Copies a recording file to a meeting's course.
     *
     * @param Meeting       $meeting
     * @param RecordingFile $file
     * @param string        $name
     *
     * @throws Exception
     */
    public function copyFileToCourse($meeting, $file, $name)
    {
        $course = $meeting->getCourse();
        if (null === $course) {
            throw new Exception('This meeting is not linked to a course');
        }
        $courseInfo = api_get_course_info_by_id($course->getId());
        if (empty($courseInfo)) {
            throw new Exception('This meeting is not linked to a valid course');
        }
        $tmpFile = tmpfile();
        if (false === $tmpFile) {
            throw new Exception('tmpfile() returned false');
        }
        $curl = curl_init($file->getFullDownloadURL($this->jwtClient->token));
        if (false === $curl) {
            throw new Exception('Could not init curl: '.curl_error($curl));
        }
        if (!curl_setopt_array(
            $curl,
            [
                CURLOPT_FILE => $tmpFile,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_MAXREDIRS => 10,
                CURLOPT_TIMEOUT => 120,
            ]
        )) {
            throw new Exception("Could not set curl options: ".curl_error($curl));
        }
        if (false === curl_exec($curl)) {
            throw new Exception("curl_exec failed: ".curl_error($curl));
        }

        $sessionId = 0;
        $session = $meeting->getSession();
        if (null !== $session) {
            $sessionId = $session->getId();
        }

        $groupId = 0;
        $group = $meeting->getGroup();
        if (null !== $group) {
            $groupId = $group->getIid();
        }

        $newPath = null;
        /*$newPath = handle_uploaded_document(
            $courseInfo,
            [
                'name' => $name,
                'tmp_name' => stream_get_meta_data($tmpFile)['uri'],
                'size' => filesize(stream_get_meta_data($tmpFile)['uri']),
                'from_file' => true,
                'move_file' => true,
                'type' => $file->file_type,
            ],
            api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document',
            '/',
            api_get_user_id(),
            $groupId,
            null,
            0,
            'overwrite',
            true,
            false,
            null,
            $sessionId,
            true
        );*/

        fclose($tmpFile);
        if (false === $newPath) {
            throw new Exception('Could not handle uploaded document');
        }
    }

    /**
     * Generates a form to fast and easily create and start an instant meeting.
     * On validation, create it then redirect to it and exit.
     *
     * @return FormValidator
     */
    public function getCreateInstantMeetingForm(
        User $user,
        Course $course,
        CGroup $group = null,
        Session $session = null
    ) {
        $extraUrl = '';
        if (!empty($course)) {
            $extraUrl = api_get_cidreq();
        }
        $form = new FormValidator('createInstantMeetingForm', 'post', api_get_self().'?'.$extraUrl, '_blank');
        $form->addButton('startButton', $this->get_lang('StartInstantMeeting'), 'video-camera', 'primary');
        if ($form->validate()) {
            try {
                $this->startInstantMeeting($this->get_lang('InstantMeeting'), $user, $course, $group, $session);
            } catch (Exception $exception) {
                Display::addFlash(
                    Display::return_message($exception->getMessage(), 'error')
                );
            }
        }

        return $form;
    }

    /**
     * Generates a form to schedule a meeting.
     * On validation, creates it and redirects to its page.
     *
     * @throws Exception
     *
     * @return FormValidator
     */
    public function getScheduleMeetingForm(User $user, Course $course = null, CGroup $group = null, Session $session = null)
    {
        $extraUrl = '';
        if (!empty($course)) {
            $extraUrl = api_get_cidreq();
        }
        $form = new FormValidator('scheduleMeetingForm', 'post', api_get_self().'?'.$extraUrl);
        $form->addHeader($this->get_lang('ScheduleAMeeting'));
        $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
        $form->setRequired($startTimeDatePicker);

        $form->addText('topic', $this->get_lang('Topic'), true);
        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);

        $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
        $form->setRequired($durationNumeric);

        if (null === $course && 'true' === $this->get('enableGlobalConference')) {
            $options = [];
            $options['everyone'] = $this->get_lang('ForEveryone');
            $options['registered_users'] = $this->get_lang('SomeUsers');
            if (!empty($options)) {
                if (1 === count($options)) {
                    $form->addHidden('type', key($options));
                } else {
                    $form->addSelect('type', $this->get_lang('ConferenceType'), $options);
                }
            }
        } else {
            // To course
            $form->addHidden('type', 'course');
        }

        /*
       // $passwordText = $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
       if (null !== $course) {
           $registrationOptions = [
               'RegisterAllCourseUsers' => $this->get_lang('RegisterAllCourseUsers'),
           ];
           $groups = GroupManager::get_groups();
           if (!empty($groups)) {
               $registrationOptions['RegisterTheseGroupMembers'] = get_lang('RegisterTheseGroupMembers');
           }
           $registrationOptions['RegisterNoUser'] = $this->get_lang('RegisterNoUser');
           $userRegistrationRadio = $form->addRadio(
               'userRegistration',
               $this->get_lang('UserRegistration'),
               $registrationOptions
           );
           $groupOptions = [];
           foreach ($groups as $group) {
               $groupOptions[$group['id']] = $group['name'];
           }
           $groupIdsSelect = $form->addSelect(
               'groupIds',
               $this->get_lang('RegisterTheseGroupMembers'),
               $groupOptions
           );
           $groupIdsSelect->setMultiple(true);
           if (!empty($groups)) {
               $jsCode = sprintf(
                   "getElementById('%s').parentNode.parentNode.parentNode.style.display = getElementById('%s').checked ? 'block' : 'none'",
                   $groupIdsSelect->getAttribute('id'),
                   $userRegistrationRadio->getelements()[1]->getAttribute('id')
               );

               $form->setAttribute('onchange', $jsCode);
           }
       }*/

        $form->addButtonCreate(get_lang('Save'));

        if ($form->validate()) {
            $type = $form->getSubmitValue('type');

            switch ($type) {
                case 'everyone':
                    $user = null;
                    $group = null;
                    $course = null;
                    $session = null;

                    break;
                case 'registered_users':
                    //$user = null;
                    $course = null;
                    $session = null;

                    break;
                case 'course':
                    $user = null;
                    //$course = null;
                    //$session = null;

                    break;
            }

            try {
                $newMeeting = $this->createScheduleMeeting(
                    $user,
                    $course,
                    $group,
                    $session,
                    new DateTime($form->getSubmitValue('startTime')),
                    $form->getSubmitValue('duration'),
                    $form->getSubmitValue('topic'),
                    $form->getSubmitValue('agenda'),
                    substr(uniqid('z', true), 0, 10)
                );

                Display::addFlash(
                    Display::return_message($this->get_lang('NewMeetingCreated'))
                );

                if ($newMeeting->isCourseMeeting()) {
                    if ('RegisterAllCourseUsers' === $form->getSubmitValue('userRegistration')) {
                        $this->registerAllCourseUsers($newMeeting);
                        Display::addFlash(
                            Display::return_message($this->get_lang('AllCourseUsersWereRegistered'))
                        );
                    } elseif ('RegisterTheseGroupMembers' === $form->getSubmitValue('userRegistration')) {
                        $userIds = [];
                        foreach ($form->getSubmitValue('groupIds') as $groupId) {
                            $userIds = array_unique(array_merge($userIds, GroupManager::get_users($groupId)));
                        }
                        $users = Database::getManager()->getRepository('ChamiloUserBundle:User')->findBy(
                            ['id' => $userIds]
                        );
                        $this->registerUsers($newMeeting, $users);
                        Display::addFlash(
                            Display::return_message($this->get_lang('GroupUsersWereRegistered'))
                        );
                    }
                }
                api_location('meeting.php?meetingId='.$newMeeting->getMeetingId().'&'.$extraUrl);
            } catch (Exception $exception) {
                Display::addFlash(
                    Display::return_message($exception->getMessage(), 'error')
                );
            }
        } else {
            $form->setDefaults(
                [
                    'duration' => 60,
                    'userRegistration' => 'RegisterAllCourseUsers',
                ]
            );
        }

        return $form;
    }

    /**
     * Return the current global meeting (create it if needed).
     *
     * @throws Exception
     *
     * @return string
     */
    public function getGlobalMeeting()
    {
        foreach ($this->getMeetingRepository()->unfinishedGlobalMeetings() as $meeting) {
            return $meeting;
        }

        return $this->createGlobalMeeting();
    }

    /**
     * @return MeetingRepository|EntityRepository
     */
    public static function getMeetingRepository()
    {
        return Database::getManager()->getRepository(Meeting::class);
    }

    /**
     * Returns the URL to enter (start or join) a meeting or null if not possible to enter the meeting,
     * The returned URL depends on the meeting current status (waiting, started or finished) and the current user.
     *
     * @param Meeting $meeting
     *
     * @throws OptimisticLockException
     * @throws Exception
     *
     * @return string|null
     */
    public function getStartOrJoinMeetingURL($meeting)
    {
        $status = $meeting->getMeetingInfoGet()->status;
        $userId = api_get_user_id();
        $currentUser = api_get_user_entity($userId);
        $isGlobal = 'true' === $this->get('enableGlobalConference') && $meeting->isGlobalMeeting();

        switch ($status) {
            case 'ended':
                if ($this->userIsConferenceManager($meeting)) {
                    return $meeting->getMeetingInfoGet()->start_url;
                }
                break;
            case 'waiting':
                // Zoom does not allow for a new meeting to be started on first participant join.
                // It requires the host to start the meeting first.
                // Therefore for global meetings we must make the first participant the host
                // that is use start_url rather than join_url.
                // the participant will not be registered and will appear as the Zoom user account owner.
                // For course and user meetings, only the host can start the meeting.
                if ($this->userIsConferenceManager($meeting)) {
                    return $meeting->getMeetingInfoGet()->start_url;
                }

                break;
            case 'started':
                // User per conference.
                if ($currentUser === $meeting->getUser()) {
                    return $meeting->getMeetingInfoGet()->join_url;
                }

                // The participant is not registered, he can join only the global meeting (automatic registration).
                if ($isGlobal) {
                    return $this->registerUser($meeting, $currentUser)->getCreatedRegistration()->join_url;
                }

                if ($meeting->isCourseMeeting()) {
                    if ($this->userIsCourseConferenceManager()) {
                        return $meeting->getMeetingInfoGet()->start_url;
                    }

                    $sessionId = api_get_session_id();
                    $courseCode = api_get_course_id();

                    if (empty($sessionId)) {
                        $isSubscribed = CourseManager::is_user_subscribed_in_course(
                            $userId,
                            $courseCode,
                            false
                        );
                    } else {
                        $isSubscribed = CourseManager::is_user_subscribed_in_course(
                            $userId,
                            $courseCode,
                            true,
                            $sessionId
                        );
                    }

                    if ($isSubscribed) {
                        if ($meeting->isCourseGroupMeeting()) {
                            $isInGroup = GroupManager::isUserInGroup($userId, $meeting->getGroup());
                            if (false === $isInGroup) {
                                throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
                            }
                        }

                        if (\Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT == $meeting->getMeetingInfoGet()->type) {
                            return $meeting->getMeetingInfoGet()->join_url;
                        }

                        return $this->registerUser($meeting, $currentUser)->getCreatedRegistration()->join_url;
                    }

                    throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
                }

                //if ('true' === $this->get('enableParticipantRegistration')) {
                    //if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {
                    // the participant must be registered
                    $registrant = $meeting->getRegistrant($currentUser);
                    if (null == $registrant) {
                        throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
                    }

                    // the participant is registered
                    return $registrant->getCreatedRegistration()->join_url;
                //}
                break;
        }

        return null;
    }

    /**
     * @param Meeting $meeting
     *
     * @return bool whether the logged-in user can manage conferences in this context, that is either
     *              the current course or session coach, the platform admin or the current course admin
     */
    public function userIsConferenceManager($meeting)
    {
        if (null === $meeting) {
            return false;
        }

        if (api_is_coach() || api_is_platform_admin()) {
            return true;
        }

        if ($meeting->isCourseMeeting() && api_get_course_id() && api_is_course_admin()) {
            return true;
        }

        return $meeting->isUserMeeting() && $meeting->getUser()->getId() == api_get_user_id();
    }

    /**
     * @return bool whether the logged-in user can manage conferences in this context, that is either
     *              the current course or session coach, the platform admin or the current course admin
     */
    public function userIsCourseConferenceManager()
    {
        if (api_is_coach() || api_is_platform_admin()) {
            return true;
        }

        if (api_get_course_id() && api_is_course_admin()) {
            return true;
        }

        return false;
    }

    /**
     * Update local recording list from remote Zoom server's version.
     * Kept to implement a future administration button ("import existing data from zoom server").
     *
     * @param DateTime $startDate
     * @param DateTime $endDate
     *
     * @throws OptimisticLockException
     * @throws Exception
     */
    public function reloadPeriodRecordings($startDate, $endDate)
    {
        $em = Database::getManager();
        $recordingRepo = $this->getRecordingRepository();
        $meetingRepo = $this->getMeetingRepository();
        $recordings = RecordingList::loadPeriodRecordings($startDate, $endDate);

        foreach ($recordings as $recordingMeeting) {
            $recordingEntity = $recordingRepo->findOneBy(['uuid' => $recordingMeeting->uuid]);
            if (null === $recordingEntity) {
                $recordingEntity = new Recording();
                $meeting = $meetingRepo->findOneBy(['meetingId' => $recordingMeeting->id]);
                if (null === $meeting) {
                    try {
                        $meetingInfoGet = MeetingInfoGet::fromId($recordingMeeting->id);
                    } catch (Exception $exception) {
                        $meetingInfoGet = null; // deleted meeting with recordings
                    }
                    if (null !== $meetingInfoGet) {
                        $meeting = $this->createMeetingFromMeeting(
                            (new Meeting())->setMeetingInfoGet($meetingInfoGet)
                        );
                        $em->persist($meeting);
                    }
                }
                if (null !== $meeting) {
                    $recordingEntity->setMeeting($meeting);
                }
            }
            $recordingEntity->setRecordingMeeting($recordingMeeting);
            $em->persist($recordingEntity);
        }
        $em->flush();
    }

    /**
     * @return RecordingRepository|EntityRepository
     */
    public static function getRecordingRepository()
    {
        return Database::getManager()->getRepository(Recording::class);
    }

    public function getToolbar($returnUrl = '')
    {
        if (!api_is_platform_admin()) {
            return '';
        }

        $actionsLeft = '';
        $back = '';
        $courseId = api_get_course_id();
        if (empty($courseId)) {
            $actionsLeft .=
                Display::url(
                    Display::getMdiIcon(ToolIcon::VIDEOCONFERENCE, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, $this->get_lang('Meetings')),
                    api_get_path(WEB_PLUGIN_PATH).'zoom/meetings.php'
                );
        } else {
            $actionsLeft .=
                Display::url(
                    Display::getMdiIcon(ToolIcon::VIDEOCONFERENCE, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, $this->get_lang('Meetings')),
                    api_get_path(WEB_PLUGIN_PATH).'zoom/start.php?'.api_get_cidreq()
                );
        }

        if (!empty($returnUrl)) {
            $back = Display::url(
                Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Back')),
                $returnUrl
            );
        }

        if (api_is_platform_admin()) {
            $actionsLeft .=
                Display::url(
                    Display::getMdiIcon(ToolIcon::SETTINGS, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Settings')),
                    api_get_path(WEB_CODE_PATH).'admin/configure_plugin.php?name=zoom'
                ).$back;
        }

        return Display::toolbarAction('toolbar', [$actionsLeft]);
    }

    public function getRecordingSetting()
    {
        $recording = (string) $this->get('enableCloudRecording');

        if (in_array($recording, [self::RECORDING_TYPE_LOCAL, self::RECORDING_TYPE_CLOUD], true)) {
            return $recording;
        }

        return self::RECORDING_TYPE_NONE;
    }

    public function hasRecordingAvailable()
    {
        $recording = $this->getRecordingSetting();

        return self::RECORDING_TYPE_NONE !== $recording;
    }

    /**
     * Updates meeting registrants list. Adds the missing registrants and removes the extra.
     *
     * @param Meeting $meeting
     * @param User[]  $users   list of users to be registered
     *
     * @throws Exception
     */
    private function updateRegistrantList($meeting, $users)
    {
        $usersToAdd = [];
        foreach ($users as $user) {
            $found = false;
            foreach ($meeting->getRegistrants() as $registrant) {
                if ($registrant->getUser() === $user) {
                    $found = true;
                    break;
                }
            }
            if (!$found) {
                $usersToAdd[] = $user;
            }
        }
        $registrantsToRemove = [];
        foreach ($meeting->getRegistrants() as $registrant) {
            $found = false;
            foreach ($users as $user) {
                if ($registrant->getUser() === $user) {
                    $found = true;
                    break;
                }
            }
            if (!$found) {
                $registrantsToRemove[] = $registrant;
            }
        }
        $this->registerUsers($meeting, $usersToAdd);
        $this->unregister($meeting, $registrantsToRemove);
    }

    /**
     * Register users to a meeting.
     *
     * @param Meeting $meeting
     * @param User[]  $users
     *
     * @throws OptimisticLockException
     *
     * @return User[] failed registrations [ user id => errorMessage ]
     */
    private function registerUsers($meeting, $users)
    {
        $failedUsers = [];
        foreach ($users as $user) {
            try {
                $this->registerUser($meeting, $user, false);
            } catch (Exception $exception) {
                $failedUsers[$user->getId()] = $exception->getMessage();
            }
        }
        Database::getManager()->flush();

        return $failedUsers;
    }

    /**
     * @throws Exception
     * @throws OptimisticLockException
     *
     * @return Registrant
     */
    private function registerUser(Meeting $meeting, User $user, $andFlush = true)
    {
        if (empty($user->getEmail())) {
            throw new Exception($this->get_lang('CannotRegisterWithoutEmailAddress'));
        }

        $meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName(
            $user->getEmail(),
            $user->getFirstname(),
            $user->getLastname()
        );

        $registrantEntity = (new Registrant())
            ->setMeeting($meeting)
            ->setUser($user)
            ->setMeetingRegistrant($meetingRegistrant)
            ->setCreatedRegistration($meeting->getMeetingInfoGet()->addRegistrant($meetingRegistrant));
        Database::getManager()->persist($registrantEntity);

        if ($andFlush) {
            Database::getManager()->flush($registrantEntity);
        }

        return $registrantEntity;
    }

    /**
     * Removes registrants from a meeting.
     *
     * @param Meeting      $meeting
     * @param Registrant[] $registrants
     *
     * @throws Exception
     */
    private function unregister($meeting, $registrants)
    {
        $meetingRegistrants = [];
        foreach ($registrants as $registrant) {
            $meetingRegistrants[] = $registrant->getMeetingRegistrant();
        }
        $meeting->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
        $em = Database::getManager();
        foreach ($registrants as $registrant) {
            $em->remove($registrant);
        }
        $em->flush();
    }

    /**
     * Starts a new instant meeting and redirects to its start url.
     *
     * @param string       $topic
     * @param User|null    $user
     * @param Course|null  $course
     * @param CGroup|null  $group
     * @param Session|null $session
     *
     * @throws Exception
     */
    private function startInstantMeeting($topic, $user = null, $course = null, $group = null, $session = null)
    {
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_INSTANT);
        //$meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
        $meeting = $this->createMeetingFromMeeting(
            (new Meeting())
                ->setMeetingInfoGet($meetingInfoGet)
                ->setUser($user)
                ->setGroup($group)
                ->setCourse($course)
                ->setSession($session)
        );
        api_location($meeting->getMeetingInfoGet()->start_url);
    }

    /**
     * Creates a meeting on Zoom servers and stores it in the local database.
     *
     * @param Meeting $meeting a new, unsaved meeting with at least a type and a topic
     *
     * @throws Exception
     *
     * @return Meeting
     */
    private function createMeetingFromMeeting($meeting)
    {
        $currentUser = api_get_user_entity(api_get_user_id());

        $meeting->getMeetingInfoGet()->settings->contact_email = $currentUser->getEmail();
        $meeting->getMeetingInfoGet()->settings->contact_name = $currentUser->getFullname();
        $meeting->getMeetingInfoGet()->settings->auto_recording = $this->getRecordingSetting();
        $meeting->getMeetingInfoGet()->settings->registrants_email_notification = false;

        //$meeting->getMeetingInfoGet()->host_email = $currentUser->getEmail();
        //$meeting->getMeetingInfoGet()->settings->alternative_hosts = $currentUser->getEmail();

        // Send create to Zoom.
        $meeting->setMeetingInfoGet($meeting->getMeetingInfoGet()->create());

        Database::getManager()->persist($meeting);
        Database::getManager()->flush();

        return $meeting;
    }

    /**
     * @throws Exception
     *
     * @return Meeting
     */
    private function createGlobalMeeting()
    {
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType(
            $this->get_lang('GlobalMeeting'),
            MeetingInfoGet::TYPE_SCHEDULED
        );
        $meetingInfoGet->start_time = (new DateTime())->format(DateTimeInterface::ISO8601);
        $meetingInfoGet->duration = 60;
        $meetingInfoGet->settings->approval_type =
            ('true' === $this->get('enableParticipantRegistration'))
                ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE
                : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
        // $meetingInfoGet->settings->host_video = true;
        $meetingInfoGet->settings->participant_video = true;
        $meetingInfoGet->settings->join_before_host = true;
        $meetingInfoGet->settings->registrants_email_notification = false;

        return $this->createMeetingFromMeeting((new Meeting())->setMeetingInfoGet($meetingInfoGet));
    }

    /**
     * Schedules a meeting and returns it.
     * set $course, $session and $user to null in order to create a global meeting.
     *
     * @param DateTime $startTime meeting local start date-time (configure local timezone on your Zoom account)
     * @param int      $duration  in minutes
     * @param string   $topic     short title of the meeting, required
     * @param string   $agenda    ordre du jour
     * @param string   $password  meeting password
     *
     * @throws Exception
     *
     * @return Meeting meeting
     */
    private function createScheduleMeeting(
        User $user = null,
        Course $course = null,
        CGroup $group = null,
        Session $session = null,
        $startTime,
        $duration,
        $topic,
        $agenda,
        $password
    ) {
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED);
        $meetingInfoGet->duration = $duration;
        $meetingInfoGet->start_time = $startTime->format(DateTimeInterface::ISO8601);
        $meetingInfoGet->agenda = $agenda;
        $meetingInfoGet->password = $password;
        $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED;
        if ('true' === $this->get('enableParticipantRegistration')) {
            $meetingInfoGet->settings->approval_type = MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
        }

        return $this->createMeetingFromMeeting(
            (new Meeting())
                ->setMeetingInfoGet($meetingInfoGet)
                ->setUser($user)
                ->setCourse($course)
                ->setGroup($group)
                ->setSession($session)
        );
    }

    /**
     * Registers all the course users to a course meeting.
     *
     * @param Meeting $meeting
     *
     * @throws OptimisticLockException
     */
    private function registerAllCourseUsers($meeting)
    {
        $this->registerUsers($meeting, $meeting->getRegistrableUsers());
    }
}