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

', $this->meetingInfoGet->topic); + if (!$this->isGlobalMeeting()) { + $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

%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 :
2. click on Advanced / Application Marketplace
3. click on Develop / build App
4. choose JWT / Create -
5. fill in information about your \"App\" +
5. Information: fill in fields about your \"App\" (application and company names, contact name and email address) -
6. click on Continue -Locate your API Key and Secret in the App Credentials page. +
6. Click on Continue +
7. App Credentials: copy your API Key and Secret to these fields below +
8. click on Continue +
9. Feature: +enable Event Subscriptions to add a new one with endpoint URL +https://your.chamilo.url/plugin/zoom/endpoint.php +and add these event types: +
- Start Meeting +
- End Meeting +
- Participant/Host joined meeting +
- Participant/Host left meeting +
- All Recordings have completed +
- Recording transcript files have completed +
then click on Done then on Save +and copy your Verification Token to the field below. +
10. click on Continue
Attention:
Zoom is NOT free software and specific rules apply to personal data protection. @@ -46,6 +61,7 @@ Will not work for a basic profile."; // please keep these lines alphabetically sorted $strings['AllCourseUsersWereRegistered'] = "All course students were registered"; $strings['Agenda'] = "Agenda"; +$strings['CannotRegisterWithoutEmailAddress'] = "Cannot register without email address"; $strings['CopyingJoinURL'] = "Copying join URL"; $strings['CopyJoinAsURL'] = "Copy 'join as' URL"; $strings['CopyToCourse'] = "Copy to course"; @@ -53,6 +69,8 @@ $strings['CouldNotCopyJoinURL'] = "Could not copy join URL"; $strings['Course'] = "Cours"; $strings['CreatedAt'] = "Created at"; $strings['CreateLinkInCourse'] = "Create link(s) in course"; +$strings['CreateUserVideoConference'] = "Create user conferences"; +$strings['DateMeetingTitle'] = "%s: %s"; $strings['DeleteMeeting'] = "Delete meeting"; $strings['DeleteFile'] = "Delete file(s)"; $strings['Details'] = "Details"; @@ -61,13 +79,16 @@ $strings['Duration'] = "Duration"; $strings['DurationFormat'] = "%hh%I"; $strings['DurationInMinutes'] = "Duration (in minutes)"; $strings['EndDate'] = "End Date"; +$strings['EnterMeeting'] = "Enter meeting"; $strings['Files'] = "Files"; $strings['Finished'] = "finished"; $strings['FileWasCopiedToCourse'] = "The file was copied to the course"; $strings['FileWasDeleted'] = "The file was deleted"; +$strings['GlobalMeeting'] = "Global meeting"; +$strings['GroupUsersWereRegistered'] = "Group members were registered"; $strings['InstantMeeting'] = "Instant meeting"; $strings['Join'] = "Join"; -$strings['JoinMeetingAsMyself'] = "Join meeting as myself"; +$strings['JoinGlobalVideoConference'] = "Join global conference"; $strings['JoinURLCopied'] = "Join URL copied"; $strings['JoinURLToSendToParticipants'] = "Join URL to send to participants"; $strings['LiveMeetings'] = "Live meetings"; @@ -105,4 +126,5 @@ $strings['UserRegistration'] = "User registration"; $strings['Y-m-d H:i'] = "Y-m-d H:i"; $strings['Waiting'] = "waiting"; $strings['XRecordingOfMeetingXFromXDurationXDotX'] = "%s recording of meeting %s from %s (%s).%s"; +$strings['YouAreNotRegisteredToThisMeeting'] = "You are not registered to this meeting"; $strings['ZoomVideoConferences'] = "Zoom Video Conferences"; diff --git a/plugin/zoom/lang/french.php b/plugin/zoom/lang/french.php index 58b1076e63..dca8b5babe 100755 --- a/plugin/zoom/lang/french.php +++ b/plugin/zoom/lang/french.php @@ -26,10 +26,25 @@ Pour les obtenir, créez une JWT app :
2. cliquez sur Avancé / Marketplace d'application
3. cliquez sur Develop / build App
4. choisissez JWT / Create -
5. saisissez quelques informations sur votre \"App\" +
5. Information: remplissez quelques champs à propos de votre \"App\" (noms de l'application, de l'entreprise, nom et adresse de courriel de contact)
6. cliquez sur Continue -
La page App Credentials affiche la clé (API Key) et le code secret (API Secret) à saisir ici. +
7. App Credentials : +copiez la clé (API Key) et le code secret (API Secret) dans les champs ci-dessous. +
8. cliquez sur Continue +
9. Feature : +activez Event Subscriptions pour en ajouter une avec comme endpoint URL +https://your.chamilo.url/plugin/zoom/endpoint.php +et ajoutez ces types d'événements : +
- Start Meeting +
- End Meeting +
- Participant/Host joined meeting +
- Participant/Host left meeting +
- All Recordings have completed +
- Recording transcript files have completed +
puis cliquez sur Done puis sur Save +et copiez votre Verification Token dans le champ ci-dessous. +
10. cliquez sur Continue
Attention :
Zoom n'est PAS un logiciel libre @@ -45,6 +60,7 @@ Ne fonctionnera pas pour un profil de base."; // please keep these lines alphabetically sorted $strings['AllCourseUsersWereRegistered'] = "Tous les étudiants du cours sont inscrits"; $strings['Agenda'] = "Ordre du jour"; +$strings['CannotRegisterWithoutEmailAddress'] = "Impossible d'inscrire un utilisateur sans adresse de courriel"; $strings['CopyingJoinURL'] = "Copie de l'URL pour rejoindre en cours"; $strings['CopyJoinAsURL'] = "Copier l'URL pour 'rejoindre en tant que'"; $strings['CopyToCourse'] = "Copier dans le cours"; @@ -52,6 +68,8 @@ $strings['CouldNotCopyJoinURL'] = "Échec de la copie de l'URL pour rejoindre"; $strings['Course'] = "Cours"; $strings['CreatedAt'] = "Créé à"; $strings['CreateLinkInCourse'] = "Créer dans le cours un ou des lien(s) vers le(s) fichier(s)"; +$strings['CreateUserVideoConference'] = "Créer des conferences par utilisateur"; +$strings['DateMeetingTitle'] = "%s : %s"; $strings['DeleteMeeting'] = "Effacer la conférence"; $strings['DeleteFile'] = "Supprimer ce(s) fichier(s)"; $strings['Details'] = "Détail"; @@ -60,13 +78,16 @@ $strings['Duration'] = "Durée"; $strings['DurationFormat'] = "%hh%I"; $strings['DurationInMinutes'] = "Durée (en minutes)"; $strings['EndDate'] = "Date de fin"; +$strings['EnterMeeting'] = "Entrer dans la conférence"; $strings['Files'] = "Fichiers"; $strings['Finished'] = "terminée"; $strings['FileWasCopiedToCourse'] = "Le fichier a été copié dans le cours"; $strings['FileWasDeleted'] = "Le fichier a été effacé"; +$strings['GlobalMeeting'] = "Conférence globale"; +$strings['GroupUsersWereRegistered'] = "Les membres des groupes ont été inscrits"; $strings['InstantMeeting'] = "Conférence instantanée"; $strings['Join'] = "Rejoindre"; -$strings['JoinMeetingAsMyself'] = "Rejoindre la conférence en tant que moi-même"; +$strings['JoinGlobalVideoConference'] = "Rejoindre la conférence globale"; $strings['JoinURLCopied'] = "URL pour rejoindre copiée"; $strings['JoinURLToSendToParticipants'] = "URL pour assister à la conférence (à envoyer aux participants)"; $strings['LiveMeetings'] = "Conférences en cours"; @@ -102,6 +123,8 @@ $strings['UpdateMeeting'] = "Mettre à jour la conférence"; $strings['UpdateRegisteredUserList'] = "Mettre à jour la liste des utilisateurs inscrits"; $strings['UserRegistration'] = "Inscription des utilisateurs"; $strings['Y-m-d H:i'] = "d/m/Y à H\hi"; +$strings['verificationToken'] = 'Verification Token'; $strings['Waiting'] = "en attente"; $strings['XRecordingOfMeetingXFromXDurationXDotX'] = "Enregistrement (%s) de la conférence %s de %s (%s).%s"; +$strings['YouAreNotRegisteredToThisMeeting'] = "Vous n'êtes pas inscrit à cette conférence"; $strings['ZoomVideoConferences'] = "Conférences vidéo Zoom"; diff --git a/plugin/zoom/lang/spanish.php b/plugin/zoom/lang/spanish.php index a90a0ab0f0..d3b3a589ff 100644 --- a/plugin/zoom/lang/spanish.php +++ b/plugin/zoom/lang/spanish.php @@ -26,10 +26,26 @@ Para obtenerlos, crea una app JWT :
2. de clic en Avanzado / Marketplace de aplicaciones
3. de clic en Develop / build App
4. escoja JWT / Create -
5. ingrese algunas informaciones sobre vuestra \"App\" +
5. Information: ingrese algunas informaciones sobre vuestra \"App\" (nombres de la aplicación, de la empresa, nombre y dirección de correo de contacto)
6. de clic en Continue -
La página App Credentials muestra la clave (API Key) y el código secreto (API Secret) por ingresar aquí. +
7. App Credentials: +muestra la clave (API Key) y el código secreto (API Secret) por ingresar aquí. +copiez la clé (API Key) et le code secret (API Secret) dans les champs ci-dessous. +
8. de clic en Continue +
9. Feature : +activez Event Subscriptions para agregar uno con su endpoint URL +https://your.chamilo.url/plugin/zoom/endpoint.php +y agrega este tipo de eventos: +
- Start Meeting +
- End Meeting +
- Participant/Host joined meeting +
- Participant/Host left meeting +
- All Recordings have completed +
- Recording transcript files have completed +
de clic en Done y luego en Save +y copie su Verification Token en el campo a continuación. +
10. de clic en Continue
Atención :
Zoom NO ES un software libre, y reglas específicas de protección de datos se aplican a este. @@ -44,6 +60,7 @@ No funcionará para un perfil base/gratuito."; // please keep these lines alphabetically sorted $strings['AllCourseUsersWereRegistered'] = "Todos los alumnos del curso están inscritos"; $strings['Agenda'] = "Orden del día"; +$strings['CannotRegisterWithoutEmailAddress'] = "No se puede registrar usuario sin dirección de correo electrónico"; $strings['CopyingJoinURL'] = "Copia de la URL para ingresar"; $strings['CopyJoinAsURL'] = "Copiar la URL para 'ingresar como'"; $strings['CopyToCourse'] = "Copiar en el curso"; @@ -51,6 +68,8 @@ $strings['CouldNotCopyJoinURL'] = "Falló la copia de la URL de ingreso"; $strings['Course'] = "Curso"; $strings['CreatedAt'] = "Creado el"; $strings['CreateLinkInCourse'] = "Crear en el curso uno o más vínculos hacia el/los archivo(s)"; +$strings['CreateUserVideoConference'] = "Crear conferencias de usario"; +$strings['DateMeetingTitle'] = "%s: %s"; $strings['DeleteMeeting'] = "Borrar la conferencia"; $strings['DeleteFile'] = "Borrar este/estos archivo(s)"; $strings['Details'] = "Detalle"; @@ -59,13 +78,16 @@ $strings['Duration'] = "Duración"; $strings['DurationFormat'] = "%hh%I"; $strings['DurationInMinutes'] = "Duración (en minutos)"; $strings['EndDate'] = "Fecha de fin"; +$strings['EnterMeeting'] = "Ingresar la conferencia"; $strings['Files'] = "Archivos"; $strings['Finished'] = "terminada"; $strings['FileWasCopiedToCourse'] = "El archivo ha sido copiado en el curso"; $strings['FileWasDeleted'] = "El archivo ha sido borrado"; +$strings['GlobalMeeting'] = "Conferencia global"; +$strings['GroupUsersWereRegistered'] = "Miembros de los grupos han sido registrados"; $strings['InstantMeeting'] = "Conferencia instantánea"; $strings['Join'] = "Ingresar"; -$strings['JoinMeetingAsMyself'] = "Ingresar la conferencia como yo mismo"; +$strings['JoinGlobalVideoConference'] = "Ingresar la conrencia global"; $strings['JoinURLCopied'] = "URL para juntarse copiada"; $strings['JoinURLToSendToParticipants'] = "URL para asistir a la conferencia (para enviar a los participantes)"; $strings['LiveMeetings'] = "Conferencias activas"; @@ -101,6 +123,8 @@ $strings['UpdateMeeting'] = "Actualizar la conferencia"; $strings['UpdateRegisteredUserList'] = "Actualizar la lista de usuarios inscritos"; $strings['UserRegistration'] = "Inscripción de los usuarios"; $strings['Y-m-d H:i'] = "d/m/Y a las H\hi"; +$strings['verificationToken'] = 'Verification Token'; $strings['Waiting'] = "en espera"; $strings['XRecordingOfMeetingXFromXDurationXDotX'] = "Grabación (%s) de la conferencia %s de %s (%s).%s"; +$strings['YouAreNotRegisteredToThisMeeting'] = "No estás registrado en esta reunión"; $strings['ZoomVideoConferences'] = "Videoconferencias Zoom"; diff --git a/plugin/zoom/lib/API/Client.php b/plugin/zoom/lib/API/Client.php index 53d08b793a..262cd072ea 100644 --- a/plugin/zoom/lib/API/Client.php +++ b/plugin/zoom/lib/API/Client.php @@ -13,8 +13,21 @@ use Exception; * * @package Chamilo\PluginBundle\Zoom\API */ -interface Client +abstract class Client { + /** @var Client */ + private static $instance; + + /** + * Returns an initialized Client. + * + * @return Client + */ + public static function getInstance() + { + return self::$instance; + } + /** * Sends a Zoom API-compliant HTTP request and retrieves the response. * @@ -30,5 +43,15 @@ interface Client * * @return string response body (not json-decoded) */ - public function send($httpMethod, $relativePath, $parameters = [], $requestBody = null); + abstract public function send($httpMethod, $relativePath, $parameters = [], $requestBody = null); + + /** + * Registers an initialized Client. + * + * @param Client $instance + */ + protected static function register($instance) + { + self::$instance = $instance; + } } diff --git a/plugin/zoom/lib/API/JWTClient.php b/plugin/zoom/lib/API/JWTClient.php index 0176b220c6..ead9545d9d 100755 --- a/plugin/zoom/lib/API/JWTClient.php +++ b/plugin/zoom/lib/API/JWTClient.php @@ -13,7 +13,7 @@ use Firebase\JWT\JWT; * * @package Chamilo\PluginBundle\Zoom */ -class JWTClient implements Client +class JWTClient extends Client { public $token; @@ -33,6 +33,7 @@ class JWTClient implements Client ], $apiSecret ); + self::register($this); } /** diff --git a/plugin/zoom/lib/API/JsonDeserializableTrait.php b/plugin/zoom/lib/API/JsonDeserializableTrait.php index 203938d48e..63301a035f 100644 --- a/plugin/zoom/lib/API/JsonDeserializableTrait.php +++ b/plugin/zoom/lib/API/JsonDeserializableTrait.php @@ -32,6 +32,20 @@ trait JsonDeserializableTrait throw new Exception('Could not decode JSON: '.$json); } + return static::fromObject($object); + } + + /** + * Builds a class instance from an already json-decoded object. + * + * @param object $object + * + * @throws Exception on unexpected object property + * + * @return static + */ + public static function fromObject($object) + { $instance = new static(); static::recursivelyCopyObjectProperties($object, $instance); @@ -101,7 +115,7 @@ trait JsonDeserializableTrait $destination->$name = $value; } } else { - throw new Exception("Source object has property $name, which was not expected."); + error_log("Source object has property $name, which was not expected: ".json_encode($source)); } } $destination->initializeExtraProperties(); diff --git a/plugin/zoom/lib/API/Meeting.php b/plugin/zoom/lib/API/Meeting.php index c26c24d92d..8e8e7e9d80 100644 --- a/plugin/zoom/lib/API/Meeting.php +++ b/plugin/zoom/lib/API/Meeting.php @@ -57,15 +57,13 @@ class Meeting /** * Creates a meeting on the server and returns the resulting MeetingInfoGet. * - * @param Client $client an API client - * * @throws Exception describing the error (message and code) * * @return MeetingInfoGet meeting */ - public function create($client) + public function create() { - return MeetingInfoGet::fromJson($client->send('POST', 'users/me/meetings', [], $this)); + return MeetingInfoGet::fromJson(Client::getInstance()->send('POST', 'users/me/meetings', [], $this)); } /** @@ -74,11 +72,9 @@ class Meeting * @param string $topic * @param int $type * - * @throws Exception - * * @return static */ - protected static function fromTopicAndType($topic, $type = self::TYPE_SCHEDULED) + public static function fromTopicAndType($topic, $type = self::TYPE_SCHEDULED) { $instance = new static(); $instance->topic = $topic; diff --git a/plugin/zoom/lib/API/MeetingInfoGet.php b/plugin/zoom/lib/API/MeetingInfoGet.php index f8fea4c5af..8998591b9d 100644 --- a/plugin/zoom/lib/API/MeetingInfoGet.php +++ b/plugin/zoom/lib/API/MeetingInfoGet.php @@ -34,58 +34,50 @@ class MeetingInfoGet extends MeetingInfo /** * Retrieves a meeting from its numeric identifier. * - * @param Client $client - * @param int $id + * @param int $id * * @throws Exception * * @return static the meeting */ - public static function fromId($client, $id) + public static function fromId($id) { - return static::fromJson($client->send('GET', "meetings/$id")); + return static::fromJson(Client::getInstance()->send('GET', "meetings/$id")); } /** * Updates the meeting on server. * - * @param Client $client - * * @throws Exception */ - public function update($client) + public function update() { - $client->send('PATCH', 'meetings/'.$this->id, [], $this); + Client::getInstance()->send('PATCH', 'meetings/'.$this->id, [], $this); } /** * Ends the meeting on server. * - * @param Client $client - * * @throws Exception */ - public function endNow($client) + public function endNow() { - $client->send('PUT', "meetings/$this->id/status", [], (object) ['action' => 'end']); + Client::getInstance()->send('PUT', "meetings/$this->id/status", [], (object) ['action' => 'end']); } /** * Deletes the meeting on server. * - * @param Client $client - * * @throws Exception */ - public function delete($client) + public function delete() { - $client->send('DELETE', "meetings/$this->id"); + Client::getInstance()->send('DELETE', "meetings/$this->id"); } /** * Adds a registrant to the meeting. * - * @param Client $client * @param MeetingRegistrant $registrant with at least 'email' and 'first_name'. * 'last_name' will also be recorded by Zoom. * Other properties remain ignored, or not returned by Zoom @@ -96,10 +88,10 @@ class MeetingInfoGet extends MeetingInfo * * @return CreatedRegistration with unique join_url and registrant_id properties */ - public function addRegistrant($client, $registrant, $occurrenceIds = '') + public function addRegistrant($registrant, $occurrenceIds = '') { return CreatedRegistration::fromJson( - $client->send( + Client::getInstance()->send( 'POST', "meetings/$this->id/registrants", empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds], @@ -111,16 +103,15 @@ class MeetingInfoGet extends MeetingInfo /** * Removes registrants from the meeting. * - * @param Client $client * @param MeetingRegistrant[] $registrants registrants to remove (id and email) * @param string $occurrenceIds separated by comma * * @throws Exception */ - public function removeRegistrants($client, $registrants, $occurrenceIds = '') + public function removeRegistrants($registrants, $occurrenceIds = '') { if (!empty($registrants)) { - $client->send( + Client::getInstance()->send( 'PUT', "meetings/$this->id/registrants/status", empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds], @@ -135,28 +126,24 @@ class MeetingInfoGet extends MeetingInfo /** * Retrieves meeting registrants. * - * @param Client $client - * * @throws Exception * * @return MeetingRegistrantListItem[] the meeting registrants */ - public function getRegistrants($client) + public function getRegistrants() { - return MeetingRegistrantList::loadMeetingRegistrants($client, $this->id); + return MeetingRegistrantList::loadMeetingRegistrants($this->id); } /** * Retrieves the meeting's instances. * - * @param Client $client - * * @throws Exception * * @return MeetingInstance[] */ - public function getInstances($client) + public function getInstances() { - return MeetingInstances::fromMeetingId($client, $this->id)->meetings; + return MeetingInstances::fromMeetingId($this->id)->meetings; } } diff --git a/plugin/zoom/lib/API/MeetingInstance.php b/plugin/zoom/lib/API/MeetingInstance.php index 8be93df7c0..c0e98da2dc 100644 --- a/plugin/zoom/lib/API/MeetingInstance.php +++ b/plugin/zoom/lib/API/MeetingInstance.php @@ -26,28 +26,26 @@ class MeetingInstance /** * Retrieves the recording of the instance. * - * @param Client $client - * * @throws Exception with code 404 when there is no recording for this meeting * * @return RecordingMeeting the recording */ - public function getRecordings($client) + public function getRecordings() { - return RecordingMeeting::fromJson($client->send('GET', 'meetings/'.htmlentities($this->uuid).'/recordings')); + return RecordingMeeting::fromJson( + Client::getInstance()->send('GET', 'meetings/'.htmlentities($this->uuid).'/recordings') + ); } /** * Retrieves the instance's participants. * - * @param Client $client - * * @throws Exception * * @return ParticipantListItem[] */ - public function getParticipants($client) + public function getParticipants() { - return ParticipantList::loadInstanceParticipants($client, $this->uuid); + return ParticipantList::loadInstanceParticipants($this->uuid); } } diff --git a/plugin/zoom/lib/API/MeetingInstances.php b/plugin/zoom/lib/API/MeetingInstances.php index 3f1ceaafc7..4666ada4e2 100644 --- a/plugin/zoom/lib/API/MeetingInstances.php +++ b/plugin/zoom/lib/API/MeetingInstances.php @@ -30,16 +30,15 @@ class MeetingInstances /** * Retrieves a meeting's instances. * - * @param Client $client - * @param int $meetingId + * @param int $meetingId * * @throws Exception * * @return MeetingInstances the meeting's instances */ - public static function fromMeetingId($client, $meetingId) + public static function fromMeetingId($meetingId) { - return static::fromJson($client->send('GET', "past_meetings/$meetingId/instances")); + return static::fromJson(Client::getInstance()->send('GET', "past_meetings/$meetingId/instances")); } /** diff --git a/plugin/zoom/lib/API/MeetingList.php b/plugin/zoom/lib/API/MeetingList.php index 50d6f4def9..e272636744 100644 --- a/plugin/zoom/lib/API/MeetingList.php +++ b/plugin/zoom/lib/API/MeetingList.php @@ -35,16 +35,15 @@ class MeetingList /** * Retrieves all meetings of a type. * - * @param Client $client - * @param int $type TYPE_SCHEDULED, TYPE_LIVE or TYPE_UPCOMING + * @param int $type TYPE_SCHEDULED, TYPE_LIVE or TYPE_UPCOMING * * @throws Exception * * @return MeetingListItem[] all meetings */ - public static function loadMeetings($client, $type) + public static function loadMeetings($type) { - return static::loadItems('meetings', $client, 'users/me/meetings', ['type' => $type]); + return static::loadItems('meetings', 'users/me/meetings', ['type' => $type]); } /** diff --git a/plugin/zoom/lib/API/MeetingRegistrant.php b/plugin/zoom/lib/API/MeetingRegistrant.php index 727f26ae08..f551751a79 100644 --- a/plugin/zoom/lib/API/MeetingRegistrant.php +++ b/plugin/zoom/lib/API/MeetingRegistrant.php @@ -77,14 +77,19 @@ class MeetingRegistrant /** * @param string $email * @param string $firstName + * @param string $lastName * * @return MeetingRegistrant */ - public static function fromEmailAndFirstName($email, $firstName) + public static function fromEmailAndFirstName($email, $firstName, $lastName = null) { $instance = new static(); $instance->first_name = $firstName; $instance->email = $email; + $instance->first_name = $firstName; + if (!is_null($lastName)) { + $instance->last_name = $lastName; + } return $instance; } diff --git a/plugin/zoom/lib/API/MeetingRegistrantList.php b/plugin/zoom/lib/API/MeetingRegistrantList.php index 5bc4cf75c0..84d569b520 100644 --- a/plugin/zoom/lib/API/MeetingRegistrantList.php +++ b/plugin/zoom/lib/API/MeetingRegistrantList.php @@ -30,16 +30,15 @@ class MeetingRegistrantList /** * Retrieves all registrant for a meeting. * - * @param Client $client - * @param int $meetingId + * @param int $meetingId * * @throws Exception * * @return MeetingRegistrantListItem[] all registrants of the meeting */ - public static function loadMeetingRegistrants($client, $meetingId) + public static function loadMeetingRegistrants($meetingId) { - return static::loadItems('registrants', $client, "meetings/$meetingId/registrants"); + return static::loadItems('registrants', "meetings/$meetingId/registrants"); } /** diff --git a/plugin/zoom/lib/API/MeetingSettings.php b/plugin/zoom/lib/API/MeetingSettings.php index fca858f56b..3f0a4ce95b 100644 --- a/plugin/zoom/lib/API/MeetingSettings.php +++ b/plugin/zoom/lib/API/MeetingSettings.php @@ -80,6 +80,9 @@ class MeetingSettings /** @var bool Enable waiting room */ public $waiting_room; + /** @var bool undocumented */ + public $request_permission_to_unmute_participants; + /** @var string[] List of global dial-in countries */ public $global_dial_in_countries; diff --git a/plugin/zoom/lib/API/Pagination.php b/plugin/zoom/lib/API/Pagination.php index 14a42163b5..9c53b992a2 100644 --- a/plugin/zoom/lib/API/Pagination.php +++ b/plugin/zoom/lib/API/Pagination.php @@ -32,7 +32,6 @@ trait Pagination * Retrieves all items from the server, possibly generating several API calls. * * @param string $arrayPropertyName item array property name - * @param Client $client * @param string $relativePath relative path to pass to Client::send * @param array $parameters parameter array to pass to Client::send * @@ -40,7 +39,7 @@ trait Pagination * * @return array united list of items */ - protected static function loadItems($arrayPropertyName, $client, $relativePath, $parameters = []) + protected static function loadItems($arrayPropertyName, $relativePath, $parameters = []) { $items = []; $pageCount = 1; @@ -48,7 +47,7 @@ trait Pagination $totalRecords = 0; for ($pageNumber = 1; $pageNumber <= $pageCount; $pageNumber++) { $response = static::fromJson( - $client->send( + Client::getInstance()->send( 'GET', $relativePath, array_merge(['page_size' => $pageSize, 'page_number' => $pageNumber], $parameters) diff --git a/plugin/zoom/lib/API/PaginationToken.php b/plugin/zoom/lib/API/PaginationToken.php index 3194f45a27..4c19cb0013 100644 --- a/plugin/zoom/lib/API/PaginationToken.php +++ b/plugin/zoom/lib/API/PaginationToken.php @@ -35,7 +35,6 @@ trait PaginationToken * Retrieves all items from the server, possibly generating several API calls. * * @param string $arrayPropertyName item array property name - * @param Client $client * @param string $relativePath relative path to pass to Client::send * @param array $parameters parameter array to pass to Client::send * @@ -43,7 +42,7 @@ trait PaginationToken * * @return array united list of items */ - protected static function loadItems($arrayPropertyName, $client, $relativePath, $parameters = []) + protected static function loadItems($arrayPropertyName, $relativePath, $parameters = []) { $items = []; $pageSize = 300; @@ -51,7 +50,7 @@ trait PaginationToken $nextPageToken = ''; do { $response = static::fromJson( - $client->send( + Client::getInstance()->send( 'GET', $relativePath, array_merge(['page_size' => $pageSize, 'next_page_token' => $nextPageToken], $parameters) diff --git a/plugin/zoom/lib/API/ParticipantList.php b/plugin/zoom/lib/API/ParticipantList.php index 10b9107401..cad3d26b23 100644 --- a/plugin/zoom/lib/API/ParticipantList.php +++ b/plugin/zoom/lib/API/ParticipantList.php @@ -31,18 +31,16 @@ class ParticipantList /** * Retrieves a meeting instance's participants. * - * @param Client $client * @param string $instanceUUID * * @throws Exception * * @return ParticipantListItem[] participants */ - public static function loadInstanceParticipants($client, $instanceUUID) + public static function loadInstanceParticipants($instanceUUID) { return static::loadItems( 'participants', - $client, 'past_meetings/'.htmlentities($instanceUUID).'/participants' ); } diff --git a/plugin/zoom/lib/API/PastMeeting.php b/plugin/zoom/lib/API/PastMeeting.php index 6163169a52..0dccf7ce3c 100644 --- a/plugin/zoom/lib/API/PastMeeting.php +++ b/plugin/zoom/lib/API/PastMeeting.php @@ -50,29 +50,26 @@ class PastMeeting extends Meeting /** * Retrieves a past meeting instance from its identifier. * - * @param Client $client * @param string $uuid * * @throws Exception * * @return PastMeeting the past meeting */ - public static function fromUUID($client, $uuid) + public static function fromUUID($uuid) { - return static::fromJson($client->send('GET', 'past_meetings/'.htmlentities($uuid))); + return static::fromJson(Client::getInstance()->send('GET', 'past_meetings/'.htmlentities($uuid))); } /** * Retrieves information on participants from a past meeting instance. * - * @param Client $client - * * @throws Exception * * @return ParticipantListItem[] participants */ - public function getParticipants($client) + public function getParticipants() { - return ParticipantList::loadInstanceParticipants($client, $this->uuid); + return ParticipantList::loadInstanceParticipants($this->uuid); } } diff --git a/plugin/zoom/lib/API/RecordingFile.php b/plugin/zoom/lib/API/RecordingFile.php index f725da8518..d853d632a0 100644 --- a/plugin/zoom/lib/API/RecordingFile.php +++ b/plugin/zoom/lib/API/RecordingFile.php @@ -95,13 +95,11 @@ class RecordingFile /** * Deletes the file. * - * @param Client $client - * * @throws Exception */ - public function delete($client) + public function delete() { - $client->send( + Client::getInstance()->send( 'DELETE', "/meetings/$this->meeting_id/recordings/$this->id", ['action' => 'delete'] diff --git a/plugin/zoom/lib/API/RecordingList.php b/plugin/zoom/lib/API/RecordingList.php index ec82442151..5bccf953ed 100644 --- a/plugin/zoom/lib/API/RecordingList.php +++ b/plugin/zoom/lib/API/RecordingList.php @@ -34,7 +34,6 @@ class RecordingList /** * Retrieves all recordings from a period of time. * - * @param Client $client * @param DateTime $startDate first day of the period * @param DateTime $endDate last day of the period * @@ -42,11 +41,10 @@ class RecordingList * * @return RecordingMeeting[] all recordings from that period */ - public static function loadPeriodRecordings($client, $startDate, $endDate) + public static function loadPeriodRecordings($startDate, $endDate) { return static::loadItems( 'meetings', - $client, 'users/me/recordings', [ 'from' => $startDate->format('Y-m-d'), diff --git a/plugin/zoom/lib/API/RecordingMeeting.php b/plugin/zoom/lib/API/RecordingMeeting.php index 155d4e06e5..e2742edc60 100644 --- a/plugin/zoom/lib/API/RecordingMeeting.php +++ b/plugin/zoom/lib/API/RecordingMeeting.php @@ -69,13 +69,15 @@ class RecordingMeeting /** * Deletes the recording on the server. * - * @param Client $client - * * @throws Exception */ - public function delete($client) + public function delete() { - $client->send('DELETE', 'meetings/'.htmlentities($this->uuid).'/recordings', ['action' => 'delete']); + Client::getInstance()->send( + 'DELETE', + 'meetings/'.htmlentities($this->uuid).'/recordings', + ['action' => 'delete'] + ); } /** diff --git a/plugin/zoom/lib/CourseMeeting.php b/plugin/zoom/lib/CourseMeeting.php deleted file mode 100644 index b454e4db4e..0000000000 --- a/plugin/zoom/lib/CourseMeeting.php +++ /dev/null @@ -1,69 +0,0 @@ -setCourseAndSessionId($courseId, $sessionId); - $instance->initializeDisplayableProperties(); - - return $instance; - } - - /** - * {@inheritdoc} - * - * @throws Exception - */ - public function initializeExtraProperties() - { - parent::initializeExtraProperties(); - $this->decodeAndRemoveTag(); - $this->initializeDisplayableProperties(); - } - - /** - * {@inheritdoc} - * - * Creates a tagged meeting - * - * @return CourseMeetingInfoGet - */ - public function create($client) - { - $new = new CourseMeetingInfoGet(); - - $this->tagAgenda(); - static::recursivelyCopyObjectProperties(parent::create($client), $new); - $this->untagAgenda(); - - return $new; - } -} diff --git a/plugin/zoom/lib/CourseMeetingInfoGet.php b/plugin/zoom/lib/CourseMeetingInfoGet.php deleted file mode 100644 index 3b263d4253..0000000000 --- a/plugin/zoom/lib/CourseMeetingInfoGet.php +++ /dev/null @@ -1,57 +0,0 @@ -decodeAndRemoveTag(); - $this->initializeDisplayableProperties(); - } - - /** - * Updates the meeting on server, tagging it so to remember its course and session. - * - * @param API\Client $client - * - * @throws Exception - */ - public function update($client) - { - $this->tagAgenda(); - parent::update($client); - $this->untagAgenda(); - } - - /** - * Retrieves meeting registrants. - * - * @param API\Client $client - * - * @throws Exception - * - * @return UserMeetingRegistrantListItem[] - */ - public function getUserRegistrants($client) - { - return UserMeetingRegistrantList::loadUserMeetingRegistrants($client, $this->id); - } -} diff --git a/plugin/zoom/lib/CourseMeetingList.php b/plugin/zoom/lib/CourseMeetingList.php deleted file mode 100644 index af9a7e56ca..0000000000 --- a/plugin/zoom/lib/CourseMeetingList.php +++ /dev/null @@ -1,26 +0,0 @@ -decodeAndRemoveTag(); - $this->initializeDisplayableProperties(); - } -} diff --git a/plugin/zoom/lib/CourseMeetingTrait.php b/plugin/zoom/lib/CourseMeetingTrait.php deleted file mode 100644 index 1e189b85b4..0000000000 --- a/plugin/zoom/lib/CourseMeetingTrait.php +++ /dev/null @@ -1,142 +0,0 @@ -course = Database::getManager()->getRepository('ChamiloCoreBundle:Course')->find($this->courseId); - } - - public function loadSession() - { - $this->session = $this->sessionId - ? Database::getManager()->getRepository('ChamiloCoreBundle:Session')->find($this->sessionId) - : null; - } - - public function setCourseAndSessionId($courseId, $sessionId) - { - $this->courseId = $courseId; - $this->sessionId = $sessionId; - } - - public function tagAgenda() - { - $this->agenda = $this->getUntaggedAgenda().$this->getTag(); - } - - public function untagAgenda() - { - $this->agenda = $this->getUntaggedAgenda(); - } - - /** - * Builds the list of users that can register into this meeting. - * - * @return User[] the list of users - */ - public function getCourseAndSessionUsers() - { - if ($this->sessionId && is_null($this->session)) { - $this->loadSession(); - } - - if (is_null($this->course)) { - $this->loadCourse(); - } - - $users = []; - - if (is_null($this->session)) { - $users = Database::getManager()->getRepository( - 'ChamiloCoreBundle:Course' - )->getSubscribedUsers($this->course)->getQuery()->getResult(); - } else { - $subscriptions = $this->session->getUserCourseSubscriptionsByStatus($this->course, Session::STUDENT); - if ($subscriptions) { - /** @var SessionRelCourseRelUser $sessionCourseUser */ - foreach ($subscriptions as $sessionCourseUser) { - $users[$sessionCourseUser->getUser()->getUserId()] = $sessionCourseUser->getUser(); - } - } - } - - return $users; - } - - /** - * @param int $courseId - * @param int $sessionId - * - * @return bool whether both values match this CourseMeeting - */ - public function matches($courseId, $sessionId) - { - return $courseId == $this->courseId && $sessionId == $this->sessionId; - } - - protected function decodeAndRemoveTag() - { - $this->isTaggedWithCourseId = preg_match(self::getTagPattern(), $this->agenda, $matches); - if ($this->isTaggedWithCourseId) { - $this->setCourseAndSessionId($matches['courseId'], $matches['sessionId']); - $this->untagAgenda(); - } else { - $this->setCourseAndSessionId(0, 0); - } - $this->course = null; - $this->session = null; - } - - protected function getUntaggedAgenda() - { - return str_replace($this->getTag(), '', $this->agenda); - } - - /** - * @return string a tag to append to a meeting agenda so to link it to a (course, session) tuple - */ - private function getTag() - { - return "\n(course $this->courseId, session $this->sessionId)"; - } - - private static function getTagPattern() - { - return '/course (?P\d+), session (?P\d+)/m'; - } -} diff --git a/plugin/zoom/lib/DisplayableMeetingTrait.php b/plugin/zoom/lib/DisplayableMeetingTrait.php deleted file mode 100644 index 5761ed4e1b..0000000000 --- a/plugin/zoom/lib/DisplayableMeetingTrait.php +++ /dev/null @@ -1,74 +0,0 @@ -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->type]; - if (property_exists($this, 'status')) { - $this->statusName = [ - 'waiting' => get_lang('Waiting'), - 'started' => get_lang('Started'), - 'finished' => get_lang('Finished'), - ][$this->status]; - } - $this->startDateTime = null; - $this->formattedStartTime = ''; - $this->durationInterval = null; - $this->formattedDuration = ''; - if (!empty($this->start_time)) { - $this->startDateTime = new DateTime($this->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->duration)) { - $now = new DateTime(); - $later = new DateTime(); - $later->add(new DateInterval('PT'.$this->duration.'M')); - $this->durationInterval = $later->diff($now); - $this->formattedDuration = $this->durationInterval->format(get_lang('DurationFormat')); - } - } -} diff --git a/plugin/zoom/lib/File.php b/plugin/zoom/lib/File.php deleted file mode 100644 index f3b9d64813..0000000000 --- a/plugin/zoom/lib/File.php +++ /dev/null @@ -1,28 +0,0 @@ -formattedFileSize = format_file_size($this->file_size); - } -} diff --git a/plugin/zoom/lib/MeetingEntityRepository.php b/plugin/zoom/lib/MeetingEntityRepository.php new file mode 100644 index 0000000000..ccc9b62162 --- /dev/null +++ b/plugin/zoom/lib/MeetingEntityRepository.php @@ -0,0 +1,144 @@ +findAll() as $candidate) { + if ($candidate->startDateTime >= $startDate + && $candidate->startDateTime <= $endDate + ) { + $matching[] = $candidate; + } + } + + return $matching; + } + + /** + * @return ArrayCollection|Collection|MeetingEntity[] + */ + public function globalMeetings() + { + return $this->matching( + Criteria::create()->where( + Criteria::expr()->andX( + Criteria::expr()->eq('course', null), + Criteria::expr()->eq('user', null) + ) + ) + ); + } + + /** + * @return ArrayCollection|Collection|MeetingEntity[] + */ + public function unfinishedGlobalMeetings() + { + return $this->globalMeetings()->filter( + function ($meeting) { + return 'finished' !== $meeting->getMeetingInfoGet()->status; + } + ); + } + + /** + * Returns either a user's meetings or all user meetings. + * + * @param User|null $user + * + * @return ArrayCollection|Collection|MeetingEntity[] + */ + public function userMeetings($user = null) + { + return $this->matching( + Criteria::create()->where( + Criteria::expr()->andX( + Criteria::expr()->eq('course', null), + is_null($user) + ? Criteria::expr()->neq('user', null) + : Criteria::expr()->eq('user', $user) + ) + ) + ); + } + + /** + * @param User|null $user + * + * @return ArrayCollection|Collection|MeetingEntity[] + */ + public function unfinishedUserMeetings($user = null) + { + return $this->userMeetings($user)->filter( + function ($meeting) { + return 'finished' !== $meeting->getMeetingInfoGet()->status; + } + ); + } + + /** + * @param DateTime $start + * @param DateTime $end + * @param User|null $user + * + * @return ArrayCollection|Collection|MeetingEntity[] + */ + public function periodUserMeetings($start, $end, $user = null) + { + return $this->userMeetings($user)->filter( + function ($meeting) use ($start, $end) { + return $meeting->startDateTime >= $start + && $meeting->startDateTime <= $end; + } + ); + } + + /** + * Returns either a course's meetings or all course meetings. + * + * @param Course|null $course + * @param Session|null $session + * + * @return ArrayCollection|Collection|MeetingEntity[] + */ + public function courseMeetings($course = null, $session = null) + { + return $this->matching( + Criteria::create()->where( + is_null($course) + ? Criteria::expr()->neq('course', null) + : Criteria::expr()->andX( + Criteria::expr()->eq('course', $course), + Criteria::expr()->eq('session', $session) + ) + ) + ); + } +} diff --git a/plugin/zoom/lib/Recording.php b/plugin/zoom/lib/Recording.php deleted file mode 100644 index 826953db3f..0000000000 --- a/plugin/zoom/lib/Recording.php +++ /dev/null @@ -1,65 +0,0 @@ -startDateTime = new DateTime($this->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->duration.'M')); - $this->durationInterval = $later->diff($now); - $this->formattedDuration = $this->durationInterval->format(get_lang('DurationFormat')); - } - - /** - * {@inheritdoc} - */ - public function itemClass($propertyName) - { - if ('recording_files' === $propertyName) { - return File::class; - } - - return parent::itemClass($propertyName); - } -} diff --git a/plugin/zoom/lib/RecordingEntityRepository.php b/plugin/zoom/lib/RecordingEntityRepository.php new file mode 100644 index 0000000000..3d6d86aaef --- /dev/null +++ b/plugin/zoom/lib/RecordingEntityRepository.php @@ -0,0 +1,69 @@ +findAll() as $candidate) { + if ($candidate->startDateTime >= $startDate + && $candidate->startDateTime <= $endDate + ) { + $matching[] = $candidate; + } + } + + return $matching; + } + + /** + * Returns a user's meeting recordings. + * + * @param User $user + * + * @return ArrayCollection|Collection|RecordingEntity[] + */ + public function userRecordings($user) + { + return $this->matching( + Criteria::create()->where( + Criteria::expr()->in( + 'meeting', + $this->getEntityManager()->getRepository(MeetingEntity::class)->userMeetings($user)->toArray() + ) + ) + ); + } + + /** + * @param DateTime $start + * @param DateTime $end + * @param User $user + * + * @return ArrayCollection|RecordingEntity[] + */ + public function getPeriodUserRecordings($start, $end, $user) + { + return $this->userRecordings($user)->filter( + function ($meeting) use ($start, $end) { + return $meeting->startDateTime >= $start + && $meeting->startDateTime <= $end; + } + ); + } +} diff --git a/plugin/zoom/lib/RecordingList.php b/plugin/zoom/lib/RecordingList.php deleted file mode 100644 index 63b10aaac7..0000000000 --- a/plugin/zoom/lib/RecordingList.php +++ /dev/null @@ -1,51 +0,0 @@ - $startDate->format('Y-m-d'), - 'to' => $endDate->format('Y-m-d'), - ] - ); - } -} diff --git a/plugin/zoom/lib/RegistrantEntityRepository.php b/plugin/zoom/lib/RegistrantEntityRepository.php new file mode 100644 index 0000000000..37f3e7d8bb --- /dev/null +++ b/plugin/zoom/lib/RegistrantEntityRepository.php @@ -0,0 +1,34 @@ +add(new DateInterval('P7D')); + $meetings = $this->getEntityManager()->getRepository(MeetingEntity::class)->periodMeetings($start, $end); + + return $this->findBy(['meeting' => $meetings, 'user' => $user]); + } +} diff --git a/plugin/zoom/lib/UserMeetingRegistrant.php b/plugin/zoom/lib/UserMeetingRegistrant.php deleted file mode 100644 index 5604623a1e..0000000000 --- a/plugin/zoom/lib/UserMeetingRegistrant.php +++ /dev/null @@ -1,51 +0,0 @@ -decodeAndRemoveTag(); - $this->computeFullName(); - } - - /** - * Creates a UserMeetingRegistrant instance from a user. - * - * @param User $user - * - * @throws Exception - * - * @return static - */ - public static function fromUser($user) - { - $instance = new static(); - $instance->email = $user->getEmail(); - $instance->first_name = $user->getFirstname(); - $instance->last_name = $user->getLastname(); - $instance->userId = $user->getId(); - $instance->user = $user; - $instance->computeFullName(); - - return $instance; - } -} diff --git a/plugin/zoom/lib/UserMeetingRegistrantList.php b/plugin/zoom/lib/UserMeetingRegistrantList.php deleted file mode 100644 index 4f164544c9..0000000000 --- a/plugin/zoom/lib/UserMeetingRegistrantList.php +++ /dev/null @@ -1,43 +0,0 @@ -decodeAndRemoveTag(); - $this->computeFullName(); - } -} diff --git a/plugin/zoom/lib/UserMeetingRegistrantTrait.php b/plugin/zoom/lib/UserMeetingRegistrantTrait.php deleted file mode 100644 index 09dcdc3d5a..0000000000 --- a/plugin/zoom/lib/UserMeetingRegistrantTrait.php +++ /dev/null @@ -1,89 +0,0 @@ -user = Database::getManager()->getRepository('ChamiloUserBundle:User')->find($this->userId); - } - - public function setUserId($userId) - { - $this->userId = $userId; - } - - public function tagEmail() - { - $this->email = str_replace('@', $this->getTag(), $this->getUntaggedEmail()); - } - - public function untagEmail() - { - $this->email = $this->getUntaggedEmail(); - } - - public function matches($userId) - { - return $userId == $this->userId; - } - - public function computeFullName() - { - $this->fullName = api_get_person_name($this->first_name, $this->last_name); - } - - protected function decodeAndRemoveTag() - { - $this->isTaggedWithUserId = preg_match(self::getTagPattern(), $this->email, $matches); - if ($this->isTaggedWithUserId) { - $this->setUserId($matches['userId']); - $this->untagEmail(); - } else { - $this->setUserId(0); - } - $this->user = null; - } - - protected function getUntaggedEmail() - { - return str_replace($this->getTag(), '@', $this->email); - } - - /** - * @return string a tag to append to a registrant comments so to link it to a user - */ - private function getTag() - { - return "+user_$this->userId@"; - } - - private static function getTagPattern() - { - return '/\+user_(?P\d+)@/m'; - } -} diff --git a/plugin/zoom/lib/zoom_plugin.class.php b/plugin/zoom/lib/zoom_plugin.class.php index dd2db00fbd..e84ecae855 100755 --- a/plugin/zoom/lib/zoom_plugin.class.php +++ b/plugin/zoom/lib/zoom_plugin.class.php @@ -1,22 +1,25 @@ 'boolean', 'apiKey' => 'text', 'apiSecret' => 'text', + 'verificationToken' => 'text', 'enableParticipantRegistration' => 'boolean', 'enableCloudRecording' => 'boolean', 'enableGlobalConference' => 'boolean', @@ -52,6 +66,8 @@ class ZoomPlugin extends Plugin ); $this->isAdminPlugin = true; + + $this->jwtClient = new JWTClient($this->get('apiKey'), $this->get('apiSecret')); } /** @@ -67,69 +83,170 @@ class ZoomPlugin extends Plugin } /** - * Creates this plugin's related data and data structure in the internal database. + * @return bool + */ + public static function currentUserCanJoinGlobalMeeting() + { + return 'true' === api_get_plugin_setting('zoom', 'enableGlobalConference') && api_user_is_login(); + } + + /** + * @return bool + */ + public static function currentUserCanCreateUserMeeting() + { + $user = api_get_user_entity(api_get_user_id()); + + return !is_null($user) + && 'true' === api_get_plugin_setting('zoom', 'enableGlobalConferencePerUser') + && in_array( + (api_is_platform_admin() ? PLATFORM_ADMIN : $user->getStatus()), + (array) api_get_plugin_setting('zoom', 'globalConferenceAllowRoles') + ) + ; + } + + /** + * @return array [ $title => $link ] + */ + public static function meetingsToWhichCurrentUserIsRegisteredComingSoon() + { + $items = []; + $linkTemplate = api_get_path(WEB_PLUGIN_PATH).'zoom/join_meeting.php?meetingId=%s'; + foreach (self::getRegistrantRepository()->meetingsComingSoonRegistrationsForUser( + api_get_user_entity(api_get_user_id()) + ) as $registrant) { + $meeting = $registrant->getMeeting(); + $items[ + sprintf( + get_lang('DateMeetingTitle'), + $meeting->formattedStartTime, + $meeting->getMeetingInfoGet()->topic + ) + ] = sprintf($linkTemplate, $meeting->getId()); + } + + return $items; + } + + /** + * @return array + */ + public static function getProfileBlockItems() + { + $elements = self::meetingsToWhichCurrentUserIsRegisteredComingSoon(); + if (ZoomPlugin::currentUserCanJoinGlobalMeeting()) { + $elements[get_lang('JoinGlobalVideoConference')] = api_get_path(WEB_PLUGIN_PATH).'zoom/global.php'; + } + if (ZoomPlugin::currentUserCanCreateUserMeeting()) { + $elements[get_lang('CreateUserVideoConference')] = api_get_path(WEB_PLUGIN_PATH).'zoom/user.php'; + } + $items = []; + foreach ($elements as $title => $link) { + $items[] = [ + 'class' => 'video-conference', + 'icon' => Display::return_icon( + 'zoom.png', + get_lang('VideoConference') + ), + 'link' => $link, + 'title' => $title, + ]; + } + + return $items; + } + + /** + * @return MeetingEntityRepository|EntityRepository + */ + public static function getMeetingRepository() + { + return Database::getManager()->getRepository(MeetingEntity::class); + } + + /** + * @return RecordingEntityRepository|EntityRepository + */ + public static function getRecordingRepository() + { + return Database::getManager()->getRepository(RecordingEntity::class); + } + + /** + * @return RegistrantEntityRepository|EntityRepository + */ + public static function getRegistrantRepository() + { + return Database::getManager()->getRepository(RegistrantEntity::class); + } + + /** + * Creates this plugin's related tables in the internal database. + * Installs course fields in all courses. + * + * @throws ToolsException */ public function install() { + (new SchemaTool(Database::getManager()))->createSchema([ + Database::getManager()->getClassMetadata(MeetingEntity::class), + Database::getManager()->getClassMetadata(RecordingEntity::class), + Database::getManager()->getClassMetadata(RegistrantEntity::class), + ]); $this->install_course_fields_in_all_courses(); } /** - * Drops this plugins' related data from the internal database. + * Drops this plugins' related tables from the internal database. + * Uninstalls course fields in all courses(). */ public function uninstall() { + (new SchemaTool(Database::getManager()))->dropSchema([ + Database::getManager()->getClassMetadata(MeetingEntity::class), + Database::getManager()->getClassMetadata(RecordingEntity::class), + Database::getManager()->getClassMetadata(RegistrantEntity::class), + ]); $this->uninstall_course_fields_in_all_courses(); } /** * Generates the search form to include in the meeting list administration page. - * The form has DatePickers 'start' and 'end' and a Radio 'type'. + * The form has DatePickers 'start' and 'end' and Checkbox 'reloadRecordingLists'. * - * @return FormValidator + * @return FormValidator the form */ public function getAdminSearchForm() { $form = new FormValidator('search'); - $startDatePicker = $form->addDatePicker('start', get_lang('StartDate')); - $endDatePicker = $form->addDatePicker('end', get_lang('EndDate')); - $typeSelect = $form->addRadio( - 'type', - get_lang('Type'), - [ - CourseMeetingList::TYPE_SCHEDULED => get_lang('ScheduledMeetings'), - CourseMeetingList::TYPE_LIVE => get_lang('LiveMeetings'), - CourseMeetingList::TYPE_UPCOMING => get_lang('UpcomingMeetings'), - ] - ); + $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($startDatePicker->getValue()); + $start = new DateTime($form->getSubmitValue('start')); } catch (Exception $exception) { $start = new DateTime(); $start->sub($oneMonth); } try { - $end = new DateTime($endDatePicker->getValue()); + $end = new DateTime($form->getSubmitValue('end')); } catch (Exception $exception) { $end = new DateTime(); $end->add($oneMonth); } - $type = $typeSelect->getValue(); } else { $start = new DateTime(); $start->sub($oneMonth); $end = new DateTime(); $end->add($oneMonth); - $type = CourseMeetingList::TYPE_SCHEDULED; } try { $form->setDefaults([ 'start' => $start->format('Y-m-d'), 'end' => $end->format('Y-m-d'), - 'type' => $type, ]); } catch (Exception $exception) { error_log(join(':', [__FILE__, __LINE__, $exception])); @@ -141,35 +258,41 @@ class ZoomPlugin extends Plugin /** * Generates a meeting edit form and updates the meeting on validation. * - * @param CourseMeetingInfoGet $meeting the meeting + * @param MeetingEntity $meetingEntity the meeting + * + * @throws Exception * * @return FormValidator */ - public function getEditMeetingForm(&$meeting) + public function getEditMeetingForm($meetingEntity) { + $meetingInfoGet = $meetingEntity->getMeetingInfoGet(); $form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']); - $withTimeAndDuration = $meeting::TYPE_SCHEDULED === $meeting->type - || $meeting::TYPE_RECURRING_WITH_FIXED_TIME === $meeting->type; - if ($withTimeAndDuration) { - $startTimeDatePicker = $form->addDateTimePicker('start_time', get_lang('StartTime')); + if ($meetingEntity->requiresDateAndDuration()) { + $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime')); $form->setRequired($startTimeDatePicker); $durationNumeric = $form->addNumeric('duration', get_lang('DurationInMinutes')); $form->setRequired($durationNumeric); } - $topicText = $form->addText('topic', get_lang('Topic')); - $agendaTextArea = $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]); - // $passwordText = $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']); + $form->addText('topic', get_lang('Topic')); + $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]); + // $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']); $form->addButtonUpdate(get_lang('UpdateMeeting')); if ($form->validate()) { - if ($withTimeAndDuration) { - $meeting->start_time = $startTimeDatePicker->getValue(); - $meeting->timezone = date_default_timezone_get(); - $meeting->duration = $durationNumeric->getValue(); + if ($meetingEntity->requiresDateAndDuration()) { + $meetingInfoGet->start_time = (new DateTime($form->getSubmitValue('startTime')))->format( + DateTimeInterface::ISO8601 + ); + $meetingInfoGet->timezone = date_default_timezone_get(); + $meetingInfoGet->duration = (int) $form->getSubmitValue('duration'); } - $meeting->topic = $topicText->getValue(); - $meeting->agenda = $agendaTextArea->getValue(); + $meetingInfoGet->topic = $form->getSubmitValue('topic'); + $meetingInfoGet->agenda = $form->getSubmitValue('agenda'); try { - $this->updateMeeting($meeting); + $meetingInfoGet->update(); + $meetingEntity->setMeetingInfoGet($meetingInfoGet); + Database::getManager()->persist($meetingEntity); + Database::getManager()->flush(); Display::addFlash( Display::return_message(get_lang('MeetingUpdated'), 'confirm') ); @@ -178,15 +301,14 @@ class ZoomPlugin extends Plugin Display::return_message($exception->getMessage(), 'error') ); } - $meeting = $this->getMeeting($meeting->id); } $defaults = [ - 'topic' => $meeting->topic, - 'agenda' => $meeting->agenda, + 'topic' => $meetingInfoGet->topic, + 'agenda' => $meetingInfoGet->agenda, ]; - if ($withTimeAndDuration) { - $defaults['start_time'] = $meeting->startDateTime->format('c'); - $defaults['duration'] = $meeting->duration; + if ($meetingEntity->requiresDateAndDuration()) { + $defaults['startTime'] = $meetingEntity->startDateTime->format('Y-m-d H:i'); + $defaults['duration'] = $meetingInfoGet->duration; } $form->setDefaults($defaults); @@ -196,18 +318,21 @@ class ZoomPlugin extends Plugin /** * Generates a meeting delete form and deletes the meeting on validation. * - * @param CourseMeetingInfoGet $meeting - * @param string $returnURL where to redirect to on successful deletion + * @param MeetingEntity $meetingEntity + * @param string $returnURL where to redirect to on successful deletion + * + * @throws Exception * * @return FormValidator */ - public function getDeleteMeetingForm($meeting, $returnURL) + public function getDeleteMeetingForm($meetingEntity, $returnURL) { $form = new FormValidator('delete', 'post', $_SERVER['REQUEST_URI']); $form->addButtonDelete(get_lang('DeleteMeeting')); if ($form->validate()) { try { - $this->deleteMeeting($meeting); + $meetingEntity->getMeetingInfoGet()->delete(); + Database::getManager()->remove($meetingEntity); Display::addFlash( Display::return_message(get_lang('MeetingDeleted'), 'confirm') ); @@ -226,35 +351,26 @@ class ZoomPlugin extends Plugin * Generates a registrant list update form listing course and session users. * Updates the list on validation. * - * @param CourseMeetingInfoGet $meeting + * @param MeetingEntity $meetingEntity * - * @return array a list of two elements: - * FormValidator the form - * UserMeetingRegistrantListItem[] the up-to-date list of registrants + * @throws Exception + * + * @return FormValidator */ - public function getRegisterParticipantForm($meeting) + public function getRegisterParticipantForm($meetingEntity) { $form = new FormValidator('register', 'post', $_SERVER['REQUEST_URI']); $userIdSelect = $form->addSelect('userIds', get_lang('RegisteredUsers')); $userIdSelect->setMultiple(true); $form->addButtonSend(get_lang('UpdateRegisteredUserList')); - $users = $meeting->getCourseAndSessionUsers(); + $users = $meetingEntity->getRegistrableUsers(); foreach ($users as $user) { $userIdSelect->addOption(api_get_person_name($user->getFirstname(), $user->getLastname()), $user->getId()); } - try { - $registrants = $this->getRegistrants($meeting); - } catch (Exception $exception) { - Display::addFlash( - Display::return_message($exception->getMessage(), 'error') - ); - $registrants = []; - } - if ($form->validate()) { - $selectedUserIds = $userIdSelect->getValue(); + $selectedUserIds = $form->getSubmitValue('userIds'); $selectedUsers = []; foreach ($users as $user) { if (in_array($user->getId(), $selectedUserIds)) { @@ -262,7 +378,7 @@ class ZoomPlugin extends Plugin } } try { - $this->updateRegistrantList($meeting, $selectedUsers); + $this->updateRegistrantList($meetingEntity, $selectedUsers); Display::addFlash( Display::return_message(get_lang('RegisteredUserListWasUpdated'), 'confirm') ); @@ -271,58 +387,43 @@ class ZoomPlugin extends Plugin Display::return_message($exception->getMessage(), 'error') ); } - try { - $registrants = $this->getRegistrants($meeting); - } catch (Exception $exception) { - Display::addFlash( - Display::return_message($exception->getMessage(), 'error') - ); - $registrants = []; - } } $registeredUserIds = []; - foreach ($registrants as $registrant) { - $registeredUserIds[] = $registrant->userId; + foreach ($meetingEntity->getRegistrants() as $registrant) { + $registeredUserIds[] = $registrant->getUser()->getId(); } $userIdSelect->setSelected($registeredUserIds); - return [$form, $registrants]; + return $form; } /** * Generates a meeting recording files management form. * Takes action on validation. * - * @param CourseMeetingInfoGet $meeting + * @param MeetingEntity $meeting + * + * @throws Exception * - * @return array a list of two elements: - * FormValidator the form - * Recording[] the up-to-date list of recordings + * @return FormValidator */ public function getFileForm($meeting) { $form = new FormValidator('fileForm', 'post', $_SERVER['REQUEST_URI']); - try { - $recordings = $this->getMeetingRecordings($meeting); - } catch (Exception $exception) { - Display::addFlash( - Display::return_message($exception->getMessage(), 'error') - ); - $recordings = []; - } - if (!empty($recordings)) { + if (!$meeting->getRecordings()->isEmpty()) { $fileIdSelect = $form->addSelect('fileIds', get_lang('Files')); $fileIdSelect->setMultiple(true); - foreach ($recordings as &$recording) { + foreach ($meeting->getRecordings() as &$recording) { // $recording->instanceDetails = $plugin->getPastMeetingInstanceDetails($instance->uuid); $options = []; - foreach ($recording->recording_files as $file) { + foreach ($recording->getRecordingMeeting()->recording_files as $file) { $options[] = [ 'text' => sprintf( '%s.%s (%s)', $file->recording_type, $file->file_type, - $file->formattedFileSize + //$file->formattedFileSize + $file->file_size ), 'value' => $file->id, ]; @@ -332,29 +433,32 @@ class ZoomPlugin extends Plugin sprintf("%s (%s)", $recording->formattedStartTime, $recording->formattedDuration) ); } - $actionRadio = $form->addRadio( + $actions = []; + if ($meeting->isCourseMeeting()) { + $actions['CreateLinkInCourse'] = get_lang('CreateLinkInCourse'); + $actions['CopyToCourse'] = get_lang('CopyToCourse'); + } + $actions['DeleteFile'] = get_lang('DeleteFile'); + $form->addRadio( 'action', get_lang('Action'), - [ - 'CreateLinkInCourse' => get_lang('CreateLinkInCourse'), - 'CopyToCourse' => get_lang('CopyToCourse'), - 'DeleteFile' => get_lang('DeleteFile'), - ] + $actions ); $form->addButtonUpdate(get_lang('DoIt')); if ($form->validate()) { - foreach ($recordings as $recording) { - foreach ($recording->recording_files as $file) { - if (in_array($file->id, $fileIdSelect->getValue())) { + foreach ($meeting->getRecordings() as $recording) { + foreach ($recording->files as $file) { + if (in_array($file->id, $form->getSubmitValue('fileIds'))) { $name = sprintf( get_lang('XRecordingOfMeetingXFromXDurationXDotX'), $file->recording_type, - $meeting->id, + $meeting->getId(), $recording->formattedStartTime, $recording->formattedDuration, $file->file_type ); - if ('CreateLinkInCourse' === $actionRadio->getValue()) { + $action = $form->getSubmitValue('action'); + if ('CreateLinkInCourse' === $action && $meeting->isCourseMeeting()) { try { $this->createLinkToFileInCourse($meeting, $file, $name); Display::addFlash( @@ -365,7 +469,7 @@ class ZoomPlugin extends Plugin Display::return_message($exception->getMessage(), 'error') ); } - } elseif ('CopyToCourse' === $actionRadio->getValue()) { + } elseif ('CopyToCourse' === $action && $meeting->isCourseMeeting()) { try { $this->copyFileToCourse($meeting, $file, $name); Display::addFlash( @@ -376,9 +480,9 @@ class ZoomPlugin extends Plugin Display::return_message($exception->getMessage(), 'error') ); } - } elseif ('DeleteFile' === $actionRadio->getValue()) { + } elseif ('DeleteFile' === $action) { try { - $this->deleteFile($file); + $file->delete(); Display::addFlash( Display::return_message(get_lang('FileWasDeleted'), 'confirm') ); @@ -391,35 +495,29 @@ class ZoomPlugin extends Plugin } } } - try { - $recordings = $this->getMeetingRecordings($meeting); - } catch (Exception $exception) { - Display::addFlash( - Display::return_message($exception->getMessage(), 'error') - ); - $recordings = []; - } } } - return [$form, $recordings]; + return $form; } /** * Generates a form to fast and easily create and start an instant meeting. * On validation, create it then redirect to it and exit. * + * @param User $user + * @param Course $course + * @param Session $session + * * @return FormValidator */ - public function getCreateInstantMeetingForm() + public function getCreateInstantMeetingForm($user, $course, $session) { $form = new FormValidator('createInstantMeetingForm', 'post', '', '_blank'); $form->addButton('startButton', get_lang('StartInstantMeeting')); - if ($form->validate()) { try { - $newInstantMeeting = $this->createInstantMeeting(); - location($newInstantMeeting->start_url); + $this->startInstantMeeting(get_lang('InstantMeeting'), $user, $course, $session); } catch (Exception $exception) { Display::addFlash( Display::return_message($exception->getMessage(), 'error') @@ -432,84 +530,100 @@ class ZoomPlugin extends Plugin /** * Generates a form to schedule a meeting. - * On validation, creates it. + * On validation, creates it and redirects to its page. + * + * @param User|null $user + * @param Course|null $course + * @param Session|null $session * * @throws Exception * * @return FormValidator */ - public function getScheduleMeetingForm() + public function getScheduleMeetingForm($user, $course = null, $session = null) { $form = new FormValidator('scheduleMeetingForm'); - $startTimeDatePicker = $form->addDateTimePicker('start_time', get_lang('StartTime')); + $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime')); $form->setRequired($startTimeDatePicker); $durationNumeric = $form->addNumeric('duration', get_lang('DurationInMinutes')); $form->setRequired($durationNumeric); - $topicText = $form->addText('topic', get_lang('Topic'), true); - $agendaTextArea = $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]); + $form->addText('topic', get_lang('Topic'), true); + $form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]); // $passwordText = $form->addText('password', get_lang('Password'), false, ['maxlength' => '10']); - $registrationOptions = [ - 'RegisterAllCourseUsers' => get_lang('RegisterAllCourseUsers'), - ]; - $groups = GroupManager::get_groups(); - if (!empty($groups)) { - $registrationOptions['RegisterTheseGroupMembers'] = get_lang('RegisterTheseGroupMembers'); - } - $registrationOptions['RegisterNoUser'] = get_lang('RegisterNoUser'); - $userRegistrationRadio = $form->addRadio( - 'userRegistration', - get_lang('UserRegistration'), - $registrationOptions - ); - $groupOptions = []; - foreach ($groups as $group) { - $groupOptions[$group['id']] = $group['name']; - } - $groupIdsSelect = $form->addSelect( - 'groupIds', - 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') + if (!is_null($course)) { + $registrationOptions = [ + 'RegisterAllCourseUsers' => get_lang('RegisterAllCourseUsers'), + ]; + $groups = GroupManager::get_groups(); + if (!empty($groups)) { + $registrationOptions['RegisterTheseGroupMembers'] = get_lang('RegisterTheseGroupMembers'); + } + $registrationOptions['RegisterNoUser'] = get_lang('RegisterNoUser'); + $userRegistrationRadio = $form->addRadio( + 'userRegistration', + get_lang('UserRegistration'), + $registrationOptions + ); + $groupOptions = []; + foreach ($groups as $group) { + $groupOptions[$group['id']] = $group['name']; + } + $groupIdsSelect = $form->addSelect( + 'groupIds', + 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->setAttribute('onchange', $jsCode); + } } $form->addButtonCreate(get_lang('ScheduleTheMeeting')); - // meeting scheduling if ($form->validate()) { try { - $newMeeting = $this->createScheduledMeeting( - new DateTime($startTimeDatePicker->getValue()), - $durationNumeric->getValue(), - $topicText->getValue(), - $agendaTextArea->getValue(), - '' // $passwordText->getValue() + $newMeeting = $this->scheduleMeeting( + $user, + $course, + $session, + new DateTime($form->getSubmitValue('startTime')), + $form->getSubmitValue('duration'), + $form->getSubmitValue('topic'), + $form->getSubmitValue('agenda'), + '' ); Display::addFlash( Display::return_message(get_lang('NewMeetingCreated')) ); - if ('RegisterAllCourseUsers' == $userRegistrationRadio->getValue()) { - $this->addRegistrants($newMeeting, $newMeeting->getCourseAndSessionUsers()); - Display::addFlash( - Display::return_message(get_lang('AllCourseUsersWereRegistered')) - ); - } elseif ('RegisterTheseGroupMembers' == $userRegistrationRadio->getValue()) { - $userIds = []; - foreach ($groupIdsSelect->getValue() as $groupId) { - $userIds = array_unique(array_merge($userIds, GroupManager::get_users($groupId))); + if ($newMeeting->isCourseMeeting()) { + if ('RegisterAllCourseUsers' == $form->getSubmitValue('userRegistration')) { + $this->registerAllCourseUsers($newMeeting); + Display::addFlash( + Display::return_message(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(get_lang('GroupUsersWereRegistered')) + ); } - $users = Database::getManager()->getRepository( - 'ChamiloUserBundle:User' - )->matching(Criteria::create()->where(Criteria::expr()->in('id', $userIds)))->getValues(); - $this->addRegistrants($newMeeting, $users); + location('meeting_from_start.php?meetingId='.$newMeeting->getId()); + } elseif (!is_null($user)) { + location('meeting_from_user.php?meetingId='.$newMeeting->getId()); } - location('meeting_from_start.php?meetingId='.$newMeeting->id); } catch (Exception $exception) { Display::addFlash( Display::return_message($exception->getMessage(), 'error') @@ -519,7 +633,6 @@ class ZoomPlugin extends Plugin $form->setDefaults( [ 'duration' => 60, - 'topic' => api_get_course_info()['title'], 'userRegistration' => 'RegisterAllCourseUsers', ] ); @@ -529,38 +642,26 @@ class ZoomPlugin extends Plugin } /** - * Retrieves information about meetings having a start_time between two dates. - * - * @param string $type MeetingList::TYPE_LIVE, MeetingList::TYPE_SCHEDULED or MeetingList::TYPE_UPCOMING - * @param DateTime $startDate - * @param DateTime $endDate - * - * @throws Exception on API error + * @param MeetingEntity $meetingEntity * - * @return CourseMeetingListItem[] matching meetings + * @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 getPeriodMeetings($type, $startDate, $endDate) + public function userIsConferenceManager($meetingEntity) { - $matchingMeetings = []; - /** @var CourseMeetingListItem $meeting */ - foreach (CourseMeetingList::loadMeetings($this->jwtClient(), $type) as $meeting) { - if (property_exists($meeting, 'start_time')) { - if ($startDate <= $meeting->startDateTime && $meeting->startDateTime <= $endDate) { - $meeting->loadCourse(); - $meeting->loadSession(); - $matchingMeetings[] = $meeting; - } - } - } - - return $matchingMeetings; + return api_is_coach() + || api_is_platform_admin() + || $meetingEntity->isCourseMeeting() && api_get_course_id() && api_is_course_admin() + || $meetingEntity->isUserMeeting() && $meetingEntity->getUser()->getId() == api_get_user_id(); } /** + * @param Course $course + * * @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() + public function userIsCourseConferenceManager($course) { return api_is_coach() || api_is_platform_admin() @@ -568,272 +669,414 @@ class ZoomPlugin extends Plugin } /** - * Retrieves a meeting. + * Adds to the meeting course documents a link to a meeting instance recording file. * - * @param int $id the meeting numeric identifier + * @param MeetingEntity $meeting + * @param RecordingFile $file + * @param string $name * * @throws Exception - * - * @return CourseMeetingInfoGet */ - public function getMeeting($id) + public function createLinkToFileInCourse($meeting, $file, $name) { - return CourseMeetingInfoGet::fromId($this->jwtClient(), $id); + $course = $meeting->getCourse(); + if (is_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')); + } } /** - * Retrieves a past meeting instance details. + * Copies a recording file to a meeting's course. * - * @param string $instanceUUID + * @param MeetingEntity $meeting + * @param RecordingFile $file + * @param string $name * * @throws Exception - * - * @return PastMeeting */ - public function getPastMeetingInstanceDetails($instanceUUID) + public function copyFileToCourse($meeting, $file, $name) { - return PastMeeting::fromUUID($this->jwtClient(), $instanceUUID); + $course = $meeting->getCourse(); + if (is_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)); + } + $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, + 'type' => $file->file_type, + ], + '/', + api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document', + api_get_user_id(), + 0, + null, + 0, + '', + true, + false, + null, + $meeting->getSession()->getId(), + true + ); + fclose($tmpFile); + if (false === $newPath) { + throw new Exception('could not handle uploaded document'); + } } /** - * Retrieves all live meetings linked to current course and session. + * Return the current global meeting (create it if needed). * - * @throws Exception on API error + * @throws Exception * - * @return CourseMeetingListItem[] matching meetings + * @return string */ - public function getLiveMeetings() + public function getGlobalMeeting() { - return $this->getMeetings(CourseMeetingList::TYPE_LIVE); - } + foreach ($this->getMeetingRepository()->unfinishedGlobalMeetings() as $meeting) { + return $meeting; + } - /** - * Retrieves all scheduled meetings linked to current course and session. - * - * @throws Exception on API error - * - * @return CourseMeetingListItem[] matching meetings - */ - public function getScheduledMeetings() - { - return $this->getMeetings(CourseMeetingList::TYPE_SCHEDULED); + return $this->createGlobalMeeting(); } /** - * Retrieves all upcoming meetings linked to current course and session. + * 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 Exception on API error + * @param MeetingEntity $meeting * - * @return CourseMeetingListItem[] matching meetings + * @throws Exception + * @throws OptimisticLockException + * + * @return string|null */ - public function getUpcomingMeetings() + public function getStartOrJoinMeetingURL($meeting) { - return $this->getMeetings(CourseMeetingList::TYPE_UPCOMING); + if ('waiting' === $meeting->getMeetingInfoGet()->status) { + // 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 ($meeting->isGlobalMeeting() && $this->get('enableGlobalConference') + || $meeting->getUser() === api_get_user_entity(api_get_user_id())) { + return $meeting->getMeetingInfoGet()->start_url; + } + } elseif ('started' === $meeting->getMeetingInfoGet()->status) { + if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) { + // the participant must be registered + $participant = api_get_user_entity(api_get_user_id()); + $registrant = $meeting->getRegistrant($participant); + if (!is_null($registrant)) { + // the participant is registered + return $registrant->getCreatedRegistration()->join_url; + } + // the participant is not registered, he can join only the global meeting (automatic registration) + if ($meeting->isGlobalMeeting() && $this->get('enableGlobalConference')) { + return $this->registerUser($meeting, $participant)->getCreatedRegistration()->join_url; + } + } else { + // no registration possible, join anonymously + return $meeting->getMeetingInfoGet()->join_url; + } + } + + return null; } /** - * Creates an instant meeting and returns it. + * Update local recording list from remote Zoom server's version. + * Kept to implement a future administration button ("import existing data from zoom server"). * - * @throws Exception describing the error (message and code) + * @param DateTime $startDate + * @param DateTime $endDate * - * @return CourseMeetingInfoGet meeting + * @throws OptimisticLockException + * @throws Exception */ - public function createInstantMeeting() + public function reloadPeriodRecordings($startDate, $endDate) { - // default meeting topic is based on session name, course title and current date - $topic = ''; - $sessionName = api_get_session_name(); - if ($sessionName) { - $topic = $sessionName.', '; + foreach (RecordingList::loadPeriodRecordings($startDate, $endDate) as $recordingMeeting) { + $recordingEntity = $this->getRecordingRepository()->find($recordingMeeting->uuid); + if (is_null($recordingEntity)) { + $recordingEntity = new RecordingEntity(); + $meetingEntity = $this->getMeetingRepository()->find($recordingMeeting->id); + if (is_null($meetingEntity)) { + try { + $meetingInfoGet = MeetingInfoGet::fromId($recordingMeeting->id); + } catch (Exception $exception) { + $meetingInfoGet = null; // deleted meeting with recordings + } + if (!is_null($meetingInfoGet)) { + $meetingEntity = $this->createMeetingFromMeetingEntity( + (new MeetingEntity())->setMeetingInfoGet($meetingInfoGet) + ); + Database::getManager()->persist($meetingEntity); + } + } + if (!is_null($meetingEntity)) { + $recordingEntity->setMeeting($meetingEntity); + } + } + $recordingEntity->setRecordingMeeting($recordingMeeting); + Database::getManager()->persist($recordingEntity); } - $courseInfo = api_get_course_info(); - $topic .= $courseInfo['title'].', '.date('yy-m-d H:i'); - $meeting = CourseMeeting::fromCourseSessionTopicAndType( - api_get_course_int_id(), - api_get_session_id(), - $topic, - CourseMeeting::TYPE_INSTANT - ); - - return $this->createMeeting($meeting); + Database::getManager()->flush(); } /** - * Schedules a meeting and returns it. + * Creates a meeting on Zoom servers and stores it in the local database. * - * @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 + * @param MeetingEntity $meeting a new, unsaved meeting with at least a type and a topic * - * @throws Exception describing the error (message and code) + * @throws Exception * - * @return CourseMeetingInfoGet meeting + * @return MeetingEntity */ - public function createScheduledMeeting($startTime, $duration, $topic, $agenda = '', $password = '') + private function createMeetingFromMeetingEntity($meeting) { - $meeting = CourseMeeting::fromCourseSessionTopicAndType( - api_get_course_int_id(), - api_get_session_id(), - $topic, - CourseMeeting::TYPE_SCHEDULED - ); - $meeting->duration = $duration; - $meeting->start_time = $startTime->format(DateTimeInterface::ISO8601); - $meeting->agenda = $agenda; - $meeting->password = $password; - $meeting->settings->approval_type = $this->get('enableParticipantRegistration') - ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE - : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED; + $meeting->getMeetingInfoGet()->settings->auto_recording = $this->get('enableCloudRecording') + ? 'cloud' + : 'local'; + $meeting->getMeetingInfoGet()->settings->registrants_email_notification = false; + $meeting->setMeetingInfoGet($meeting->getMeetingInfoGet()->create()); + Database::getManager()->persist($meeting); + Database::getManager()->flush(); - return $this->createMeeting($meeting); + return $meeting; } /** - * Updates a meeting. - * - * @param CourseMeetingInfoGet $meeting the meeting with updated properties + * @throws Exception * - * @throws Exception on API error + * @return MeetingEntity */ - public function updateMeeting($meeting) + private function createGlobalMeeting() { - $meeting->update($this->jwtClient()); - } + $meetingInfoGet = MeetingInfoGet::fromTopicAndType( + get_lang('GlobalMeeting'), + MeetingInfoGet::TYPE_SCHEDULED + ); + $meetingInfoGet->start_time = (new DateTime())->format(DateTimeInterface::ISO8601); + $meetingInfoGet->duration = 60; + $meetingInfoGet->settings->approval_type = + ('true' === $this->get('enableParticipantRegistration')) + ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE + : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED; + // $meetingInfoGet->settings->host_video = true; + $meetingInfoGet->settings->participant_video = true; + $meetingInfoGet->settings->join_before_host = true; + $meetingInfoGet->settings->registrants_email_notification = false; - /** - * Deletes a meeting. - * - * @param CourseMeetingInfoGet $meeting - * - * @throws Exception on API error - */ - public function deleteMeeting($meeting) - { - $meeting->delete($this->jwtClient()); + return $this->createMeetingFromMeetingEntity((new MeetingEntity())->setMeetingInfoGet($meetingInfoGet)); } /** - * Retrieves all recordings from a period of time. + * Starts a new instant meeting and redirects to its start url. * - * @param DateTime $startDate start date - * @param DateTime $endDate end date + * @param string $topic + * @param User|null $user + * @param Course|null $course + * @param Session|null $session * * @throws Exception - * - * @return Recording[] all recordings */ - public function getRecordings($startDate, $endDate) + private function startInstantMeeting($topic, $user = null, $course = null, $session = null) { - return RecordingList::loadRecordings($this->jwtClient(), $startDate, $endDate); + $meeting = $this->createMeetingFromMeetingEntity( + (new MeetingEntity()) + ->setMeetingInfoGet(MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_INSTANT)) + ->setUser($user) + ->setCourse($course) + ->setSession($session) + ); + location($meeting->getMeetingInfoGet()->start_url); } /** - * Retrieves a meetings instances' recordings. + * Schedules a meeting and returns it. + * set $course, $session and $user to null in order to create a global meeting. * - * @param CourseMeetingInfoGet $meeting + * @param User|null $user the current user, for a course meeting or a user meeting + * @param Course|null $course the course, for a course meeting + * @param Session|null $session the session, for a course 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 Recording[] meeting instances' recordings + * @return MeetingEntity meeting */ - public function getMeetingRecordings($meeting) + private function scheduleMeeting($user, $course, $session, $startTime, $duration, $topic, $agenda, $password) { - $interval = new DateInterval('P1M'); - $startDate = clone $meeting->startDateTime; - $startDate->sub($interval); - $endDate = clone $meeting->startDateTime; - $endDate->add($interval); - $recordings = []; - foreach ($this->getRecordings($startDate, $endDate) as $recording) { - if ($recording->id == $meeting->id) { - $recordings[] = $recording; - } - } + $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED); + $meetingInfoGet->duration = $duration; + $meetingInfoGet->start_time = $startTime->format(DateTimeInterface::ISO8601); + $meetingInfoGet->agenda = $agenda; + $meetingInfoGet->password = $password; + $meetingInfoGet->settings->approval_type = $this->get('enableParticipantRegistration') + ? MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE + : MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED; - return $recordings; + return $this->createMeetingFromMeetingEntity( + (new MeetingEntity()) + ->setMeetingInfoGet($meetingInfoGet) + ->setUser($user) + ->setCourse($course) + ->setSession($session) + ); } /** - * Retrieves a meeting instance's participants. - * - * @param string $instanceUUID the meeting instance UUID + * @param MeetingEntity $meetingEntity + * @param User $user + * @param bool $andFlush * + * @throws OptimisticLockException * @throws Exception * - * @return ParticipantListItem[] + * @return RegistrantEntity */ - public function getParticipants($instanceUUID) + private function registerUser($meetingEntity, $user, $andFlush = true) { - return ParticipantList::loadInstanceParticipants($this->jwtClient(), $instanceUUID); + if (empty($user->getEmail())) { + throw new Exception('CannotRegisterWithoutEmailAddress'); + } + $meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName( + $user->getEmail(), + $user->getFirstname(), + $user->getLastname() + ); + $registrantEntity = (new RegistrantEntity()) + ->setMeeting($meetingEntity) + ->setUser($user) + ->setMeetingRegistrant($meetingRegistrant) + ->setCreatedRegistration($meetingEntity->getMeetingInfoGet()->addRegistrant($meetingRegistrant)); + Database::getManager()->persist($registrantEntity); + if ($andFlush) { + Database::getManager()->flush($registrantEntity); + } + + return $registrantEntity; } /** - * Retrieves a meeting's registrants. + * Register users to a meeting. * - * @param CourseMeetingInfoGet $meeting + * @param MeetingEntity $meetingEntity + * @param User[] $users * - * @throws Exception + * @throws OptimisticLockException * - * @return UserMeetingRegistrantListItem[] the meeting registrants + * @return User[] failed registrations [ user id => errorMessage ] */ - public function getRegistrants($meeting) + private function registerUsers($meetingEntity, $users) { - return $meeting->getUserRegistrants($this->jwtClient()); + $failedUsers = []; + foreach ($users as $user) { + try { + $this->registerUser($meetingEntity, $user, false); + } catch (Exception $exception) { + $failedUsers[$user->getId()] = $exception->getMessage(); + } + } + Database::getManager()->flush(); + + return $failedUsers; } /** - * Registers users to a meeting. - * - * @param CourseMeetingInfoGet $meeting - * @param \Chamilo\UserBundle\Entity\User[] $users + * Registers all the course users to a course meeting. * - * @throws Exception + * @param MeetingEntity $meetingEntity * - * @return CreatedRegistration[] the created registrations + * @throws OptimisticLockException */ - public function addRegistrants($meeting, $users) + private function registerAllCourseUsers($meetingEntity) { - $createdRegistrations = []; - foreach ($users as $user) { - $registrant = UserMeetingRegistrant::fromUser($user); - $registrant->tagEmail(); - $createdRegistrations[] = $meeting->addRegistrant($this->jwtClient(), $registrant); - } - - return $createdRegistrations; + $this->registerUsers($meetingEntity, $meetingEntity->getRegistrableUsers()); } /** * Removes registrants from a meeting. * - * @param CourseMeetingInfoGet $meeting - * @param UserMeetingRegistrant[] $registrants + * @param MeetingEntity $meetingEntity + * @param RegistrantEntity[] $registrants * * @throws Exception */ - public function removeRegistrants($meeting, $registrants) + private function unregister($meetingEntity, $registrants) { - $meeting->removeRegistrants($this->jwtClient(), $registrants); + $meetingRegistrants = []; + foreach ($registrants as $registrant) { + $meetingRegistrants[] = $registrant->getMeetingRegistrant(); + } + $meetingEntity->getMeetingInfoGet()->removeRegistrants($meetingRegistrants); + foreach ($registrants as $registrant) { + Database::getManager()->remove($registrant); + } + Database::getManager()->flush(); } /** * Updates meeting registrants list. Adds the missing registrants and removes the extra. * - * @param CourseMeetingInfoGet $meeting - * @param \Chamilo\UserBundle\Entity\User[] $users list of users to be registred + * @param MeetingEntity $meetingEntity + * @param User[] $users list of users to be registered * * @throws Exception */ - public function updateRegistrantList($meeting, $users) + private function updateRegistrantList($meetingEntity, $users) { - $registrants = $this->getRegistrants($meeting); $usersToAdd = []; foreach ($users as $user) { $found = false; - foreach ($registrants as $registrant) { - if ($registrant->matches($user->getId())) { + foreach ($meetingEntity->getRegistrants() as $registrant) { + if ($registrant->getUser() === $user) { $found = true; break; } @@ -843,10 +1086,10 @@ class ZoomPlugin extends Plugin } } $registrantsToRemove = []; - foreach ($registrants as $registrant) { + foreach ($meetingEntity->getRegistrants() as $registrant) { $found = false; foreach ($users as $user) { - if ($registrant->matches($user->getId())) { + if ($registrant->getUser() === $user) { $found = true; break; } @@ -855,176 +1098,7 @@ class ZoomPlugin extends Plugin $registrantsToRemove[] = $registrant; } } - $this->addRegistrants($meeting, $usersToAdd); - $this->removeRegistrants($meeting, $registrantsToRemove); - } - - /** - * Adds to the meeting course documents a link to a meeting instance recording file. - * - * @param CourseMeetingInfoGet $meeting - * @param File $file - * @param string $name - * - * @throws Exception - */ - public function createLinkToFileInCourse($meeting, $file, $name) - { - $courseInfo = api_get_course_info_by_id($meeting->courseId); - 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 CourseMeetingInfoGet $meeting - * @param File $file - * @param string $name - * - * @throws Exception - */ - public function copyFileToCourse($meeting, $file, $name) - { - $courseInfo = api_get_course_info_by_id($meeting->courseId); - 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)); - } - $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, - 'type' => $file->file_type, - ], - '/', - api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document', - api_get_user_id(), - 0, - null, - 0, - '', - true, - false, - null, - $meeting->sessionId, - true - ); - fclose($tmpFile); - if (false === $newPath) { - throw new Exception('could not handle uploaded document'); - } - } - - /** - * Deletes a meeting instance's recordings. - * - * @param Recording $recording - * - * @throws Exception - */ - public function deleteRecordings($recording) - { - $recording->delete($this->jwtClient()); - } - - /** - * Deletes a meeting instance recording file. - * - * @param File $file - * - * @throws Exception - */ - public function deleteFile($file) - { - $file->delete($this->jwtClient()); - } - - /** - * Caches and returns the JWT client instance, initialized with plugin settings. - * - * @return JWTClient object that provides means of communications with the Zoom servers - */ - protected function jwtClient() - { - static $jwtClient = null; - if (is_null($jwtClient)) { - $jwtClient = new JWTClient($this->get('apiKey'), $this->get('apiSecret')); - } - - return $jwtClient; - } - - /** - * Retrieves all meetings of a specific type and linked to current course and session. - * - * @param string $type MeetingList::TYPE_LIVE, MeetingList::TYPE_SCHEDULED or MeetingList::TYPE_UPCOMING - * - * @throws Exception on API error - * - * @return CourseMeetingListItem[] matching meetings - */ - private function getMeetings($type) - { - $matchingMeetings = []; - $courseId = api_get_course_int_id(); - $sessionId = api_get_session_id(); - /** @var CourseMeetingListItem $candidateMeeting */ - foreach (CourseMeetingList::loadMeetings($this->jwtClient(), $type) as $candidateMeeting) { - if ($candidateMeeting->matches($courseId, $sessionId)) { - $matchingMeetings[] = $candidateMeeting; - } - } - - return $matchingMeetings; - } - - /** - * Creates a meeting on the server and returns it. - * - * @param CourseMeeting $meeting a meeting with at least a type and a topic - * - * @throws Exception describing the error (message and code) - * - * @return CourseMeetingInfoGet the new meeting - */ - private function createMeeting($meeting) - { - $meeting->settings->auto_recording = $this->get('enableCloudRecording') - ? 'cloud' - : 'local'; - $meeting->settings->registrants_email_notification = false; - - return $meeting->create($this->jwtClient()); + $this->registerUsers($meetingEntity, $usersToAdd); + $this->unregister($meetingEntity, $registrantsToRemove); } } diff --git a/plugin/zoom/meeting.php b/plugin/zoom/meeting.php index 999cb8f00a..3059797477 100755 --- a/plugin/zoom/meeting.php +++ b/plugin/zoom/meeting.php @@ -1,7 +1,7 @@ getMeeting($_REQUEST['meetingId']); +/** @var MeetingEntity $meeting */ +$meeting = $plugin->getMeetingRepository()->find($_REQUEST['meetingId']); +if (is_null($meeting)) { + throw new Exception('MeetingNotFound'); +} -$tpl = new Template($meeting->id); +$tpl = new Template($meeting->getId()); -if ($plugin->userIsConferenceManager()) { +if ($plugin->userIsConferenceManager($meeting)) { // user can edit, start and delete meeting $tpl->assign('isConferenceManager', true); $tpl->assign('editMeetingForm', $plugin->getEditMeetingForm($meeting)->returnForm()); $tpl->assign('deleteMeetingForm', $plugin->getDeleteMeetingForm($meeting, $returnURL)->returnForm()); - if ($plugin->get('enableParticipantRegistration') - && MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $meeting->settings->approval_type) { - list($registerParticipantForm, $registrants) = $plugin->getRegisterParticipantForm($meeting); - $tpl->assign('registerParticipantForm', $registerParticipantForm->returnForm()); - $tpl->assign('registrants', $registrants); // FIXME cache + if ($plugin->get('enableParticipantRegistration') && $meeting->requiresRegistration()) { + $tpl->assign('registerParticipantForm', $plugin->getRegisterParticipantForm($meeting)->returnForm()); + $tpl->assign('registrants', $meeting->getRegistrants()); } if ($plugin->get('enableCloudRecording') - && 'cloud' === $meeting->settings->auto_recording + && $meeting->hasCloudAutoRecordingEnabled() // && 'finished' === $meeting->status ) { - list($fileForm, $recordings) = $plugin->getFileForm($meeting); - $tpl->assign('fileForm', $fileForm->returnForm()); - $tpl->assign('recordings', $recordings); + $tpl->assign('fileForm', $plugin->getFileForm($meeting)->returnForm()); + $tpl->assign('recordings', $meeting->getRecordings()); } -} elseif (MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $meeting->settings->approval_type) { +} elseif ($meeting->requiresRegistration()) { $userId = api_get_user_id(); try { - foreach ($plugin->getRegistrants($meeting) as $registrant) { + foreach ($meeting->getRegistrants() as $registrant) { if ($registrant->userId == $userId) { $tpl->assign('currentUserJoinURL', $registrant->join_url); break; diff --git a/plugin/zoom/meeting_from_user.php b/plugin/zoom/meeting_from_user.php new file mode 100644 index 0000000000..a4ccd74bc6 --- /dev/null +++ b/plugin/zoom/meeting_from_user.php @@ -0,0 +1,13 @@ +userIsConferenceManager()) { +if ($plugin->userIsCourseConferenceManager(api_get_course_entity())) { // user can create a new meeting - $tpl->assign('createInstantMeetingForm', $plugin->getCreateInstantMeetingForm()->returnForm()); - $tpl->assign('scheduleMeetingForm', $plugin->getScheduleMeetingForm()->returnForm()); + $tpl->assign( + 'createInstantMeetingForm', + $plugin->getCreateInstantMeetingForm( + api_get_user_entity(api_get_user_id()), + api_get_course_entity(), + api_get_session_entity() + )->returnForm() + ); + $tpl->assign('scheduleMeetingForm', $plugin->getScheduleMeetingForm( + api_get_user_entity(api_get_user_id()), + api_get_course_entity(), + api_get_session_entity() + )->returnForm()); } try { - $tpl->assign('scheduledMeetings', $plugin->getScheduledMeetings()); + $tpl->assign( + 'scheduledMeetings', + $plugin->getMeetingRepository()->courseMeetings(api_get_course_entity(), api_get_session_entity()) + ); } catch (Exception $exception) { Display::addFlash( Display::return_message('Could not retrieve scheduled meeting list: '.$exception->getMessage(), 'error') diff --git a/plugin/zoom/user.php b/plugin/zoom/user.php new file mode 100644 index 0000000000..10dddda3a6 --- /dev/null +++ b/plugin/zoom/user.php @@ -0,0 +1,32 @@ +getAdminSearchForm(); +$startDate = new DateTime($form->getElement('start')->getValue()); +$endDate = new DateTime($form->getElement('end')->getValue()); + +$tpl = new Template(); +$tpl->assign('meetings', $plugin->getMeetingRepository()->periodUserMeetings($startDate, $endDate, $user)); +if ($plugin->get('enableCloudRecording')) { + $tpl->assign( + 'recordings', + $plugin->getRecordingRepository()->getPeriodUserRecordings($startDate, $endDate, $user) + ); +} +$tpl->assign('search_form', $form->returnForm()); +$tpl->assign('schedule_form', $plugin->getScheduleMeetingForm($user)->returnForm()); +$tpl->assign('content', $tpl->fetch('zoom/view/admin.tpl')); +$tpl->display_one_col_template(); diff --git a/plugin/zoom/view/admin.tpl b/plugin/zoom/view/admin.tpl index 00745df5e0..df777442ba 100644 --- a/plugin/zoom/view/admin.tpl +++ b/plugin/zoom/view/admin.tpl @@ -4,6 +4,7 @@ {{ 'StartTime'|get_lang }} + {{ 'User'|get_lang }} {{ 'Course'|get_lang }} {{ 'Session'|get_lang }} {{ 'Topic'|get_lang }} @@ -17,13 +18,14 @@ {% for meeting in meetings %} {{ meeting.formattedStartTime }} - {{ meeting.course ? meeting.course.title : '-' }} - {{ meeting.session ? meeting.session.name : '-' }} - {{ meeting.topic }} + {{ meeting.user ? meeting.user : '-' }} + {{ meeting.course ? meeting.course : '-' }} + {{ meeting.session ? meeting.session : '-' }} + {{ meeting.meetingInfoGet.topic }} {% if recordings %} {% for recording in recordings %} - {% if recording.id == meeting.id %} + {% if recording.recordingMeeting.id == meeting.id %}
{{ recording.formattedStartTime }} @@ -31,10 +33,10 @@
    - {% for file in recording.recording_files %} + {% for file in recording.recordingMeeting.recording_files %}
  • {{ file.recording_type }}.{{ file.file_type }} - ({{ file.formattedFileSize }}) + ({{ file.file_size }})
  • {% endfor %}
@@ -52,4 +54,6 @@ {% endfor %} - \ No newline at end of file + + +{{ schedule_form }} \ No newline at end of file diff --git a/plugin/zoom/view/meeting.tpl b/plugin/zoom/view/meeting.tpl index 806a4a5d86..8f4266347b 100644 --- a/plugin/zoom/view/meeting.tpl +++ b/plugin/zoom/view/meeting.tpl @@ -1,8 +1,17 @@ -

{{ 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 %}