diff --git a/main/inc/lib/userportal.lib.php b/main/inc/lib/userportal.lib.php index 48c92c7a7a..490e7753d6 100755 --- a/main/inc/lib/userportal.lib.php +++ b/main/inc/lib/userportal.lib.php @@ -952,6 +952,12 @@ class IndexManager ]; } + if ('true' === api_get_plugin_setting('zoom', 'tool_enable')) { + foreach (ZoomPlugin::getProfileBlockItems() as $item) { + $items[] = $item; + } + } + if ( true === api_get_configuration_value('whispeak_auth_enabled') && !WhispeakAuthPlugin::checkUserIsEnrolled($userId) diff --git a/plugin/zoom/Entity/MeetingEntity.php b/plugin/zoom/Entity/MeetingEntity.php new file mode 100644 index 0000000000..366d2464dc --- /dev/null +++ b/plugin/zoom/Entity/MeetingEntity.php @@ -0,0 +1,508 @@ +registrants = new ArrayCollection(); + $this->recordings = new ArrayCollection(); + } + + /** + * @return string + */ + public function __toString() + { + return sprintf('Meeting %d', $this->id); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * @return Course + */ + public function getCourse() + { + return $this->course; + } + + /** + * @return Session + */ + public function getSession() + { + return $this->session; + } + + /** + * @return RegistrantEntity[]|ArrayCollection + */ + public function getRegistrants() + { + return $this->registrants; + } + + /** + * @return RecordingEntity[]|ArrayCollection + */ + public function getRecordings() + { + return $this->recordings; + } + + /** + * @ORM\PostLoad + * + * @throws Exception + */ + public function postLoad() + { + if (!is_null($this->meetingListItemJson)) { + $this->meetingListItem = MeetingListItem::fromJson($this->meetingListItemJson); + } + if (!is_null($this->meetingInfoGetJson)) { + $this->meetingInfoGet = MeetingInfoGet::fromJson($this->meetingInfoGetJson); + } + $this->initializeDisplayableProperties(); + } + + /** + * @ORM\PostUpdate + * + * @throws Exception + */ + public function postUpdate() + { + $this->initializeDisplayableProperties(); + } + + /** + * @ORM\PreFlush + */ + public function preFlush() + { + if (!is_null($this->meetingListItem)) { + $this->meetingListItemJson = json_encode($this->meetingListItem); + } + if (!is_null($this->meetingInfoGet)) { + $this->meetingInfoGetJson = json_encode($this->meetingInfoGet); + } + } + + /** + * @return MeetingListItem + */ + public function getMeetingListItem() + { + return $this->meetingListItem; + } + + /** + * @return MeetingInfoGet + */ + public function getMeetingInfoGet() + { + return $this->meetingInfoGet; + } + + /** + * @param User $user + * + * @return $this + */ + public function setUser($user) + { + $this->user = $user; + + return $this; + } + + /** + * @param Course $course + * + * @return $this + */ + public function setCourse($course) + { + $this->course = $course; + + return $this; + } + + /** + * @param Session $session + * + * @return $this + */ + public function setSession($session) + { + $this->session = $session; + + return $this; + } + + /** + * @param MeetingListItem $meetingListItem + * + * @throws Exception + * + * @return MeetingEntity + */ + public function setMeetingListItem($meetingListItem) + { + if (is_null($this->id)) { + $this->id = $meetingListItem->id; + } elseif ($this->id != $meetingListItem->id) { + throw new Exception('the MeetingEntity identifier differs from the MeetingListItem identifier'); + } + $this->meetingListItem = $meetingListItem; + + return $this; + } + + /** + * @param MeetingInfoGet $meetingInfoGet + * + * @throws Exception + * + * @return MeetingEntity + */ + public function setMeetingInfoGet($meetingInfoGet) + { + if (is_null($this->id)) { + $this->id = $meetingInfoGet->id; + } elseif ($this->id != $meetingInfoGet->id) { + throw new Exception('the MeetingEntity identifier differs from the MeetingInfoGet identifier'); + } + $this->meetingInfoGet = $meetingInfoGet; + $this->initializeDisplayableProperties(); + + return $this; + } + + /** + * @return bool + */ + public function isCourseMeeting() + { + return !is_null($this->course); + } + + /** + * @return bool + */ + public function isUserMeeting() + { + return !is_null($this->user) && is_null($this->course); + } + + /** + * @return bool + */ + public function isGlobalMeeting() + { + return is_null($this->user) && is_null($this->course); + } + + public function setStatus($status) + { + $this->meetingInfoGet->status = $status; + } + + /** + * Builds the list of users that can register into this meeting. + * Zoom requires an email address, therefore users without an email address are excluded from the list. + * + * @return User[] the list of users + */ + public function getRegistrableUsers() + { + /** @var User[] $users */ + $users = []; + if (!$this->isCourseMeeting()) { + $users = Database::getManager()->getRepository('ChamiloUserBundle:User')->findBy(['active' => true]); + } elseif (is_null($this->session)) { + if (!is_null($this->course)) { + /** @var CourseRelUser $courseRelUser */ + foreach ($this->course->getUsers() as $courseRelUser) { + $users[] = $courseRelUser->getUser(); + } + } + } else { + if (!is_null($this->course)) { + $subscriptions = $this->session->getUserCourseSubscriptionsByStatus($this->course, Session::STUDENT); + if ($subscriptions) { + /** @var SessionRelCourseRelUser $sessionCourseUser */ + foreach ($subscriptions as $sessionCourseUser) { + $users[] = $sessionCourseUser->getUser(); + } + } + } + } + $activeUsersWithEmail = []; + foreach ($users as $user) { + if ($user->isActive() && !empty($user->getEmail())) { + $activeUsersWithEmail[] = $user; + } + } + + return $activeUsersWithEmail; + } + + /** + * @return bool + */ + public function requiresDateAndDuration() + { + return MeetingInfoGet::TYPE_SCHEDULED === $this->meetingInfoGet->type + || MeetingInfoGet::TYPE_RECURRING_WITH_FIXED_TIME === $this->meetingInfoGet->type; + } + + /** + * @return bool + */ + public function requiresRegistration() + { + return + MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $this->meetingInfoGet->settings->approval_type; + } + + /** + * @return bool + */ + public function hasCloudAutoRecordingEnabled() + { + return 'cloud' === $this->meetingInfoGet->settings->auto_recording; + } + + /** + * @param User $user + * + * @return bool + */ + public function hasRegisteredUser($user) + { + return $this->getRegistrants()->exists( + function (RegistrantEntity $registrantEntity) use (&$user) { + return $registrantEntity->getUser() === $user; + } + ); + } + + /** + * @param User $user + * + * @return RegistrantEntity|null + */ + public function getRegistrant($user) + { + foreach ($this->getRegistrants() as $registrant) { + if ($registrant->getUser() === $user) { + return $registrant; + } + } + + return null; + } + + /** + * Generates a short presentation of the meeting for the future participant. + * To be displayed above the "Enter meeting" link. + * + * @return string + */ + public function getIntroduction() + { + $introduction = sprintf('
%s (%s)
', $this->formattedStartTime, $this->formattedDuration); + } + if ($this->user) { + $introduction .= sprintf('%s
', $this->user->getFullname()); + } elseif ($this->isCourseMeeting()) { + if (is_null($this->session)) { + $introduction .= sprintf('%s
', $this->course); + } else { + $introduction .= sprintf('%s (%s)
', $this->course, $this->session); + } + } + if (!empty($this->meetingInfoGet->agenda)) { + $introduction .= sprintf('%s
', $this->meetingInfoGet->agenda); + } + + return $introduction; + } + + /** + * @throws Exception on unexpected start_time or duration + */ + private function initializeDisplayableProperties() + { + $this->typeName = [ + API\Meeting::TYPE_INSTANT => get_lang('InstantMeeting'), + API\Meeting::TYPE_SCHEDULED => get_lang('ScheduledMeeting'), + API\Meeting::TYPE_RECURRING_WITH_NO_FIXED_TIME => get_lang('RecurringWithNoFixedTime'), + API\Meeting::TYPE_RECURRING_WITH_FIXED_TIME => get_lang('RecurringWithFixedTime'), + ][$this->meetingInfoGet->type]; + if (property_exists($this, 'status')) { + $this->statusName = [ + 'waiting' => get_lang('Waiting'), + 'started' => get_lang('Started'), + 'finished' => get_lang('Finished'), + ][$this->meetingInfoGet->status]; + } + $this->startDateTime = null; + $this->formattedStartTime = ''; + $this->durationInterval = null; + $this->formattedDuration = ''; + if (!empty($this->meetingInfoGet->start_time)) { + $this->startDateTime = new DateTime($this->meetingInfoGet->start_time); + $this->startDateTime->setTimezone(new DateTimeZone(date_default_timezone_get())); + $this->formattedStartTime = $this->startDateTime->format(get_lang('Y-m-d H:i')); + } + if (!empty($this->meetingInfoGet->duration)) { + $now = new DateTime(); + $later = new DateTime(); + $later->add(new DateInterval('PT'.$this->meetingInfoGet->duration.'M')); + $this->durationInterval = $later->diff($now); + $this->formattedDuration = $this->durationInterval->format(get_lang('DurationFormat')); + } + } +} diff --git a/plugin/zoom/Entity/RecordingEntity.php b/plugin/zoom/Entity/RecordingEntity.php new file mode 100644 index 0000000000..a933b3ece5 --- /dev/null +++ b/plugin/zoom/Entity/RecordingEntity.php @@ -0,0 +1,185 @@ +getRecordingMeeting(); + if (property_exists($object, $name)) { + return $object->$name; + } + throw new Exception(sprintf('%s does not know property %s', $this, $name)); + } + + /** + * @return string + */ + public function __toString() + { + return sprintf('Recording %d', $this->uuid); + } + + /** + * @return MeetingEntity + */ + public function getMeeting() + { + return $this->meeting; + } + + /** + * @throws Exception + * + * @return RecordingMeeting + */ + public function getRecordingMeeting() + { + return $this->recordingMeeting; + } + + /** + * @param MeetingEntity $meeting + * + * @return $this + */ + public function setMeeting($meeting) + { + $this->meeting = $meeting; + $this->meeting->getRecordings()->add($this); + + return $this; + } + + /** + * @param RecordingMeeting $recordingMeeting + * + * @throws Exception + * + * @return RecordingEntity + */ + public function setRecordingMeeting($recordingMeeting) + { + if (is_null($this->uuid)) { + $this->uuid = $recordingMeeting->uuid; + } elseif ($this->uuid !== $recordingMeeting->uuid) { + throw new Exception('the RecordingEntity identifier differs from the RecordingMeeting identifier'); + } + if (is_null($this->meeting)) { + $this->meeting = Database::getManager()->getRepository(MeetingEntity::class)->find($recordingMeeting->id); + // $this->meeting remains null when the remote RecordingMeeting refers to a deleted meeting + } elseif ($this->meeting->getId() != $recordingMeeting->id) { + throw new Exception('The RecordingEntity meeting id differs from the RecordingMeeting meeting id'); + } + $this->recordingMeeting = $recordingMeeting; + + return $this; + } + + /** + * @ORM\PostLoad + * + * @throws Exception + */ + public function postLoad() + { + if (!is_null($this->recordingMeetingJson)) { + $this->recordingMeeting = RecordingMeeting::fromJson($this->recordingMeetingJson); + } + $this->initializeExtraProperties(); + } + + /** + * @ORM\PreFlush + */ + public function preFlush() + { + if (!is_null($this->recordingMeeting)) { + $this->recordingMeetingJson = json_encode($this->recordingMeeting); + } + } + + /** + * @throws Exception + */ + public function initializeExtraProperties() + { + $this->startDateTime = new DateTime($this->recordingMeeting->start_time); + $this->startDateTime->setTimezone(new DateTimeZone(date_default_timezone_get())); + $this->formattedStartTime = $this->startDateTime->format(get_lang('Y-m-d H:i')); + + $now = new DateTime(); + $later = new DateTime(); + $later->add(new DateInterval('PT'.$this->recordingMeeting->duration.'M')); + $this->durationInterval = $later->diff($now); + $this->formattedDuration = $this->durationInterval->format(get_lang('DurationFormat')); + } +} diff --git a/plugin/zoom/Entity/RegistrantEntity.php b/plugin/zoom/Entity/RegistrantEntity.php new file mode 100644 index 0000000000..1c95244095 --- /dev/null +++ b/plugin/zoom/Entity/RegistrantEntity.php @@ -0,0 +1,260 @@ +id); + } + + /** + * @return MeetingEntity + */ + public function getMeeting() + { + return $this->meeting; + } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * @throws Exception + * + * @return MeetingRegistrantListItem + */ + public function getMeetingRegistrantListItem() + { + return $this->meetingRegistrantListItem; + } + + /** + * @throws Exception + * + * @return CreatedRegistration + */ + public function getCreatedRegistration() + { + return $this->createdRegistration; + } + + /** + * @throws Exception + * + * @return MeetingRegistrant + */ + public function getMeetingRegistrant() + { + return $this->meetingRegistrant; + } + + /** + * @param MeetingEntity $meeting + * + * @return $this + */ + public function setMeeting($meeting) + { + $this->meeting = $meeting; + $this->meeting->getRegistrants()->add($this); + + return $this; + } + + /** + * @param User $user + * + * @return $this + */ + public function setUser($user) + { + $this->user = $user; + + return $this; + } + + /** + * @param MeetingRegistrantListItem $meetingRegistrantListItem + * + * @throws Exception + * + * @return $this + */ + public function setMeetingRegistrantListItem($meetingRegistrantListItem) + { + if (!is_null($this->meeting) && $this->meeting->getId() != $meetingRegistrantListItem->id) { + throw new Exception('RegistrantEntity meeting id differs from MeetingRegistrantListItem id'); + } + $this->meetingRegistrantListItem = $meetingRegistrantListItem; + $this->computeFullName(); + + return $this; + } + + /** + * @param CreatedRegistration $createdRegistration + * + * @throws Exception + * + * @return $this + */ + public function setCreatedRegistration($createdRegistration) + { + if (is_null($this->id)) { + $this->id = $createdRegistration->registrant_id; + } elseif ($this->id != $createdRegistration->registrant_id) { + throw new Exception('RegistrantEntity id differs from CreatedRegistration identifier'); + } + $this->createdRegistration = $createdRegistration; + + return $this; + } + + /** + * @param MeetingRegistrant $meetingRegistrant + * + * @throws Exception + * + * @return $this + */ + public function setMeetingRegistrant($meetingRegistrant) + { + $this->meetingRegistrant = $meetingRegistrant; + $this->computeFullName(); + + return $this; + } + + /** + * @ORM\PostLoad + * + * @throws Exception + */ + public function postLoad() + { + if (!is_null($this->meetingRegistrantJson)) { + $this->meetingRegistrant = MeetingRegistrant::fromJson($this->meetingRegistrantJson); + } + if (!is_null($this->createdRegistrationJson)) { + $this->createdRegistration = CreatedRegistration::fromJson($this->createdRegistrationJson); + } + if (!is_null($this->meetingRegistrantListItemJson)) { + $this->meetingRegistrantListItem = MeetingRegistrantListItem::fromJson( + $this->meetingRegistrantListItemJson + ); + } + $this->computeFullName(); + } + + /** + * @ORM\PreFlush + */ + public function preFlush() + { + if (!is_null($this->meetingRegistrant)) { + $this->meetingRegistrantJson = json_encode($this->meetingRegistrant); + } + if (!is_null($this->createdRegistration)) { + $this->createdRegistrationJson = json_encode($this->createdRegistration); + } + if (!is_null($this->meetingRegistrantListItem)) { + $this->meetingRegistrantListItemJson = json_encode($this->meetingRegistrantListItem); + } + } + + public function computeFullName() + { + $this->fullName = api_get_person_name( + $this->meetingRegistrant->first_name, + $this->meetingRegistrant->last_name + ); + } +} diff --git a/plugin/zoom/README.code.md b/plugin/zoom/README.code.md new file mode 100644 index 0000000000..275ba64833 --- /dev/null +++ b/plugin/zoom/README.code.md @@ -0,0 +1,70 @@ +The Chamilo Zoom plugin class itself is defined in _plugin/zoom/lib/zoom_plugin.class.php_ + +It manipulates both **remote Zoom server objects** and **local database entities**: + +# Local database entities + +The local entities map the remote objects to Chamilo courses/sessions and users. + +They also maintain a cache of the matching remote objects. + +_Entity/*Entity.php_ are the local database entity classes. + +Doctrine entity manager repository classes are in _lib/*EntityRepository.php_. + +# Remote Zoom server objets + +_lib/API/*.php_ contains the Zoom API data structure definition classes, +based on Zoom's own API specification: + +* https://marketplace.zoom.us/docs/api-reference/zoom-api +* https://marketplace.zoom.us/docs/api-reference/zoom-api/Zoom%20API.oas2.json + +These classes provide methods to list, create, update and delete the remote objects. + +# JWT Client + +API class methods use a JWT client implemented in _lib/API/JWTClient.php_. + +The plugin constructor initializes the JWT Client, giving it required API key and secret. + +# Event notification handler + +_endpoint.php_ is the Zoom API event notification web hook end point. + +It handles notifications sent by Zoom servers on useful events : + +* meeting start and end, +* registrant join and leave, +* recordings created and deleted + +# Administrative interface + +_admin.php_ is the administrative interface. +It lists all meetings and recordings. + +# Course tool + +_start.php_ is the **course** tool target: + +* to the course teacher, it shows a course meeting management interface; +* to the course learners, it shows the list of scheduled course meetings. + +# Home page's profile block (also on "My Courses" page) + +This plugin can add 3 kinds of links to "profile block" : + +1. _join_meeting.php?meetingId=…_ links to upcoming meetings accessible to the current user. +_join_meeting.php_ presents the meeting and shows a link to enter the meeting. +2. _user.php_ is the **user**'s own meeting management interface. +3. _global.php_ directs the user to _join_meeting.php_ with the **global** meeting. + +# Meeting management page + +_admin.php_, _start.php_ and _user.php_ link to _meeting.php_. + +_meeting.php_ is the meeting management page, where one can manage + +* the meeting properties, +* the list of its registrants and +* its recordings. \ No newline at end of file diff --git a/plugin/zoom/README.md b/plugin/zoom/README.md index d00088f335..51e3da5cc0 100644 --- a/plugin/zoom/README.md +++ b/plugin/zoom/README.md @@ -1 +1,35 @@ -This plugin allows teachers to launch a Zoom conference at any time and students to join it. \ No newline at end of file +This plugin adds Zoom meetings, user registration to meetings and meeting recordings. + +## Meetings + +A **meeting** can be linked to a local **user** and/or a local **course**/**session**: + + * a meeting with a course is a _course meeting_; + * a meeting with a user and no course is a _user meeting_; + * a meeting with no course nor user is a _global meeting_. + +## Registrants + +A **registrant** is the registration of a local user to a meeting. + +Users do not register themselves to meetings. + +* They are registered to a course meeting by the course manager. +* They are registered to a user meeting by that user. +* They are registered automatically to the global meeting, when they enter it. + +## Recordings + +A **recording** is the list of files created during a past meeting instance. + +Course meeting files can be copied to the course by the course manager. + +# Required Zoom user account + +Recordings and user registration are only available to paying Zoom customers. + +For a non-paying Zoom user, this plugin still works but participants will join anonymously. + +# Contributing + +Read README.code.md for an introduction to the plugin's code. \ No newline at end of file diff --git a/plugin/zoom/admin.php b/plugin/zoom/admin.php index e8affc4b04..e9a9e1511e 100644 --- a/plugin/zoom/admin.php +++ b/plugin/zoom/admin.php @@ -16,14 +16,13 @@ $plugin = ZoomPlugin::create(); $this_section = SECTION_PLATFORM_ADMIN; $form = $plugin->getAdminSearchForm(); -$startDate = new DateTime($form->getElement('start')->getValue()); -$endDate = new DateTime($form->getElement('end')->getValue()); -$type = $form->getElement('type')->getValue(); +$startDate = new DateTime($form->getSubmitValue('start')); +$endDate = new DateTime($form->getSubmitValue('end')); $tpl = new Template($tool_name); -$tpl->assign('meetings', $plugin->getPeriodMeetings($type, $startDate, $endDate)); +$tpl->assign('meetings', $plugin->getMeetingRepository()->periodMeetings($startDate, $endDate)); if ($plugin->get('enableCloudRecording')) { - $tpl->assign('recordings', $plugin->getRecordings($startDate, $endDate)); + $tpl->assign('recordings', $plugin->getRecordingRepository()->getPeriodRecordings($startDate, $endDate)); } $tpl->assign('search_form', $form->returnForm()); $tpl->assign('content', $tpl->fetch('zoom/view/admin.tpl')); diff --git a/plugin/zoom/endpoint.php b/plugin/zoom/endpoint.php new file mode 100644 index 0000000000..528e5862a7 --- /dev/null +++ b/plugin/zoom/endpoint.php @@ -0,0 +1,100 @@ +event) || !isset($decoded->payload->object)) { + error_log(sprintf('Did not recognize event notification: %s', $body)); + http_response_code(422); // Unprocessable Entity + exit; +} +$object = $decoded->payload->object; +list($objectType, $action) = explode('.', $decoded->event); +switch ($objectType) { + case 'meeting': + $meetingRepository = $entityManager->getRepository(MeetingEntity::class); + $registrantRepository = $entityManager->getRepository(RegistrantEntity::class); + switch ($action) { + case 'deleted': + $meetingEntity = $meetingRepository->find($object->id); + if (!is_null($meetingEntity)) { + $entityManager->remove($meetingEntity); + } + break; + case 'ended': + case 'started': + $entityManager->persist( + ( + $meetingRepository->find($object->id)->setStatus($action) + ?: (new MeetingEntity())->setMeetingInfoGet(MeetingInfoGet::fromObject($object)) + ) + ); + $entityManager->flush(); + break; + case 'participant_joined': + case 'participant_left': + $registrant = $registrantRepository->find($object->participant->id); + if (!is_null($registrant)) { + // TODO log attendance + } + break; + case 'alert': + default: + error_log(sprintf('Event "%s" on %s was unhandled: %s', $action, $objectType, $body)); + http_response_code(501); // Not Implemented + } + break; + case 'recording': + $recordingRepository = $entityManager->getRepository(RecordingEntity::class); + switch ($action) { + case 'completed': + $entityManager->persist( + (new RecordingEntity())->setRecordingMeeting(RecordingMeeting::fromObject($object)) + ); + $entityManager->flush(); + break; + case 'recovered': + if (is_null($recordingRepository->find($object->uuid))) { + $entityManager->persist( + (new RecordingEntity())->setRecordingMeeting(RecordingMeeting::fromObject($object)) + ); + $entityManager->flush(); + } + break; + case 'trashed': + case 'deleted': + $recordingEntity = $recordingRepository->find($object->uuid); + if (!is_null($recordingEntity)) { + $entityManager->remove($recordingEntity); + $entityManager->flush(); + } + break; + default: + error_log(sprintf('Event "%s" on %s was unhandled: %s', $action, $objectType, $body)); + http_response_code(501); // Not Implemented + } + break; + default: + error_log(sprintf('Event "%s" on %s was unhandled: %s', $action, $objectType, $body)); + http_response_code(501); // Not Implemented +} diff --git a/plugin/zoom/global.php b/plugin/zoom/global.php new file mode 100644 index 0000000000..26f076a75d --- /dev/null +++ b/plugin/zoom/global.php @@ -0,0 +1,12 @@ +getGlobalMeeting()->getId()); diff --git a/plugin/zoom/join_meeting.php b/plugin/zoom/join_meeting.php new file mode 100644 index 0000000000..8871b0ae68 --- /dev/null +++ b/plugin/zoom/join_meeting.php @@ -0,0 +1,39 @@ +get_title()); + +if (array_key_exists('meetingId', $_REQUEST)) { + /** @var MeetingEntity $meeting */ + $meeting = $plugin->getMeetingRepository()->find($_REQUEST['meetingId']); + try { + if (is_null($meeting)) { + throw new Exception('Meeting not found'); + } + printf( + '%s', + $meeting->getIntroduction(), + $plugin->getStartOrJoinMeetingURL($meeting), + get_lang('EnterMeeting') + ); + } catch (Exception $exception) { + Display::addFlash( + Display::return_message($exception->getMessage(), 'error') + ); + } +} + +Display::display_footer(); diff --git a/plugin/zoom/lang/english.php b/plugin/zoom/lang/english.php index 78d36753c2..25d0a1b8fe 100755 --- a/plugin/zoom/lang/english.php +++ b/plugin/zoom/lang/english.php @@ -8,6 +8,7 @@ $strings['plugin_comment'] = "Zoom Videoconference integration in courses and se $strings['tool_enable'] = 'Zoom videoconference tool enabled'; $strings['apiKey'] = 'API Key'; $strings['apiSecret'] = 'API Secret'; +$strings['verificationToken'] = 'Verification Token'; $strings['enableParticipantRegistration'] = 'Enable participant registration'; $strings['enableCloudRecording'] = 'Enable cloud recording'; $strings['enableGlobalConference'] = 'Enable global conference'; @@ -28,10 +29,24 @@ To get them, create a JWT App :https://your.chamilo.url/plugin/zoom/endpoint.php
+and add these event types:
+https://your.chamilo.url/plugin/zoom/endpoint.php
+et ajoutez ces types d'événements :
+https://your.chamilo.url/plugin/zoom/endpoint.php
+y agrega este tipo de eventos:
+{{ meeting.typeName }} {{ meeting.id }} ({{ meeting.statusName }})
++ {{ meeting.typeName }} {{ meeting.id }} ({{ meeting.meetingInfoGet.status }}) +
+{% if meeting.meetingInfoGet.status != 'finished' %} ++ + {{ 'EnterMeeting'|get_lang }} + +
+{% endif %} {% if isConferenceManager and meeting.status == 'waiting' %}- + {{ 'StartMeeting'|get_lang }}
@@ -16,11 +25,11 @@ {% endif %} -{% if meeting.settings.approval_type == 2 %} +{% if meeting.meetingInfoGet.settings.approval_type == 2 %}{% endif %} @@ -32,7 +41,7 @@ {{ deleteMeetingForm }} {{ registerParticipantForm }} {{ fileForm }} -{% if registrants and meeting.settings.approval_type != 2 %} +{% if registrants and meeting.meetingInfoGet.settings.approval_type != 2 %}