Zoom meeting registrant and recording management - refs BT#17288

pull/3274/head
Sébastien Ducoulombier 6 years ago
parent 296db4e016
commit 189ac597da
  1. 6
      plugin/zoom/admin.php
  2. 3
      plugin/zoom/config.php
  3. 54
      plugin/zoom/lang/english.php
  4. 52
      plugin/zoom/lang/french.php
  5. 12
      plugin/zoom/lib/API/CreatedRegistration.php
  6. 13
      plugin/zoom/lib/API/CustomQuestion.php
  7. 34
      plugin/zoom/lib/API/GlobalDialInNumber.php
  8. 76
      plugin/zoom/lib/API/JWTClient.php
  9. 14
      plugin/zoom/lib/API/JsonDeserializableTrait.php
  10. 7
      plugin/zoom/lib/API/Meeting.php
  11. 4
      plugin/zoom/lib/API/MeetingInfo.php
  12. 2
      plugin/zoom/lib/API/MeetingInstances.php
  13. 2
      plugin/zoom/lib/API/MeetingList.php
  14. 2
      plugin/zoom/lib/API/MeetingListItem.php
  15. 55
      plugin/zoom/lib/API/MeetingRegistrant.php
  16. 21
      plugin/zoom/lib/API/MeetingRegistrantList.php
  17. 3
      plugin/zoom/lib/API/MeetingRegistrantListItem.php
  18. 29
      plugin/zoom/lib/API/MeetingSettings.php
  19. 8
      plugin/zoom/lib/API/ParticipantList.php
  20. 3
      plugin/zoom/lib/API/PastMeeting.php
  21. 10
      plugin/zoom/lib/API/RecordingFile.php
  22. 11
      plugin/zoom/lib/API/RecordingMeeting.php
  23. 25
      plugin/zoom/lib/API/TrackingField.php
  24. 2
      plugin/zoom/lib/CourseMeetingInfoGet.php
  25. 2
      plugin/zoom/lib/CourseMeetingList.php
  26. 54
      plugin/zoom/lib/CourseMeetingTrait.php
  27. 16
      plugin/zoom/lib/DisplayableMeetingTrait.php
  28. 46
      plugin/zoom/lib/UserMeetingRegistrant.php
  29. 19
      plugin/zoom/lib/UserMeetingRegistrantList.php
  30. 42
      plugin/zoom/lib/UserMeetingRegistrantListItem.php
  31. 82
      plugin/zoom/lib/UserMeetingRegistrantTrait.php
  32. 153
      plugin/zoom/lib/zoom_plugin.class.php
  33. 184
      plugin/zoom/meeting.php
  34. 12
      plugin/zoom/start.php
  35. 177
      plugin/zoom/view/meeting.tpl
  36. 2
      plugin/zoom/view/start.tpl

@ -22,9 +22,9 @@ $typeSelect = $form->addRadio(
'type',
get_lang('Type'),
[
JWTClient::MEETING_LIST_TYPE_SCHEDULED => get_lang('Scheduled'),
JWTClient::MEETING_LIST_TYPE_LIVE => get_lang('Live'),
JWTClient::MEETING_LIST_TYPE_UPCOMING => get_lang('Upcoming'),
JWTClient::MEETING_LIST_TYPE_SCHEDULED => get_lang('ScheduledMeetings'),
JWTClient::MEETING_LIST_TYPE_LIVE => get_lang('LiveMeetings'),
JWTClient::MEETING_LIST_TYPE_UPCOMING => get_lang('UpcomingMeetings'),
]
);
$form->addButtonSearch(get_lang('Search'));

@ -2,3 +2,6 @@
/* For licensing terms, see /license.txt */
require_once __DIR__.'/../../main/inc/global.inc.php';
// the section (for the tabs)
$this_section = SECTION_COURSES;

@ -8,6 +8,8 @@ $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['enableParticipantRegistration'] = 'Enable participant registration';
$strings['enableCloudRecording'] = 'Enable cloud recording';
$strings['tool_enable_help'] = "Choose whether you want to enable the Zoom videoconference tool.
Once enabled, it will show as an additional course tool in all courses' homepage :
@ -32,43 +34,71 @@ Locate your API Key and Secret in the App Credentials page.
<br/>Zoom is <em>NOT</em> free software and specific rules apply to personal data protection.
Please check with Zoom and make sure they satisfy you and learning users.";
$strings['enableParticipantRegistration_help'] = "Requires a paying Zoom profile.
Will not work for a <em>basic</em> profile.";
$strings['enableCloudRecording_help'] = "Requires a paying Zoom profile.
Will not work for a <em>basic</em> profile.";
// please keep these lines alphabetically sorted
$strings['%Hh%I'] = "%Hh%I";
$strings['AllCourseUsersWereRegistered'] = "All course students were registered";
$strings['AllRecordingsWereCopiedToCourse'] = "All recordings were copied to the course";
$strings['Agenda'] = "Agenda";
$strings['Course'] = "Course";
$strings['CopyAllRecordingsToCourse'] = "Copy all recordings to course";
$strings['CopyingJoinURL'] = "Copying join URL";
$strings['CopyJoinURL'] = "Copy join URL";
$strings['CopyRecordingToCourse'] = "Copy recording to course";
$strings['CouldNotCopyJoinURL'] = "Could not copy join URL";
$strings['Course'] = "Cours";
$strings['CreatedAt'] = "Created at";
$strings['DeleteMeeting'] = "Delete meeting";
$strings['DeleteRecordings'] = "Delete recordings";
$strings['Details'] = "Details";
$strings['Duration'] = "Duration";
$strings['DurationFormat'] = "%hh%I";
$strings['DurationInMinutes'] = "Duration (in minutes)";
$strings['EndDate'] = "End Date";
$strings['Instant'] = "Instant";
$strings['Finished'] = "finished";
$strings['InstantMeeting'] = "Instant meeting";
$strings['InstanceParticipants'] = "Participants";
$strings['InstancesAndRecordings'] = "Instances et enregistrements";
$strings['Join'] = "Join";
$strings['JoinMeeting'] = "Join meeting";
$strings['JoinURL'] = "Join URL";
$strings['JoinURLCopied'] = "Join URL copied";
$strings['JoinURLToSendToParticipants'] = "Join URL to send to participants";
$strings['Live'] = "Live";
$strings['JoinURLToShare'] = "Join URL to share";
$strings['LiveMeetings'] = "Live meetings";
$strings['Meeting'] = "Meeting";
$strings['MeetingDeleted'] = "Meeting deleted";
$strings['MeetingUpdated'] = "Meeting updated";
$strings['NewMeetingCreated'] = "New meeting created";
$strings['Participants'] = "Participants";
$strings['Password'] = "Password";
$strings['Recordings'] = "Recordings";
$strings['RecordingsWereDeleted'] = "Recordings were deleted";
$strings['RecordingWasCopiedToCourse'] = "The recording was copied to the course";
$strings['RecurringWithFixedTime'] = "Recurring with fixed time";
$strings['RecurringWithNoFixedTime'] = "Recurring with no fixed time";
$strings['ScheduleMeeting'] = "Schedule meeting";
$strings['Scheduled'] = "Scheduled";
$strings['RegisterAllCourseUsers'] = "Register all course users";
$strings['RegisteredUserListWasUpdated'] = "Registered user list updated";
$strings['RegisteredUsers'] = "Registered users";
$strings['ScheduleAMeeting'] = "Schedule a meeting";
$strings['ScheduledMeeting'] = "Scheduled meeting";
$strings['ScheduledMeetings'] = "Scheduled Meetings";
$strings['ScheduleTheMeeting'] = "Schedule the meeting";
$strings['Search'] = "Search";
$strings['Session'] = "Session";
$strings['StartDate'] = "Start Date";
$strings['Started'] = "started";
$strings['StartInstantMeeting'] = "Start instant meeting";
$strings['StartMeeting'] = "Start meeting";
$strings['StartTime'] = "Start time";
$strings['StartURLNotToBeShared'] = "Start URL (not to be shared)";
$strings['Status'] = "Status";
$strings['Topic'] = "Topic";
$strings['TopicAndAgenda'] = "Topic and agenda";
$strings['TotalSize'] = "Total size";
$strings['Type'] = "Type";
$strings['Upcoming'] = "Upcoming";
$strings['UpcomingMeetings'] = "Upcoming meetings";
$strings['UpdateMeeting'] = "Update meeting";
$strings['UpdateRegisteredUserList'] = "Update registered user list";
$strings['Y-m-d H:i'] = "Y-m-d H:i";
$strings['Waiting'] = "waiting";
$strings['ZoomVideoconference'] = "Zoom Videoconference";
$strings['ZoomVideoconferences'] = "Zoom Videoconferences";

@ -8,6 +8,8 @@ $strings['plugin_comment'] = "Intégration de conférences vidéo Zoom dans les
$strings['tool_enable'] = 'Outil de conférence vidéos Zoom activé';
$strings['apiKey'] = "Clé d'API (<em>API Key</em>)";
$strings['apiSecret'] = "Code secret d'API (<em>API Secret</em>)";
$strings['enableParticipantRegistration'] = "Activer l'inscription des participants";
$strings['enableCloudRecording'] = "Activer l'enregistrement sur les serveurs de Zoom";
$strings['tool_enable_help'] = "Choisissez si vous voulez activer l'outil de conférence vidéo Zoom.
Une fois activé, il apparaitra dans les pages d'accueil de tous les cours :
@ -31,43 +33,71 @@ Pour les obtenir, créez une <em>JWT app</em> :
et des règles spécifiques de protection des données personnelles s'y appliquent.
Merci de vérifier auprès de Zoom qu'elles sont satisfaisantes pour vous et les apprenants qui l'utiliseront.";
$strings['enableParticipantRegistration_help'] = "Nécessite un profil Zoom payant.
Ne fonctionnera pas pour un profil <em>de base</em>.";
$strings['enableCloudRecording_help'] = "Nécessite un profil Zoom payant.
Ne fonctionnera pas pour un profil <em>de base</em>.";
// please keep these lines alphabetically sorted
$strings['%Hh%I'] = "%Hh%I";
$strings['AllCourseUsersWereRegistered'] = "Tous les étudiants du cours sont inscrits";
$strings['AllRecordingsWereCopiedToCourse'] = "Tous les enregistrements ont été copiés dans le cours";
$strings['Agenda'] = "Ordre du jour";
$strings['CopyAllRecordingsToCourse'] = "Copier tous les enregistrements dans le cours";
$strings['CopyingJoinURL'] = "Copie de l'URL pour rejoindre en cours";
$strings['CopyJoinURL'] = "Copier l'URL pour rejoindre";
$strings['CopyRecordingToCourse'] = "Copier l'enregistrement dans le cours";
$strings['CouldNotCopyJoinURL'] = "Échec de la copie de l'URL pour rejoindre";
$strings['Course'] = "Cours";
$strings['CreatedAt'] = "Créé à";
$strings['DeleteMeeting'] = "Effacer la conférence";
$strings['DeleteRecordings'] = "Supprimer l'enregistrement";
$strings['Details'] = "Détail";
$strings['Duration'] = "Durée";
$strings['DurationFormat'] = "%hh%I";
$strings['DurationInMinutes'] = "Durée (en minutes)";
$strings['EndDate'] = "Date de fin";
$strings['Instant'] = "Instantané";
$strings['Finished'] = "terminée";
$strings['InstantMeeting'] = "Conférence instantanée";
$strings['InstanceParticipants'] = "Participants";
$strings['InstancesAndRecordings'] = "Instances et enregistrements";
$strings['Join'] = "Rejoindre";
$strings['JoinMeeting'] = "Rejoindre la conférence";
$strings['JoinURL'] = "URL pour rejoindre";
$strings['JoinURLCopied'] = "URL pour rejoindre copiée";
$strings['JoinURLToSendToParticipants'] = "URL pour assister à la conférence (à envoyer aux participants)";
$strings['Live'] = "En cours";
$strings['JoinURLToShare'] = "URL pour rejoindre, à partager";
$strings['LiveMeetings'] = "Conférences en cours";
$strings['Meeting'] = "Conférence";
$strings['MeetingDeleted'] = "Conférence effacée";
$strings['MeetingUpdated'] = "Conférence mise à jour";
$strings['NewMeetingCreated'] = "Nouvelle conférence créée";
$strings['Participants'] = "Participants";
$strings['Password'] = "Mot de passe";
$strings['Recordings'] = "Enregistrements";
$strings['RecordingsWereDeleted'] = "Les enregistrements ont été supprimés";
$strings['RecordingWasCopiedToCourse'] = "L'enregistrement a été copié dans le cours";
$strings['RecurringWithFixedTime'] = "Recurrent, à heure fixe";
$strings['RecurringWithNoFixedTime'] = "Recurrent, sans heure fixe";
$strings['ScheduleMeeting'] = "Programmer une conférence";
$strings['Scheduled'] = "Programmées";
$strings['RegisterAllCourseUsers'] = "Inscrire tous les utilisateurs du cours";
$strings['RegisteredUserListWasUpdated'] = "Liste des utilisateurs inscrits mise à jour";
$strings['RegisteredUsers'] = "Utilisateurs inscrits";
$strings['ScheduleAMeeting'] = "Programmer une conférence";
$strings['ScheduledMeeting'] = "Conférence programmée";
$strings['ScheduledMeetings'] = "Conférences programmées";
$strings['ScheduleTheMeeting'] = "Programmer la conférence";
$strings['Search'] = "Rechercher";
$strings['Session'] = "Session";
$strings['StartDate'] = "Date de début";
$strings['Started'] = "démarrée";
$strings['StartInstantMeeting'] = "Démarrer une conférence instantanée";
$strings['StartMeeting'] = "démarrer la conférence";
$strings['StartTime'] = "Heure de début";
$strings['StartURLNotToBeShared'] = "URL de démarrage de la conférence (à ne pas partager)";
$strings['Status'] = "Statut";
$strings['Topic'] = "Objet";
$strings['TopicAndAgenda'] = "Objet et ordre du jour";
$strings['TotalSize'] = "Taille totale";
$strings['Type'] = "Type";
$strings['Upcoming'] = "À venir";
$strings['UpcomingMeeting'] = "Conférences à venir";
$strings['UpdateMeeting'] = "Mettre à jour la conférence";
$strings['UpdateRegisteredUserList'] = "Mettre à jour la liste des utilisateurs inscrits";
$strings['Y-m-d H:i'] = "d/m/Y à H\hi";
$strings['Waiting'] = "en attente";
$strings['ZoomVideoconference'] = "Conférence vidéo Zoom";
$strings['ZoomVideoconferences'] = "Conférences vidéo Zoom";

@ -3,6 +3,8 @@
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class CreatedRegistration
{
use JsonDeserializableTrait;
@ -16,7 +18,7 @@ class CreatedRegistration
*/
public $join_url;
/** @var Unique identifier of the registrant */
/** @var string Unique identifier of the registrant */
public $registrant_id;
/** @var string The start time for the meeting. */
@ -24,4 +26,12 @@ class CreatedRegistration
/** @var string Topic of the meeting. */
public $topic;
/**
* @inheritDoc
*/
protected function itemClass($propertyName)
{
throw new Exception("no such array property $propertyName");
}
}

@ -0,0 +1,13 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
class CustomQuestion
{
/** @var string */
public $title;
/** @var string */
public $value;
}

@ -0,0 +1,34 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class GlobalDialInNumber
{
use JsonDeserializableTrait;
/** @var string Country code. For example, BR. */
public $country;
/** @var string Full name of country. For example, Brazil. */
public $country_name;
/** @var string City of the number, if any. For example, Chicago. */
public $city;
/** @var string Phone number. For example, +1 2332357613. */
public $number;
/** @var string Type of number. Either "toll" or "tollfree". */
public $type;
/**
* @inheritDoc
*/
public function itemClass($propertyName)
{
throw new Exception("No such array property $propertyName");
}
}

@ -206,7 +206,10 @@ class JWTClient
* Adds a meeting registrant.
*
* @param int $meetingId meeting identifier
* @param MeetingRegistrant $registrant with at least 'email' and 'first_name'
* @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
* (at least while using profile "Pro")
* @param string $occurrenceIds separated by comma
*
* @throws Exception describing the error (message and code)
@ -215,12 +218,38 @@ class JWTClient
*/
public function addRegistrant($meetingId, $registrant, $occurrenceIds = '')
{
$path = 'meetings/'.$meetingId.'/registrants';
if (!empty($occurrenceIds)) {
$path .= "?occurrence_ids=$occurrenceIds";
}
return CreatedRegistration::fromJson(
$this->send(
'POST',
"meetings/$meetingId/registrants",
empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds],
$registrant
)
);
}
return CreatedRegistration::fromJson($this->send('POST', $path, [], $registrant));
/**
* Removes meeting registrants.
*
* @param int $meetingId meeting identifier
* @param MeetingRegistrant[] $registrants registrants to remove (id and email)
* @param string $occurrenceIds separated by comma
*
* @throws Exception
*/
public function removeRegistrants($meetingId, array $registrants, $occurrenceIds = '')
{
if (!empty($registrants)) {
$this->send(
'PUT',
"meetings/$meetingId/registrants/status",
empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds],
(object) [
'action' => 'cancel',
'registrants' => $registrants,
]
);
}
}
/**
@ -242,51 +271,64 @@ class JWTClient
}
/**
* Retrieves a past meeting's details.
* Retrieves past meeting instance details.
*
* @param string $meetingUUID the meeting UUID
* @param string $instanceUUID the meeting instance UUID
*
* @throws Exception describing the error (message and code)
*
* @return PastMeeting meeting
*/
public function getPastMeetingDetails($meetingUUID)
public function getPastMeetingInstanceDetails($instanceUUID)
{
return PastMeeting::fromJson($this->send('GET', 'past_meetings/'.$meetingUUID));
return PastMeeting::fromJson($this->send('GET', 'past_meetings/'.$instanceUUID));
}
/**
* Gets the recordings from a meeting.
*
* @param string $meetingUUID
* @param string $instanceUUID
*
* @throws Exception describing the error (message and code)
* @throws Exception describing the error (message and code).
* Code is 404 when there is no recording for this meeting.
*
* @return RecordingMeeting the recordings for this meeting
*/
public function getRecordings($meetingUUID)
public function getRecordings($instanceUUID)
{
return RecordingMeeting::fromJson(
$this->send(
'GET',
'meetings/'.$this->doubleEncode($meetingUUID).'/recordings'
'meetings/'.$this->doubleEncode($instanceUUID).'/recordings'
)
);
}
/**
* Deletes a meetings recordings.
*
* @param $instanceUUID
*
* @throws Exception
*/
public function deleteRecordings($instanceUUID)
{
$this->send('DELETE', 'meetings/'.$this->doubleEncode($instanceUUID).'/recordings', ['action' => 'delete']);
}
/**
* Retrieves information on participants from a past meeting.
*
* @param string $meetingUUID the meeting instance UUID
* @param string $instanceUUID the meeting instance UUID
*
* @throws Exception describing the error (message and code)
*
* @return ParticipantListItem[] participants
*/
public function getParticipants($meetingUUID)
public function getParticipants($instanceUUID)
{
return $this->getFullList(
'past_meetings/'.$this->doubleEncode($meetingUUID).'/participants',
'past_meetings/'.$this->doubleEncode($instanceUUID).'/participants',
ParticipantList::class,
'participants'
);

@ -43,7 +43,7 @@ trait JsonDeserializableTrait
*
* @return string class name of the items to be found in the named array property
*/
abstract protected function itemClass($propertyName);
abstract public function itemClass($propertyName);
/**
* Copies values from another object properties to an instance, recursively.
@ -65,11 +65,15 @@ trait JsonDeserializableTrait
}
} elseif (is_array($value)) {
if (is_array($destination->$name)) {
$itemClass = $destination->itemClass($name);
foreach ($value as $sourceItem) {
$itemClass = $destination->itemClass($name);
$item = new $itemClass();
self::recursivelyCopyObjectProperties($sourceItem, $item);
$destination->$name[] = $item;
if ('string' === $itemClass) {
$destination->$name[] = $sourceItem;
} else {
$item = new $itemClass();
self::recursivelyCopyObjectProperties($sourceItem, $item);
$destination->$name[] = $item;
}
}
} else {
throw new Exception("Source property $name is an array, which is not expected");

@ -18,7 +18,7 @@ class Meeting
/** @var string password to join. [a-z A-Z 0-9 @ - _ *]. Max of 10 characters. */
public $password;
/** @var array field => value */
/** @var TrackingField[] Tracking fields */
public $tracking_fields;
/** @var object, only for a recurring meeting with fixed time (type 8) */
@ -58,8 +58,11 @@ class Meeting
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
public function itemClass($propertyName)
{
if ('tracking_fields' === $propertyName) {
return TrackingField::class;
}
throw new Exception("no such array property $propertyName");
}
}

@ -14,6 +14,9 @@ class MeetingInfo extends Meeting
/** @var string, for participants to join the meeting - to share with users to invite */
public $join_url;
/** @var string undocumented */
public $registration_url;
/** @var string H.323/SIP room system password */
public $h323_password;
@ -22,4 +25,5 @@ class MeetingInfo extends Meeting
/** @var object[] */
public $occurrences;
}

@ -23,7 +23,7 @@ class MeetingInstances
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
public function itemClass($propertyName)
{
if ('meetings' === $propertyName) {
return MeetingInstance::class;

@ -23,7 +23,7 @@ class MeetingList
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
public function itemClass($propertyName)
{
if ('meetings' === $propertyName) {
return MeetingListItem::class;

@ -31,7 +31,7 @@ class MeetingListItem
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
public function itemClass($propertyName)
{
throw new Exception("no such array property $propertyName");
}

@ -3,40 +3,93 @@
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class MeetingRegistrant
{
use JsonDeserializableTrait;
/** @var string */
public $email;
/** @var string */
public $first_name;
/** @var string */
public $last_name;
/** @var string */
public $address;
/** @var string */
public $city;
/** @var string */
public $country;
/** @var string */
public $zip;
/** @var string */
public $state;
/** @var string */
public $phone;
/** @var string */
public $industry;
/** @var string */
public $org;
/** @var string */
public $job_title;
/** @var string */
public $purchasing_time_frame;
/** @var string */
public $role_in_purchase_process;
/** @var string */
public $no_of_employees;
/** @var string */
public $comments;
/** @var object[] title => value */
public $custom_question;
public $custom_questions;
/**
* MeetingRegistrant constructor.
*/
public function __construct()
{
$this->custom_questions = [];
}
/**
* @param string $email
* @param string $first_name
*
* @return MeetingRegistrant
*/
public static function fromEmailAndFirstName($email, $first_name)
{
$instance = new static();
$instance->first_name = $first_name;
$instance->email = $email;
return $instance;
}
/**
* @inheritDoc
*/
public function itemClass($propertyName)
{
if ('custom_questions' == $propertyName) {
return CustomQuestion::class;
}
throw new Exception("no such array property $propertyName");
}
}

@ -3,10 +3,31 @@
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class MeetingRegistrantList
{
use Pagination;
/** @var MeetingRegistrantListItem[] */
public $registrants;
/**
* MeetingRegistrantList constructor.
*/
public function __construct()
{
$this->registrants = [];
}
/**
* @inheritDoc
*/
public function itemClass($propertyName)
{
if ('registrants' === $propertyName) {
return MeetingRegistrantListItem::class;
}
throw new Exception("no such array property $propertyName");
}
}

@ -7,14 +7,17 @@ class MeetingRegistrantListItem extends MeetingRegistrant
{
/** @var string Registrant ID. */
public $id;
/** @var string The status of the registrant's registration.
* `approved`: User has been successfully approved for the webinar.
* `pending`: The registration is still pending.
* `denied`: User has been denied from joining the webinar.
*/
public $status;
/** @var string The time at which the registrant registered. */
public $create_time;
/** @var string The URL using which an approved registrant can join the webinar. */
public $join_url;
}

@ -3,8 +3,12 @@
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class MeetingSettings
{
use JsonDeserializableTrait;
const APPROVAL_TYPE_AUTOMATICALLY_APPROVE = 0;
const APPROVAL_TYPE_MANUALLY_APPROVE = 1;
const APPROVAL_TYPE_NO_REGISTRATION_REQUIRED = 2;
@ -74,7 +78,7 @@ class MeetingSettings
/** @var string[] List of global dial-in countries */
public $global_dial_in_countries;
/** @var object[] Global Dial-in Countries/Regions */
/** @var GlobalDialInNumber[] Global Dial-in Countries/Regions */
public $global_dial_in_numbers;
/** @var string Contact name for registration */
@ -106,4 +110,27 @@ class MeetingSettings
* @see https://support.zoom.us/hc/en-us/articles/360037117472-Authentication-Profiles-for-Meetings-and-Webinars#h_5c0df2e1-cfd2-469f-bb4a-c77d7c0cca6f
*/
public $authentication_name;
/**
* MeetingSettings constructor.
*/
public function __construct()
{
$this->global_dial_in_countries = [];
$this->global_dial_in_numbers = [];
}
/**
* @inheritDoc
*/
public function itemClass($propertyName)
{
if ('global_dial_in_countries' === $propertyName) {
return 'string';
}
if ('global_dial_in_numbers' === $propertyName) {
return GlobalDialInNumber::class;
}
throw new Exception("No such array property $propertyName");
}
}

@ -9,6 +9,12 @@ class ParticipantList
{
use Pagination;
/** @var string The next page token is used to paginate through large result sets.
* A next page token will be returned whenever the set of available results exceeds the current page size.
* The expiration period for this token is 15 minutes.
*/
public $next_page_token;
/** @var ParticipantListItem[] */
public $participants;
@ -23,7 +29,7 @@ class ParticipantList
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
public function itemClass($propertyName)
{
if ('participants' === $propertyName) {
return ParticipantListItem::class;

@ -40,4 +40,7 @@ class PastMeeting extends Meeting
/** @var int number of meeting participants */
public $participants_count;
/** @var string undocumented */
public $dept;
}

@ -3,6 +3,8 @@
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class RecordingFile
{
use JsonDeserializableTrait;
@ -68,4 +70,12 @@ class RecordingFile
* `TIMELINE`
*/
public $recording_type;
/**
* @inheritDoc
*/
public function itemClass($propertyName)
{
throw new Exception("No such array property $propertyName");
}
}

@ -24,9 +24,15 @@ class RecordingMeeting
/** @var string Meeting topic. */
public $topic;
/** @var int undocumented */
public $type;
/** @var string The time at which the meeting started. */
public $start_time;
/** @var string undocumented */
public $timezone;
/** @var int Meeting duration. */
public $duration;
@ -36,6 +42,9 @@ class RecordingMeeting
/** @var string Number of recording files returned in the response of this API call. */
public $recording_count;
/** @var string undocumented */
public $share_url;
/** @var RecordingFile[] List of recording file. */
public $recording_files;
@ -50,7 +59,7 @@ class RecordingMeeting
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
public function itemClass($propertyName)
{
if ('recording_files' === $propertyName) {
return RecordingFile::class;

@ -0,0 +1,25 @@
<?php
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class TrackingField
{
use JsonDeserializableTrait;
/** @var string Tracking fields type */
public $field;
/** @var string Tracking fields value */
public $value;
/**
* @inheritDoc
*/
public function itemClass($propertyName)
{
throw new Exception("no such array property $propertyName");
}
}

@ -36,8 +36,6 @@ class CourseMeetingInfoGet extends API\MeetingInfoGet
$instance = new static();
self::recursivelyCopyObjectProperties($meeting, $instance);
$instance->decodeAndRemoveTag();
$instance->loadCourse();
$instance->loadSession();
$instance->initializeDisplayableProperties();
return $instance;

@ -8,7 +8,7 @@ class CourseMeetingList extends API\MeetingList
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
public function itemClass($propertyName)
{
if ('meetings' === $propertyName) {
return CourseMeetingListItem::class;

@ -4,6 +4,12 @@
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
use Chamilo\UserBundle\Entity\User;
use Database;
/**
* Trait CourseMeetingTrait.
* A Zoom meeting linked to a (course, session) pair.
@ -20,23 +26,25 @@ trait CourseMeetingTrait
/** @var int meeting course id as found in the agenda field */
public $courseId;
/** @var array meeting course info */
/** @var Course meeting course */
public $course;
/** @var int meeting session id as found in the agenda field */
public $sessionId;
/** @var array meeting session info */
/** @var Session meeting session */
public $session;
public function loadCourse()
{
$this->course = api_get_course_info_by_id($this->courseId); // TODO cache
$this->course = Database::getManager()->getRepository('ChamiloCoreBundle:Course')->find($this->courseId);
}
public function loadSession()
{
$this->session = api_get_session_info($this->sessionId); // TODO cache
$this->session = $this->sessionId
? Database::getManager()->getRepository('ChamiloCoreBundle:Session')->find($this->sessionId)
: null;
}
public function setCourseAndSessionId($courseId, $sessionId)
@ -55,6 +63,40 @@ trait CourseMeetingTrait
$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
@ -75,8 +117,8 @@ trait CourseMeetingTrait
} else {
$this->setCourseAndSessionId(0, 0);
}
$this->course = [];
$this->session = [];
$this->course = null;
$this->session = null;
}
protected function getUntaggedAgenda()

@ -28,17 +28,27 @@ trait DisplayableMeetingTrait
/** @var string meeting formatted duration */
public $formattedDuration;
/** @var string */
public $statusName;
/**
* @throws Exception on unexpected start_time or duration
*/
public function initializeDisplayableProperties()
{
$this->typeName = [
API\Meeting::TYPE_INSTANT => get_lang('Instant'),
API\Meeting::TYPE_SCHEDULED => get_lang('Scheduled'),
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;
@ -53,7 +63,7 @@ trait DisplayableMeetingTrait
$later = new DateTime();
$later->add(new DateInterval('PT'.$this->duration.'M'));
$this->durationInterval = $later->diff($now);
$this->formattedDuration = $this->durationInterval->format(get_lang('%Hh%I'));
$this->formattedDuration = $this->durationInterval->format(get_lang('DurationFormat'));
}
}
}

@ -0,0 +1,46 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\UserBundle\Entity\User;
use Exception;
class UserMeetingRegistrant extends API\MeetingRegistrant
{
use UserMeetingRegistrantTrait;
/**
* {@inheritdoc}
*/
public static function fromJson($json)
{
$instance = parent::fromJson($json);
$instance->decodeAndRemoveTag();
$instance->computeFullName();
return $instance;
}
/**
* 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;
}
}

@ -0,0 +1,19 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
class UserMeetingRegistrantList extends API\MeetingRegistrantList
{
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('meetings' === $propertyName) {
return UserMeetingRegistrantListItem::class;
}
return parent::itemClass($propertyName);
}
}

@ -0,0 +1,42 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Exception;
class UserMeetingRegistrantListItem extends API\MeetingRegistrantListItem
{
use UserMeetingRegistrantTrait;
/**
* {@inheritdoc}
*/
public static function fromJson($json)
{
$instance = parent::fromJson($json);
$instance->decodeAndRemoveTag();
$instance->computeFullName();
return $instance;
}
/**
* UserMeetingRegistrantListItem constructor.
*
* @param API\MeetingRegistrantListItem $meetingRegistrantListItem
*
* @throws Exception
*
* @return static
*/
public static function fromMeetingRegistrantListItem($meetingRegistrantListItem)
{
$instance = new static();
self::recursivelyCopyObjectProperties($meetingRegistrantListItem, $instance);
$instance->decodeAndRemoveTag();
$instance->computeFullName();
return $instance;
}
}

@ -0,0 +1,82 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\UserBundle\Entity\User;
use Database;
trait UserMeetingRegistrantTrait
{
/** @var bool whether the remote zoom record contains a local user's identifier */
public $isTaggedWithUserId;
/** @var int */
public $userId;
/** @var User */
public $user;
/** @var string */
public $fullName;
public function loadUser()
{
$this->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<userId>\d+)@/m';
}
}

@ -2,12 +2,18 @@
/* For licensing terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\API\CreatedRegistration;
use Chamilo\PluginBundle\Zoom\API\JWTClient;
use Chamilo\PluginBundle\Zoom\API\MeetingInstance;
use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
use Chamilo\PluginBundle\Zoom\API\ParticipantListItem;
use Chamilo\PluginBundle\Zoom\API\PastMeeting;
use Chamilo\PluginBundle\Zoom\API\RecordingMeeting;
use Chamilo\PluginBundle\Zoom\CourseMeeting;
use Chamilo\PluginBundle\Zoom\CourseMeetingInfoGet;
use Chamilo\PluginBundle\Zoom\CourseMeetingListItem;
use Chamilo\PluginBundle\Zoom\UserMeetingRegistrant;
use Chamilo\PluginBundle\Zoom\UserMeetingRegistrantListItem;
class ZoomPlugin extends Plugin
{
@ -22,6 +28,8 @@ class ZoomPlugin extends Plugin
'tool_enable' => 'boolean',
'apiKey' => 'text',
'apiSecret' => 'text',
'enableParticipantRegistration' => 'boolean',
'enableCloudRecording' => 'boolean',
]
);
@ -110,6 +118,30 @@ class ZoomPlugin extends Plugin
return CourseMeetingInfoGet::fromMeetingInfoGet($this->jwtClient()->getMeeting($meetingId));
}
/**
* @param $meetingId
*
* @return MeetingInstance[]
*
* @throws Exception
*/
public function getEndedMeetingInstances($meetingId)
{
return $this->jwtClient()->getEndedMeetingInstances($meetingId);
}
/**
* @param string $instanceUUID
*
* @throws Exception
*
* @return PastMeeting
*/
public function getPastMeetingInstanceDetails($instanceUUID)
{
return $this->jwtClient()->getPastMeetingInstanceDetails($instanceUUID);
}
/**
* Retrieves all live meetings linked to current course and session.
*
@ -198,6 +230,9 @@ class ZoomPlugin extends Plugin
$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;
return $this->createMeeting($meeting);
}
@ -214,6 +249,7 @@ class ZoomPlugin extends Plugin
{
$meeting->tagAgenda();
$this->jwtClient()->updateMeeting($meetingId, $meeting);
$meeting->untagAgenda();
}
/**
@ -243,29 +279,29 @@ class ZoomPlugin extends Plugin
/**
* @see JWTClient::getRecordings()
*
* @param string $meetingUUID
* @param string $instanceUUID
*
* @throws Exception on API error
*
* @return RecordingMeeting the recordings of the meeting
*/
public function getRecordings($meetingUUID)
public function getRecordings($instanceUUID)
{
return $this->jwtClient()->getRecordings($meetingUUID);
return $this->jwtClient()->getRecordings($instanceUUID);
}
/**
* @see JWTClient::getParticipants()
*
* @param string $meetingUUID
* @param string $instanceUUID
*
* @throws Exception
*
* @return ParticipantListItem[]
*/
public function getParticipants($meetingUUID)
public function getParticipants($instanceUUID)
{
return $this->jwtClient()->getParticipants($meetingUUID);
return $this->jwtClient()->getParticipants($instanceUUID);
}
/**
@ -318,9 +354,112 @@ class ZoomPlugin extends Plugin
*/
private function createMeeting($meeting)
{
$meeting->settings->auto_recording = 'cloud';
$meeting->settings->auto_recording = $this->get('enableCloudRecording')
? 'cloud'
: 'local';
$meeting->settings->registrants_email_notification = false;
$meeting->tagAgenda();
return CourseMeetingInfoGet::fromMeetingInfoGet($this->jwtClient()->createMeeting($meeting));
}
/**
* @param $meetingId
*
* @throws Exception
*
* @return UserMeetingRegistrantListItem[]
*/
public function getRegistrants($meetingId)
{
$registrants = [];
foreach ($this->jwtClient()->getRegistrants($meetingId) as $registrant) {
$registrants[] = UserMeetingRegistrantListItem::fromMeetingRegistrantListItem($registrant);
}
return $registrants;
}
/**
* @param int $meetingId
* @param \Chamilo\UserBundle\Entity\User[] $users
*
* @throws Exception
*
* @return CreatedRegistration[]
*/
public function addRegistrants($meetingId, $users)
{
$createdRegistrations = [];
foreach ($users as $user) {
$registrant = UserMeetingRegistrant::fromUser($user);
$registrant->tagEmail();
$createdRegistrations[] = $this->jwtClient()->addRegistrant($meetingId, $registrant);
}
return $createdRegistrations;
}
/**
* @param int $meetingId
* @param UserMeetingRegistrant[] $registrants
*
* @throws Exception
*/
public function removeRegistrants($meetingId, $registrants)
{
$this->jwtClient()->removeRegistrants($meetingId, $registrants);
}
/**
* Updates meeting registrants list. Adds the missing registrants and removes the extra.
*
* @param int $meetingId meeting identifier
* @param \Chamilo\UserBundle\Entity\User[] $users list of users to be registred
*
* @throws Exception
*/
public function updateRegistrantList($meetingId, $users)
{
$registrants = $this->getRegistrants($meetingId);
$usersToAdd = [];
foreach ($users as $user) {
$found = false;
foreach ($registrants as $registrant) {
if ($registrant->matches($user->getId())) {
$found = true;
break;
}
}
if (!$found) {
$usersToAdd[] = $user;
}
}
$registrantsToRemove = [];
foreach ($registrants as $registrant) {
$found = false;
foreach ($users as $user) {
if ($registrant->matches($user->getId())) {
$found = true;
break;
}
}
if (!$found) {
$registrantsToRemove[] = $registrant;
}
}
$this->addRegistrants($meetingId, $usersToAdd);
$this->removeRegistrants($meetingId, $registrantsToRemove);
}
/**
* Deletes a meeting instance's recordings.
*
* @param string $instanceUUID
*
* @throws Exception
*/
public function deleteRecordings($instanceUUID)
{
$this->jwtClient()->deleteRecordings($instanceUUID);
}
}

@ -1,6 +1,8 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
if (!isset($returnURL)) {
exit;
}
@ -13,22 +15,25 @@ $logInfo = [
Event::registerLog($logInfo);
$tool_name = get_lang('ZoomVideoconference');
$tpl = new Template($tool_name);
$plugin = ZoomPlugin::create();
$interbreadcrumb[] = [ // used in templates
'url' => $returnURL,
'name' => get_lang('ZoomVideoconferences'),
];
if (!array_key_exists('meetingId', $_REQUEST)) {
throw new Exception('MeetingNotFound');
}
$plugin = ZoomPlugin::create();
$meeting = $plugin->getMeeting($_REQUEST['meetingId']);
$tpl = new Template($meeting->id);
if ($plugin->userIsConferenceManager()) {
// user can edit, start and delete meeting
$tpl->assign('isConferenceManager', true);
$editMeetingForm = new FormValidator('editMeetingForm');
$editMeetingForm->addHidden('meetingId', $meeting->id);
$editMeetingForm = new FormValidator('editMeetingForm', 'post', $_SERVER['REQUEST_URI']);
if ($meeting::TYPE_SCHEDULED === $meeting->type
||
@ -54,7 +59,6 @@ if ($plugin->userIsConferenceManager()) {
Display::addFlash(
Display::return_message(get_lang('MeetingUpdated'), 'confirm')
);
location($returnURL);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
@ -77,8 +81,7 @@ if ($plugin->userIsConferenceManager()) {
}
$tpl->assign('editMeetingForm', $editMeetingForm->returnForm());
$deleteMeetingForm = new FormValidator('deleteMeetingForm');
$deleteMeetingForm->addHidden('meetingId', $meeting->id);
$deleteMeetingForm = new FormValidator('deleteMeetingForm', 'post', $_SERVER['REQUEST_URI']);
$deleteMeetingForm->addButtonDelete(get_lang('DeleteMeeting'));
$tpl->assign('deleteMeetingForm', $deleteMeetingForm->returnForm());
@ -95,29 +98,156 @@ if ($plugin->userIsConferenceManager()) {
);
}
}
}
$tpl->assign('meeting', $meeting);
try {
$tpl->assign('recordings', $plugin->getRecordings($meeting->uuid));
} catch (Exception $exception) {
if ($exception->getCode() === 404) { // No recording for this meeting
// fine, ignoring
} else {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
if ($plugin->get('enableParticipantRegistration')
&& MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $meeting->settings->approval_type) {
$tpl->assign('enableParticipantRegistration', true);
$registerParticipantForm = new FormValidator('registerParticipantForm', 'post', $_SERVER['REQUEST_URI']);
$userIdSelect = $registerParticipantForm->addSelect('userIds', get_lang('RegisteredUsers'));
$userIdSelect->setMultiple(true);
$registerParticipantForm->addButtonSend(get_lang('UpdateRegisteredUserList'));
$users = $meeting->getCourseAndSessionUsers();
foreach ($users as $user) {
$userIdSelect->addOption(api_get_person_name($user->getFirstname(), $user->getLastname()), $user->getId());
}
if ($registerParticipantForm->validate()) {
$selectedUserIds = $userIdSelect->getValue();
$selectedUsers = [];
foreach ($users as $user) {
if (in_array($user->getId(), $selectedUserIds)) {
$selectedUsers[] = $user;
}
}
try {
$plugin->updateRegistrantList($meeting->id, $selectedUsers);
Display::addFlash(
Display::return_message(get_lang('RegisteredUserListWasUpdated'), 'confirm')
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
}
try {
$registrants = $plugin->getRegistrants($meeting->id);
$tpl->assign('registrants', $registrants);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
$registrants = [];
}
$registeredUserIds = [];
foreach ($registrants as $registrant) {
$registeredUserIds[] = $registrant->userId;
}
$userIdSelect->setSelected($registeredUserIds);
$tpl->assign('registerParticipantForm', $registerParticipantForm->returnForm());
}
}
try {
$tpl->assign('participants', $plugin->getParticipants($meeting->uuid));
} catch (Exception $exception) {
if ($exception->getCode() === 400) { // Only available pour paid account
// pass
} else {
if ($plugin->get('enableCloudRecording')
&& 'cloud' === $meeting->settings->auto_recording
// && 'finished' === $meeting->status
) {
$instances = [];
foreach ($plugin->getEndedMeetingInstances($meeting->id) as $instance) {
// $instance->instanceDetails = $plugin->getPastMeetingInstanceDetails($instance->uuid);
try {
$instance->recordings = $plugin->getRecordings($instance->uuid);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
foreach ($instance->recordings->recording_files as &$file) {
$copyToCourseForm = new FormValidator(
'copyToCourseForm'.$file->id,
'post',
$_SERVER['REQUEST_URI']
);
$copyToCourseForm->addButtonCopy(get_lang('CopyRecordingToCourse'));
if ($copyToCourseForm->validate()) {
try {
$plugin->copyRecordingToCourse($meeting, $file);
Display::addFlash(
Display::return_message(get_lang('RecordingWasCopiedToCourse'), 'confirm')
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
}
$file->copyToCourseForm = $copyToCourseForm->returnForm();
}
$copyAllRecordingsToCourseForm = new FormValidator(
'copyAllRecordingsToCourseForm'.$instance->uuid,
'post',
$_SERVER['REQUEST_URI']
);
$copyAllRecordingsToCourseForm->addButtonCopy(get_lang('CopyAllRecordingsToCourse'));
if ($copyAllRecordingsToCourseForm->validate()) {
try {
$plugin->copyAllRecordingsToCourse($instance->uuid);
Display::addFlash(
Display::return_message(get_lang('AllRecordingsWereCopiedToCourse'), 'confirm')
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
}
$instance->copyAllRecordingsToCourseForm = $copyAllRecordingsToCourseForm->returnForm();
$deleteRecordingsForm = new FormValidator(
'deleteRecordingsForm'.$instance->uuid,
'post',
$_SERVER['REQUEST_URI']
);
$deleteRecordingsForm->addButtonSend(get_lang('DeleteRecordings'));
if ($deleteRecordingsForm->validate()) {
try {
$plugin->deleteRecordings($instance->uuid);
Display::addFlash(
Display::return_message(get_lang('RecordingsWereDeleted'), 'confirm')
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
}
$instance->deleteRecordingsForm = $deleteRecordingsForm->returnForm();
// $instance->participants = $plugin->getParticipants($instance->uuid);
$instances[] = $instance;
}
$tpl->assign('instances', $instances);
}
} elseif (MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $meeting->settings->approval_type) {
$userId = api_get_user_id();
try {
foreach ($plugin->getRegistrants($meeting->id) as $registrant) {
if ($registrant->userId == $userId) {
$tpl->assign('currentUserJoinURL', $registrant->join_url);
break;
}
}
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
}
$tpl->assign('meeting', $meeting);
$tpl->assign('content', $tpl->fetch('zoom/view/meeting.tpl'));
$tpl->display_one_col_template();

@ -48,7 +48,8 @@ if ($plugin->userIsConferenceManager()) {
$topicText = $scheduleMeetingForm->addText('topic', get_lang('Topic'), true);
$agendaTextArea = $scheduleMeetingForm->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
// $passwordText = $scheduleMeetingForm->addText('password', get_lang('Password'), false, ['maxlength' => '10']);
$scheduleMeetingForm->addButtonCreate(get_lang('ScheduleMeeting'));
$registerAll = $scheduleMeetingForm->addCheckBox('registerAll', null, get_lang('RegisterAllCourseUsers'));
$scheduleMeetingForm->addButtonCreate(get_lang('ScheduleTheMeeting'));
// meeting scheduling
if ($scheduleMeetingForm->validate()) {
@ -63,7 +64,13 @@ if ($plugin->userIsConferenceManager()) {
Display::addFlash(
Display::return_message($plugin->get_lang('NewMeetingCreated'))
);
$tpl->assign('newMeeting', $newMeeting);
if ($registerAll->getValue()) {
$plugin->addRegistrants($newMeeting->id, $newMeeting->getCourseAndSessionUsers());
Display::addFlash(
Display::return_message($plugin->get_lang('AllCourseUsersWereRegistered'))
);
}
location('meeting_from_start.php?meetingId='.$newMeeting->id);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
@ -74,6 +81,7 @@ if ($plugin->userIsConferenceManager()) {
[
'duration' => 60,
'topic' => api_get_course_info()['title'],
'registerAll' => true,
]
);
}

@ -1,85 +1,134 @@
<div class="page-header">
<h2>{{ 'Meeting'|get_lang }}</h2>
</div>
<style>
dl.meeting_properties dt {
margin-top: 1em;
font-size: smaller;
}
</style>
<dl class="meeting_properties">
<dt>{{ 'Course'|get_lang }}</dt>
<dd>
{% if meeting.course %}
<a href="{{ meeting.course.course_public_url }}">
{{ meeting.course.title }}
</a>
{% else %}
-
{% endif %}
</dd>
<dt>{{ 'Session'|get_lang }}</dt>
<dd>{{ meeting.session ? meeting.session.name : '-' }}</dd>
<dt>{{ 'Status'|get_lang }}</dt>
<dd>{{ meeting.status }}</dd>
<dt>{{ 'Topic'|get_lang }}</dt>
<dd>{{ meeting.topic }}</dd>
<dt>{{ 'Agenda'|get_lang }}</dt>
<dd>{{ meeting.agenda| nl2br }}</dd>
<p>{{ meeting.typeName }} {{ meeting.id }} ({{ meeting.statusName }})</p>
<h2>{{ meeting.topic }}</h2>
{% if meeting.agenda %}
<blockquote>{{ meeting.agenda| nl2br }}</blockquote>
{% endif %}
<dt>{{ 'Type'|get_lang }}</dt>
<dd>{{meeting.typeName}}</dd>
{% if meeting.type == 2 or meeting.type == 8 %}
<dl class="meeting_properties">
<dt>{{ 'StartTime'|get_lang }}</dt>
<dd>{{ meeting.formattedStartTime }}</dd>
<dt>{{ 'Duration'|get_lang }}</dt>
<dd>{{ meeting.formattedDuration }}</dd>
</dl>
{% endif %}
{% if isConferenceManager %}
<dt>{{ 'StartURLNotToBeShared'|get_lang }}</dt>
<dd><a class="btn" href="{{meeting.start_url}}">Start</a></dd>
<dt>{{ 'JoinURLToSendToParticipants'|get_lang }}</dt>
<dd>{{meeting.join_url}}</dd>
<!-- {{ meeting.settings| var_dump }} -->
{% if isConferenceManager and meeting.status == 'waiting' %}
<p>
<a class="btn" href="{{ meeting.start_url }}">
{{ 'StartMeeting'|get_lang }}
</a>
</p>
{% endif %}
{% else %}
{% if currentUserJoinURL %}
<p>
<a href="{{ currentUserJoinURL }}">
{{ 'JoinMeeting'|get_lang }}
</a>
</p>
{% endif %}
<dt>{{ 'JoinURL'|get_lang }}</dt>
<dd><a href="{{meeting.join_url}}">{{meeting.join_url}}</a></dd>
{% if meeting.settings.approval_type == 2 %}
<p>
<label>
{{ 'JoinURLToShare'|get_lang }}
<input readonly value="{{ meeting.join_url }}"/>
</label>
</p>
{% endif %}
{% endif %}
</dl>
{% if recordings %}
<h3>{{ 'Recordings'|get_lang }}</h3>
<ul>
{% for recording in recordings %}
<li>
<a href="{{recording.share_url}}">{{recording.share_url}}</a>
</li>
{% if instances %}
<h3>{{ 'InstancesAndRecordings'|get_lang }}</h3>
{% for instance in instances %}
<div>
<h4>{{ instance.start_time }} ({{ instance.recordings.duration }} minutes)</h4>
<a href="{{ instance.recordings.share_url }}">
{{ instance.recordings.recording_count }} recordings
{{ instance.deleteRecordingsForm }}
</a>
<table class="table">
{% for file in instance.recordings.recording_files %}
<tr>
<th>{{ file.file_type }}<th>
<td>
<a href="{{file.play_url}}" target="_blank">
Play {{file.recording_type}}
</a>
</td>
<td class="right">
<a href="{{file.download_url}}">Download {{ file.file_size }} bytes</a>
</td>
<td>
{{ file.copyToCourseForm }}
</td>
</tr>
{% endfor %}
<tr>
<th colspan="4" class="right">
{{ 'TotalSize'|get_lang }} {{ instance.recordings.total_size }} bytes
</th>
<td>
{{ instance.copyAllRecordingsToCourseForm }}
</td>
</tr>
</table>
{% if instance.participants %}
<h4>{{ 'InstanceParticipants'|get_lang }}</h4>
<ul>
{% for participant in instance.participants %}
<li>
{{ participant.name }}
</li>
{% endfor %}
</ul>
</ul>
{% endif %}
</div>
{% endfor %}
{% endif %}
{% if participants %}
<h3>{{ 'Participants'|get_lang }}</h3>
{{ participants| var_dump }}
<ul>
{% for participant in participants %}
<li>
<a href="{{recording.share_url}}">{{recording.share_url}}</a>
</li>
{% if registrants and isConferenceManager %}
<script>
function copyJoinURL(event, url) {
event.target.textContent = '{{ 'CopyingJoinURL'|get_lang|escape }}';
navigator.clipboard.writeText(url).then(function() {
event.target.textContent = '{{ 'JoinURLCopied'|get_lang|escape }}';
}, function() {
event.target.textContent = '{{ 'CouldNotCopyJoinURL'|get_lang|escape }}' + ' ' + url;
});
}
</script>
<table class="table">
<tr>
<th>{{ 'RegisteredUsers'|get_lang }}</th>
<th>{{ 'JoinURL'|get_lang }}</th>
</tr>
{% for registrant in registrants %}
<tr>
<td>
{{ registrant.fullName }}
</td>
<td>
<a onclick="copyJoinURL(event, '{{ registrant.join_url }}')">
{{ 'CopyJoinURL'|get_lang }}
</a>
</td>
</tr>
{% endfor %}
</ul>
</table>
{% else %}
<p>
{{ 'JoinURLToSendToParticipants'|get_lang }}
{{meeting.join_url}}
</p>
{% endif %}
{% if isConferenceManager %}
{{ editMeetingForm }}
{{ deleteMeetingForm }}
{% if enableParticipantRegistration %}
{{ registerParticipantForm }}
{% endif %}
{% endif %}

@ -39,6 +39,6 @@
<!-- p>No scheduled meeting currently</p -->
{% endif %}
{% if scheduleMeetingForm %}
<h3>{{ 'ScheduleMeeting'|get_lang }}</h3>
<h3>{{ 'ScheduleAMeeting'|get_lang }}</h3>
{{ scheduleMeetingForm }}
{% endif %}
Loading…
Cancel
Save