%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /proc/self/root/home/tjamichg/cursos.tjamich.gob.mx/plugin/zoom/lib/
Upload File :
Create Path :
Current File : //proc/self/root/home/tjamichg/cursos.tjamich.gob.mx/plugin/zoom/lib/ZoomPlugin.php

<?php

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

use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CourseBundle\Entity\CGroupInfo;
use Chamilo\PluginBundle\Zoom\API\BaseMeetingTrait;
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\API\ServerToServerOAuthClient;
use Chamilo\PluginBundle\Zoom\API\WebinarRegistrantSchema;
use Chamilo\PluginBundle\Zoom\API\WebinarSchema;
use Chamilo\PluginBundle\Zoom\API\WebinarSettings;
use Chamilo\PluginBundle\Zoom\Meeting;
use Chamilo\PluginBundle\Zoom\MeetingActivity;
use Chamilo\PluginBundle\Zoom\MeetingRepository;
use Chamilo\PluginBundle\Zoom\Presenter;
use Chamilo\PluginBundle\Zoom\Recording;
use Chamilo\PluginBundle\Zoom\RecordingRepository;
use Chamilo\PluginBundle\Zoom\Registrant;
use Chamilo\PluginBundle\Zoom\RegistrantRepository;
use Chamilo\PluginBundle\Zoom\Signature;
use Chamilo\PluginBundle\Zoom\Webinar;
use Chamilo\UserBundle\Entity\User;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\ToolsException;

/**
 * Class ZoomPlugin. Integrates Zoom meetings in courses.
 */
class ZoomPlugin extends Plugin
{
    public const RECORDING_TYPE_CLOUD = 'cloud';
    public const RECORDING_TYPE_LOCAL = 'local';
    public const RECORDING_TYPE_NONE = 'none';
    public const SETTING_ACCOUNT_ID = 'account_id';
    public const SETTING_CLIENT_ID = 'client_id';
    public const SETTING_CLIENT_SECRET = 'client_secret';
    public const SETTING_SECRET_TOKEN = 'secret_token';

    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.6',
            'Sébastien Ducoulombier, Julio Montoya, Angel Fernando Quiroz Campos',
            [
                'tool_enable' => 'boolean',
                'apiKey' => 'text',
                'apiSecret' => 'text',
                'verificationToken' => 'text',
                self::SETTING_ACCOUNT_ID => 'text',
                self::SETTING_CLIENT_ID => 'text',
                self::SETTING_CLIENT_SECRET => 'text',
                self::SETTING_SECRET_TOKEN => 'text',
                'enableParticipantRegistration' => 'boolean',
                'enablePresenter' => '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'),
                        SESSIONADMIN => get_lang('SessionsAdmin'),
                    ],
                    'attributes' => ['multiple' => 'multiple'],
                ],
                'accountSelector' => 'text',
            ]
        );

        $this->isAdminPlugin = true;

        $accountId = $this->get(self::SETTING_ACCOUNT_ID);
        $clientId = $this->get(self::SETTING_CLIENT_ID);
        $clientSecret = $this->get(self::SETTING_CLIENT_SECRET);

        if (!empty($accountId) && !empty($clientId) && !empty($clientSecret)) {
            $this->jwtClient = new ServerToServerOAuthClient($accountId, $clientId, $clientSecret);
        } else {
            $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(): ZoomPlugin
    {
        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') && api_user_is_login();
        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::return_icon(
                    'bbb.png',
                    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->getTopic()
            )] = sprintf($linkTemplate, $meeting->getMeetingId());
        }

        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',
                'plugin_zoom_signature',
            ]
        );

        if ($tablesExists) {
            return;
        }

        $em = Database::getManager();

        (new SchemaTool($em))->createSchema(
            [
                $em->getClassMetadata(Meeting::class),
                $em->getClassMetadata(Webinar::class),
                $em->getClassMetadata(MeetingActivity::class),
                $em->getClassMetadata(Recording::class),
                $em->getClassMetadata(Registrant::class),
                $em->getClassMetadata(Signature::class),
            ]
        );

        // Copy icons into the main/img/icons folder
        $iconName = 'zoom_meet';
        $iconsList = [
            '64/'.$iconName.'.png',
            '64/'.$iconName.'_na.png',
            '32/'.$iconName.'.png',
            '32/'.$iconName.'_na.png',
            '22/'.$iconName.'.png',
            '22/'.$iconName.'_na.png',
        ];
        $sourceDir = api_get_path(SYS_PLUGIN_PATH).'zoom/resources/img/';
        $destinationDir = api_get_path(SYS_CODE_PATH).'img/icons/';
        foreach ($iconsList as $icon) {
            $src = $sourceDir.$icon;
            $dest = $destinationDir.$icon;
            copy($src, $dest);
        }

        $this->install_course_fields_in_all_courses(true, 'zoom_meet.png');
    }

    /**
     * Drops this plugins' related tables from the internal database.
     * Uninstalls course fields in all courses().
     */
    public function uninstall()
    {
        $em = Database::getManager();

        (new SchemaTool($em))->dropSchema(
            [
                $em->getClassMetadata(Meeting::class),
                $em->getClassMetadata(Webinar::class),
                $em->getClassMetadata(MeetingActivity::class),
                $em->getClassMetadata(Recording::class),
                $em->getClassMetadata(Registrant::class),
                $em->getClassMetadata(Signature::class),
            ]
        );
        $this->uninstall_course_fields_in_all_courses();

        // Remove icons from the main/img/icons folder
        $iconName = 'zoom_meet';
        $iconsList = [
            '64/'.$iconName.'.png',
            '64/'.$iconName.'_na.png',
            '32/'.$iconName.'.png',
            '32/'.$iconName.'_na.png',
            '22/'.$iconName.'.png',
            '22/'.$iconName.'_na.png',
        ];
        $destinationDir = api_get_path(SYS_CODE_PATH).'img/icons/';
        foreach ($iconsList as $icon) {
            $dest = $destinationDir.$icon;
            if (is_file($dest)) {
                @unlink($dest);
            }
        }
    }

    /**
     * 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;
    }

    /**
     * @throws Exception
     */
    public function getEditConferenceForm(Meeting $conference): FormValidator
    {
        $isWebinar = $conference instanceof Webinar;
        $requiresDateAndDuration = $conference->requiresDateAndDuration();

        /** @var BaseMeetingTrait $schema */
        $schema = $isWebinar ? $conference->getWebinarSchema() : $conference->getMeetingInfoGet();

        $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
        $form->addHeader(
            $isWebinar ? $this->get_lang('UpdateWebinar') : $this->get_lang('UpdateMeeting')
        );
        $form->addLabel(get_lang('Type'), $conference->typeName);
        if ($conference->getAccountEmail()) {
            $form->addLabel(
                $this->get_lang('AccountEmail'),
                $conference->getAccountEmail()
            );
        }
        $form->addText('topic', $this->get_lang('Topic'));

        if ($requiresDateAndDuration) {
            $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
            $durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));

            $form->setRequired($startTimeDatePicker);
            $form->setRequired($durationNumeric);
        }

        $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
        $form->addCheckBox('sign_attendance', $this->get_lang('SignAttendance'), get_lang('Yes'));
        $form->addTextarea('reason_to_sign', $this->get_lang('ReasonToSign'), ['rows' => 5]);
        $form->addButtonUpdate(get_lang('Update'));

        if ($form->validate()) {
            $formValues = $form->exportValues();

            $em = Database::getManager();

            if ($requiresDateAndDuration) {
                $schema->start_time = (new DateTime($formValues['startTime']))->format(DATE_ATOM);
                $schema->timezone = date_default_timezone_get();
                $schema->duration = (int) $formValues['duration'];
            }

            $schema->topic = $formValues['topic'];
            $schema->agenda = $formValues['agenda'];

            $conference
                ->setSignAttendance(isset($formValues['sign_attendance']))
                ->setReasonToSignAttendance($formValues['reason_to_sign']);

            try {
                $schema->update();

                if ($isWebinar) {
                    $conference->setWebinarSchema($schema);
                } else {
                    $conference->setMeetingInfoGet($schema);
                }

                $em->persist($conference);
                $em->flush();

                Display::addFlash(
                    Display::return_message(
                        $isWebinar ? $this->get_lang('WebinarUpdated') : $this->get_lang('MeetingUpdated'),
                        'confirm'
                    )
                );
            } catch (Exception $exception) {
                Display::addFlash(
                    Display::return_message($exception->getMessage(), 'error')
                );
            }
        }

        $defaults = [
            'topic' => $schema->topic,
            'agenda' => $schema->agenda,
        ];

        if ($requiresDateAndDuration) {
            $defaults['startTime'] = $conference->startDateTime->format('Y-m-d H:i');
            $defaults['duration'] = $schema->duration;
        }

        $defaults['sign_attendance'] = $conference->isSignAttendance();
        $defaults['reason_to_sign'] = $conference->getReasonToSignAttendance();

        $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;
    }

    public function getDeleteWebinarForm(Webinar $webinar, string $returnURL): FormValidator
    {
        $id = $webinar->getMeetingId();
        $form = new FormValidator('delete', 'post', api_get_self()."?meetingId=$id");
        $form->addButtonDelete($this->get_lang('DeleteWebinar'));

        if ($form->validate()) {
            $this->deleteWebinar($webinar, $returnURL);
        }

        return $form;
    }

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

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

        try {
            $meeting->getMeetingInfoGet()->delete();
        } catch (Exception $exception) {
            $this->handleException($exception);
        }

        $em = Database::getManager();
        $em->remove($meeting);
        $em->flush();

        Display::addFlash(
            Display::return_message($this->get_lang('MeetingDeleted'), 'confirm')
        );
        api_location($returnURL);

        return true;
    }

    public function deleteWebinar(Webinar $webinar, string $returnURL)
    {
        try {
            $webinar->getWebinarSchema()->delete();
        } catch (Exception $exception) {
            $this->handleException($exception);
        }

        $em = Database::getManager();
        $em->remove($webinar);
        $em->flush();

        Display::addFlash(
            Display::return_message($this->get_lang('WebinarDeleted'), 'success')
        );

        api_location($returnURL);
    }

    /**
     * @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'));

        $selfRegistrationUrl = api_get_path(WEB_PLUGIN_PATH)
            .'zoom/subscription.php?meetingId='.$meeting->getMeetingId();

        $form->addHtml(
            '<div class="form-group"><div class="col-sm-8 col-sm-offset-2">
                <hr style="margin-top: 0;">
                <label for="frm-registration__txt-self-registration">'
            .$this->get_lang('UrlForSelfRegistration').'</label>
                <div class="input-group">
                    <input type="text" class="form-control" id="frm-registration__txt-self-registration" value="'
            .$selfRegistrationUrl.'">
                    <span class="input-group-btn">
                        <button class="btn btn-default" type="button"
                         onclick="copyTextToClipBoard(\'frm-registration__txt-self-registration\');">'
            .$this->get_lang('CopyTextToClipboard').'</button>
                    </span>
                </div>
            </div></div>'
        );

        $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;
    }

    public function getRegisterPresenterForm(Meeting $meeting): FormValidator
    {
        $form = new FormValidator('register_presenter', 'post', $_SERVER['REQUEST_URI']);

        $presenterIdSelect = $form->addSelect('presenterIds', $this->get_lang('RegisteredPresenters'));
        $presenterIdSelect->setMultiple(true);

        $form->addButtonSend($this->get_lang('UpdateRegisteredUserList'));

        $users = $meeting->getRegistrableUsers();

        foreach ($users as $user) {
            $presenterIdSelect->addOption(
                api_get_person_name($user->getFirstname(), $user->getLastname()),
                $user->getId()
            );
        }

        if ($form->validate()) {
            $selectedPresenterIds = $form->getSubmitValue('presenterIds') ?: [];
            $selectedPresenters = [];

            foreach ($users as $user) {
                if (in_array($user->getId(), $selectedPresenterIds)) {
                    $selectedPresenters[] = $user;
                }
            }

            try {
                $this->updatePresenterList($meeting, $selectedPresenters);

                Display::addFlash(
                    Display::return_message($this->get_lang('RegisteredUserListWasUpdated'), 'confirm')
                );
            } catch (Exception $exception) {
                Display::addFlash(
                    Display::return_message($exception->getMessage(), 'error')
                );
            }
        }

        $registeredPresenterIds = [];

        foreach ($meeting->getPresenters() as $registrant) {
            if ($registrant instanceof Presenter) {
                $registeredPresenterIds[] = $registrant->getUser()->getId();
            }
        }

        $presenterIdSelect->setSelected($registeredPresenterIds);

        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 = 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,
        CGroupInfo $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, CGroupInfo $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'));

        $form->addSelect(
            'conference_type',
            $this->get_lang('ConferenceType'),
            [
                'meeting' => $this->get_lang('Meeting'),
                'webinar' => $this->get_lang('Webinar'),
            ]
        );
        $form->addRule('conference_type', get_lang('ThisFieldIsRequired'), 'required');

        $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('AudienceType'), $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->addCheckBox('sign_attendance', $this->get_lang('SignAttendance'), get_lang('Yes'));
        $form->addTextarea('reason_to_sign', $this->get_lang('ReasonToSign'), ['rows' => 5]);

        $accountEmails = $this->getAccountEmails();

        if (!empty($accountEmails)) {
            $form->addSelect('account_email', $this->get_lang('AccountEmail'), $accountEmails);
        }

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

        if ($form->validate()) {
            $formValues = $form->exportValues();
            $conferenceType = $formValues['conference_type'];
            $password = substr(uniqid('z', true), 0, 10);

            switch ($formValues['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;
            }

            $accountEmail = $formValues['account_email'] ?? null;
            $accountEmail = $accountEmail && in_array($accountEmail, $accountEmails) ? $accountEmail : null;

            try {
                $startTime = new DateTime($formValues['startTime']);

                if ('meeting' === $conferenceType) {
                    $newMeeting = $this->createScheduleMeeting(
                        $user,
                        $course,
                        $group,
                        $session,
                        $startTime,
                        $formValues['duration'],
                        $formValues['topic'],
                        $formValues['agenda'],
                        $password,
                        isset($formValues['sign_attendance']),
                        $formValues['reason_to_sign'],
                        $accountEmail
                    );

                    Display::addFlash(
                        Display::return_message($this->get_lang('NewMeetingCreated'))
                    );
                } elseif ('webinar' === $conferenceType) {
                    $newMeeting = $this->createScheduleWebinar(
                        $user,
                        $course,
                        $group,
                        $session,
                        $startTime,
                        $formValues['duration'],
                        $formValues['topic'],
                        $formValues['agenda'],
                        $password,
                        isset($formValues['sign_attendance']),
                        $formValues['reason_to_sign'],
                        $accountEmail
                    );

                    Display::addFlash(
                        Display::return_message($this->get_lang('NewWebinarCreated'))
                    );
                } else {
                    throw new Exception('Invalid conference type');
                }

                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.
     *
     * @throws OptimisticLockException
     * @throws Exception
     *
     * @return string|null
     */
    public function getStartOrJoinMeetingURL(Meeting $meeting)
    {
        if ($meeting instanceof Webinar) {
            $status = 'started';
        } else {
            $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 instanceof Webinar
                        ? $meeting->getWebinarSchema()->start_url
                        : $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 instanceof Webinar
                            ? $meeting->getWebinarSchema()->start_url
                            : $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()) {
                            $groupInfo = GroupManager::get_group_properties($meeting->getGroup()->getIid(), true);
                            $isInGroup = GroupManager::is_user_in_group($userId, $groupInfo);
                            if (false === $isInGroup) {
                                throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
                            }
                        }

                        if (!$meeting instanceof Webinar
                            && \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->getRegistrantByUser($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;
        }

        $currentUser = api_get_user_entity(api_get_user_id());

        if ('true' === $this->get('enableParticipantRegistration')
            && 'true' === $this->get('enablePresenter')
            && $currentUser
            && $meeting->hasUserAsPresenter($currentUser)
        ) {
            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(string $returnUrl = ''): string
    {
        $isPlatformOrSessionAdmin = api_is_platform_admin(true);
        $isSessionAdmin = api_is_session_admin();

        if (!$isPlatformOrSessionAdmin) {
            return '';
        }

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

        if (!empty($returnUrl)) {
            $back = Display::url(
                Display::return_icon('back.png', get_lang('Back'), null, ICON_SIZE_MEDIUM),
                $returnUrl
            );
        }

        if (!$isSessionAdmin) {
            $actionsLeft .= Display::url(
                Display::return_icon('agenda.png', get_lang('Calendar'), [], ICON_SIZE_MEDIUM),
                'calendar.php'
            );

            $actionsLeft .= Display::url(
                Display::return_icon('settings.png', get_lang('Settings'), null, ICON_SIZE_MEDIUM),
                api_get_path(WEB_CODE_PATH).'admin/configure_plugin.php?name=zoom'
            );
        }

        return Display::toolbarAction('toolbar', [$back.PHP_EOL.$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;
    }

    /**
     * @throws OptimisticLockException
     * @throws \Doctrine\ORM\ORMException
     */
    public function saveSignature(Registrant $registrant, string $file): bool
    {
        if (empty($file)) {
            return false;
        }

        $signature = $registrant->getSignature();

        if (null !== $signature) {
            return false;
        }

        $signature = new Signature();
        $signature
            ->setFile($file)
            ->setRegisteredAt(api_get_utc_datetime(null, false, true))
        ;

        $registrant->setSignature($signature);

        $em = Database::getManager();
        $em->persist($signature);
        $em->flush();

        return true;
    }

    public function getSignature(int $userId, Meeting $meeting): ?Signature
    {
        $signatureRepo = Database::getManager()
            ->getRepository(Signature::class)
        ;

        return $signatureRepo->findOneBy(['user' => $userId, 'meeting' => $meeting]);
    }

    public function exportSignatures(Meeting $meeting, $formatToExport)
    {
        $signatures = array_map(
            function (Registrant $registrant) use ($formatToExport) {
                $signature = $registrant->getSignature();

                $item = [
                    $registrant->getUser()->getLastname(),
                    $registrant->getUser()->getFirstname(),
                    $signature
                        ? api_convert_and_format_date($signature->getRegisteredAt(), DATE_TIME_FORMAT_LONG)
                        : '-',
                ];

                if ('pdf' === $formatToExport) {
                    $item[] = $signature
                        ? Display::img($signature->getFile(), '', ['style' => 'width: 150px;'], false)
                        : '-';
                }

                return $item;
            },
            $meeting->getRegistrants()->toArray()
        );

        $data = array_merge(
            [
                [
                    get_lang('LastName'),
                    get_lang('FirstName'),
                    get_lang('DateTime'),
                    'pdf' === $formatToExport ? get_lang('File') : null,
                ],
            ],
            $signatures
        );

        if ('pdf' === $formatToExport) {
            $params = [
                'filename' => get_lang('Attendance'),
                'pdf_title' => get_lang('Attendance'),
                'pdf_description' => $meeting->getIntroduction(),
                'show_teacher_as_myself' => false,
            ];

            Export::export_table_pdf($data, $params);
        }

        if ('xls' === $formatToExport) {
            $introduction = array_map(
                function ($line) {
                    return [
                        strip_tags(trim($line)),
                    ];
                },
                explode(PHP_EOL, $meeting->getIntroduction())
            );

            Export::arrayToXls(
                array_merge($introduction, $data),
                get_lang('Attendance')
            );
        }
    }

    /**
     * @throws Exception
     */
    public function createWebinarFromSchema(Webinar $webinar, WebinarSchema $schema): Webinar
    {
        $currentUser = api_get_user_entity(api_get_user_id());

        $schema->settings->contact_email = $currentUser->getEmail();
        $schema->settings->contact_name = $currentUser->getFullname();
        $schema->settings->auto_recording = $this->getRecordingSetting();
        $schema->settings->registrants_email_notification = false;
        $schema->settings->attendees_and_panelists_reminder_email_notification->enable = false;
        $schema->settings->follow_up_attendees_email_notification->enable = false;
        $schema->settings->follow_up_absentees_email_notification->enable = false;

        $schema = $schema->create($webinar->getAccountEmail());

        $webinar->setWebinarSchema($schema);

        $em = Database::getManager();
        $em->persist($webinar);
        $em->flush();

        return $webinar;
    }

    public function getAccountEmails(): array
    {
        $currentValue = $this->get('accountSelector');

        if (empty($currentValue)) {
            return [];
        }

        $emails = explode(';', $currentValue);
        $trimmed = array_map('trim', $emails);
        $filtered = array_filter($trimmed);

        return array_combine($filtered, $filtered);
    }

    /**
     * Register users to a meeting.
     *
     * @param User[] $users
     *
     * @throws OptimisticLockException
     *
     * @return User[] failed registrations [ user id => errorMessage ]
     */
    public function registerUsers(Meeting $meeting, array $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;
    }

    /**
     * @param array<User> $users
     *
     * @throws OptimisticLockException
     * @throws \Doctrine\ORM\ORMException
     */
    public function registerPresenters(Meeting $meeting, array $users): array
    {
        $failedUsers = [];

        foreach ($users as $user) {
            try {
                $this->registerUser($meeting, $user, false, true);
            } catch (Exception $exception) {
                $failedUsers[$user->getId()] = $exception->getMessage();
            }
        }

        Database::getManager()->flush();

        return $failedUsers;
    }

    /**
     * Removes registrants from a meeting.
     *
     * @param Registrant[] $registrants
     *
     * @throws Exception
     */
    public function unregister(Meeting $meeting, array $registrants)
    {
        $meetingRegistrants = [];
        foreach ($registrants as $registrant) {
            $meetingRegistrants[] = $registrant->getMeetingRegistrant();
        }

        if ($meeting instanceof Webinar) {
            $meeting->getWebinarSchema()->removeRegistrants($meetingRegistrants);
        } else {
            $meeting->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
        }

        $em = Database::getManager();
        foreach ($registrants as $registrant) {
            $em->remove($registrant);
        }
        $em->flush();
    }

    /**
     * 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);
    }

    private function updatePresenterList($meeting, $users)
    {
        /** @var array<Registrant> $presenters */
        $presenters = $meeting->getPresenters();

        $presenterToAdd = [];

        foreach ($users as $user) {
            $foundPresenter = false;

            foreach ($presenters as $presenter) {
                if ($presenter->getUser() === $user) {
                    $foundPresenter = true;

                    break;
                }
            }

            if (!$foundPresenter) {
                $presenterToAdd[] = $user;
            }
        }

        $registrantsToRemove = [];

        foreach ($presenters as $registrant) {
            $found = false;
            foreach ($users as $user) {
                if ($registrant->getUser() === $user) {
                    $found = true;
                    break;
                }
            }
            if (!$found) {
                $registrantsToRemove[] = $registrant;
            }
        }

        $this->registerPresenters($meeting, $presenterToAdd);
        $this->unregister($meeting, $registrantsToRemove);
    }

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

        if ($meeting instanceof Webinar) {
            $meetingRegistrant = WebinarRegistrantSchema::fromEmailAndFirstName(
                $user->getEmail(),
                $user->getFirstname(),
                $user->getLastname()
            );
        } else {
            $meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName(
                $user->getEmail(),
                $user->getFirstname(),
                $user->getLastname()
            );
        }

        $registrantEntity = new Registrant();

        if ($isPresenter) {
            $registrantEntity = new Presenter();
        }

        $registrantEntity
            ->setMeeting($meeting)
            ->setUser($user)
            ->setMeetingRegistrant($meetingRegistrant)
        ;

        if ($meeting instanceof Webinar) {
            $registrantEntity->setCreatedRegistration($meeting->getWebinarSchema()->addRegistrant($meetingRegistrant));
        } else {
            $registrantEntity->setCreatedRegistration($meeting->getMeetingInfoGet()->addRegistrant($meetingRegistrant));
        }

        Database::getManager()->persist($registrantEntity);

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

        return $registrantEntity;
    }

    /**
     * Starts a new instant meeting and redirects to its start url.
     *
     * @param string          $topic
     * @param User|null       $user
     * @param Course|null     $course
     * @param CGroupInfo|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(
                $meeting->getAccountEmail()
            )
        );

        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(DATE_ATOM);
        $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,
        CGroupInfo $group = null,
        Session $session = null,
        $startTime,
        $duration,
        $topic,
        $agenda,
        $password,
        bool $signAttendance = false,
        string $reasonToSignAttendance = '',
        string $accountEmail = null
    ) {
        $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED);
        $meetingInfoGet->duration = $duration;
        $meetingInfoGet->start_time = $startTime->format(DATE_ATOM);
        $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)
                ->setSignAttendance($signAttendance)
                ->setReasonToSignAttendance($reasonToSignAttendance)
                ->setAccountEmail($accountEmail)
        );
    }

    /**
     * @throws Exception
     */
    private function createScheduleWebinar(
        ?User $user,
        ?Course $course,
        ?CGroupInfo $group,
        ?Session $session,
        DateTime $startTime,
        $duration,
        $topic,
        $agenda,
        $password,
        bool $signAttendance = false,
        string $reasonToSignAttendance = '',
        string $accountEmail = null
    ): Webinar {
        $webinarSchema = WebinarSchema::fromTopicAndType($topic);
        $webinarSchema->duration = $duration;
        $webinarSchema->start_time = $startTime->format(DATE_ATOM);
        $webinarSchema->agenda = $agenda;
        $webinarSchema->password = $password;

        if ('true' === $this->get('enableParticipantRegistration')) {
            $webinarSchema->settings->approval_type = WebinarSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
        }

        $webinar = (new Webinar())
            ->setUser($user)
            ->setCourse($course)
            ->setGroup($group)
            ->setSession($session)
            ->setSignAttendance($signAttendance)
            ->setReasonToSignAttendance($reasonToSignAttendance)
            ->setAccountEmail($accountEmail)
        ;

        return $this->createWebinarFromSchema($webinar, $webinarSchema);
    }

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

Zerion Mini Shell 1.0