Plugin: Zoom: Allow webinars - refs BT#19479

pull/4160/head
Angel Fernando Quiroz Campos 4 years ago
parent 2a65680086
commit caef076dcf
  1. 33
      main/calendar/agenda_js.php
  2. 38
      main/inc/lib/agenda.lib.php
  3. 64
      plugin/zoom/Entity/Meeting.php
  4. 6
      plugin/zoom/Entity/Registrant.php
  5. 135
      plugin/zoom/Entity/Webinar.php
  6. 35
      plugin/zoom/README.md
  7. 61
      plugin/zoom/calendar.ajax.php
  8. 47
      plugin/zoom/calendar.php
  9. 5
      plugin/zoom/endpoint.php
  10. 18
      plugin/zoom/lang/english.php
  11. 18
      plugin/zoom/lang/spanish.php
  12. 26
      plugin/zoom/lib/API/FollowUpUsers.php
  13. 8
      plugin/zoom/lib/API/Meeting.php
  14. 12
      plugin/zoom/lib/API/MeetingInfoGet.php
  15. 95
      plugin/zoom/lib/API/MeetingRegistrant.php
  16. 22
      plugin/zoom/lib/API/Ocurrence.php
  17. 23
      plugin/zoom/lib/API/QuestionAndAnswer.php
  18. 103
      plugin/zoom/lib/API/RegistrantSchema.php
  19. 9
      plugin/zoom/lib/API/WebinarRegistrantSchema.php
  20. 143
      plugin/zoom/lib/API/WebinarSchema.php
  21. 201
      plugin/zoom/lib/API/WebinarSettings.php
  22. 467
      plugin/zoom/lib/ZoomPlugin.php
  23. 11
      plugin/zoom/meeting.php
  24. 117
      plugin/zoom/subscription.php
  25. 312
      plugin/zoom/view/calendar.tpl
  26. 25
      plugin/zoom/view/meeting.tpl
  27. 39
      plugin/zoom/view/meeting_details.tpl
  28. 5
      plugin/zoom/view/meetings.tpl
  29. 4
      plugin/zoom/view/start.tpl
  30. 3
      plugin/zoom/view/subscription.tpl

@ -92,16 +92,11 @@ switch ($type) {
if (api_is_anonymous()) { if (api_is_anonymous()) {
api_not_allowed(true); api_not_allowed(true);
} }
$extra_field_data = UserManager::get_extra_user_data_by_field( $googleCalendarUrl = Agenda::returnGoogleCalendarUrl(api_get_user_id());
api_get_user_id(),
'google_calendar_url' if (!empty($googleCalendarUrl)) {
);
if (!empty($extra_field_data) &&
isset($extra_field_data['google_calendar_url']) &&
!empty($extra_field_data['google_calendar_url'])
) {
$tpl->assign('use_google_calendar', 1); $tpl->assign('use_google_calendar', 1);
$tpl->assign('google_calendar_url', $extra_field_data['google_calendar_url']); $tpl->assign('google_calendar_url', $googleCalendarUrl);
} }
$this_section = SECTION_MYAGENDA; $this_section = SECTION_MYAGENDA;
if (!api_is_anonymous()) { if (!api_is_anonymous()) {
@ -312,23 +307,11 @@ $form->addHtml('</div>');
$tpl->assign('form_add', $form->returnForm()); $tpl->assign('form_add', $form->returnForm());
$tpl->assign('legend_list', api_get_configuration_value('agenda_legend')); $tpl->assign('legend_list', api_get_configuration_value('agenda_legend'));
$onHoverInfo = api_get_configuration_value('agenda_on_hover_info'); $onHoverInfo = Agenda::returnOnHoverInfo();
if (!empty($onHoverInfo)) { $tpl->assign('on_hover_info', $onHoverInfo);
$options = $onHoverInfo['options'];
} else { $extraSettings = Agenda::returnFullCalendarExtraSettings();
$options = [
'comment' => true,
'description' => true,
];
}
$tpl->assign('on_hover_info', $options);
$settings = api_get_configuration_value('fullcalendar_settings');
$extraSettings = '';
if (!empty($settings) && isset($settings['settings']) && !empty($settings['settings'])) {
$encoded = json_encode($settings['settings']);
$extraSettings = substr($encoded, 1, -1).',';
}
$tpl->assign('fullcalendar_settings', $extraSettings); $tpl->assign('fullcalendar_settings', $extraSettings);
$templateName = $tpl->get_template('agenda/month.tpl'); $templateName = $tpl->get_template('agenda/month.tpl');

@ -4591,6 +4591,44 @@ class Agenda
});'; });';
} }
public static function returnGoogleCalendarUrl(int $userId): ?string
{
$extraFieldInfo = UserManager::get_extra_user_data_by_field($userId, 'google_calendar_url');
if (empty($extraFieldInfo) || empty($extraFieldInfo['google_calendar_url'])) {
return null;
}
return $extraFieldInfo['google_calendar_url'];
}
public static function returnFullCalendarExtraSettings(): ?string
{
$settings = api_get_configuration_value('fullcalendar_settings');
if (empty($settings) || empty($settings['settings'])) {
return null;
}
$encoded = json_encode($settings['settings']);
return substr($encoded, 1, -1).',';
}
public static function returnOnHoverInfo()
{
$onHoverInfo = api_get_configuration_value('agenda_on_hover_info');
if (!empty($onHoverInfo)) {
return $onHoverInfo['options'];
}
return [
'comment' => true,
'description' => true,
];
}
private function editReminders(int $eventId, array $reminderList = []) private function editReminders(int $eventId, array $reminderList = [])
{ {
if (false === api_get_configuration_value('agenda_reminders')) { if (false === api_get_configuration_value('agenda_reminders')) {

@ -35,6 +35,9 @@ use Exception;
* } * }
* ) * )
* @ORM\HasLifecycleCallbacks * @ORM\HasLifecycleCallbacks
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="string")
* @ORM\DiscriminatorMap({"meeting" = "Chamilo\PluginBundle\Zoom\Meeting", "webinar" = "Chamilo\PluginBundle\Zoom\Webinar"})
*/ */
class Meeting class Meeting
{ {
@ -151,6 +154,13 @@ class Meeting
*/ */
protected $recordings; protected $recordings;
/**
* @var string|null
*
* @ORM\Column(type="string", name="account_email", nullable=true)
*/
protected $accountEmail;
public function __construct() public function __construct()
{ {
$this->registrants = new ArrayCollection(); $this->registrants = new ArrayCollection();
@ -498,12 +508,9 @@ class Meeting
|| MeetingInfoGet::TYPE_RECURRING_WITH_FIXED_TIME === $this->meetingInfoGet->type; || MeetingInfoGet::TYPE_RECURRING_WITH_FIXED_TIME === $this->meetingInfoGet->type;
} }
/** public function requiresRegistration(): bool
* @return bool
*/
public function requiresRegistration()
{ {
return MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE === $this->meetingInfoGet->settings->approval_type; return true; //MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE === $this->meetingInfoGet->settings->approval_type;
/*return /*return
MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $this->meetingInfoGet->settings->approval_type;*/ MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $this->meetingInfoGet->settings->approval_type;*/
} }
@ -516,21 +523,7 @@ class Meeting
return \ZoomPlugin::RECORDING_TYPE_NONE !== $this->meetingInfoGet->settings->auto_recording; return \ZoomPlugin::RECORDING_TYPE_NONE !== $this->meetingInfoGet->settings->auto_recording;
} }
/** public function getRegistrantByUser(User $user): ?Registrant
* @param User $user
*
* @return bool
*/
public function hasRegisteredUser($user)
{
return $this->getRegistrants()->exists(
function (int $key, Registrant $registrantEntity) use (&$user) {
return $registrantEntity->getUser() === $user;
}
);
}
public function getRegistrant(User $user): ?Registrant
{ {
$criteria = Criteria::create() $criteria = Criteria::create()
->where( ->where(
@ -549,7 +542,7 @@ class Meeting
*/ */
public function getIntroduction() public function getIntroduction()
{ {
$introduction = sprintf('<h1>%s</h1>', $this->meetingInfoGet->topic).PHP_EOL; $introduction = sprintf('<h1>%s</h1>', $this->getTopic()).PHP_EOL;
if (!$this->isGlobalMeeting()) { if (!$this->isGlobalMeeting()) {
if (!empty($this->formattedStartTime)) { if (!empty($this->formattedStartTime)) {
$introduction .= $this->formattedStartTime; $introduction .= $this->formattedStartTime;
@ -568,8 +561,9 @@ class Meeting
$introduction .= sprintf('<p class="main">%s (%s)</p>', $this->course, $this->session).PHP_EOL; $introduction .= sprintf('<p class="main">%s (%s)</p>', $this->course, $this->session).PHP_EOL;
} }
} }
if (!empty($this->meetingInfoGet->agenda)) {
$introduction .= sprintf('<p>%s</p>', $this->meetingInfoGet->agenda).PHP_EOL; if (!empty($this->getAgenda())) {
$introduction .= sprintf('<p>%s</p>', $this->getAgenda()).PHP_EOL;
} }
return $introduction; return $introduction;
@ -587,6 +581,18 @@ class Meeting
return $this; return $this;
} }
public function getAccountEmail(): ?string
{
return $this->accountEmail;
}
public function setAccountEmail(?string $accountEmail): self
{
$this->accountEmail = $accountEmail;
return $this;
}
public function getReasonToSignAttendance(): ?string public function getReasonToSignAttendance(): ?string
{ {
return $this->reasonToSignAttendance; return $this->reasonToSignAttendance;
@ -599,10 +605,20 @@ class Meeting
return $this; return $this;
} }
public function getTopic(): string
{
return $this->meetingInfoGet->topic;
}
public function getAgenda(): ?string
{
return $this->meetingInfoGet->agenda;
}
/** /**
* @throws Exception on unexpected start_time or duration * @throws Exception on unexpected start_time or duration
*/ */
private function initializeDisplayableProperties() protected function initializeDisplayableProperties()
{ {
$zoomPlugin = new \ZoomPlugin(); $zoomPlugin = new \ZoomPlugin();

@ -219,13 +219,9 @@ class Registrant
} }
/** /**
* @param MeetingRegistrant $meetingRegistrant
*
* @throws Exception * @throws Exception
*
* @return $this
*/ */
public function setMeetingRegistrant($meetingRegistrant) public function setMeetingRegistrant(API\RegistrantSchema $meetingRegistrant): Registrant
{ {
$this->meetingRegistrant = $meetingRegistrant; $this->meetingRegistrant = $meetingRegistrant;
$this->computeFullName(); $this->computeFullName();

@ -0,0 +1,135 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\PluginBundle\Zoom\API\WebinarSchema;
use Chamilo\PluginBundle\Zoom\API\WebinarSettings;
use DateInterval;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use ZoomPlugin;
/**
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks
*/
class Webinar extends Meeting
{
/**
* @var string
*
* @ORM\Column(name="webinar_schema_json", type="text", nullable=true)
*/
protected $webinarSchemaJson;
/**
* @var WebinarSchema
*/
protected $webinarSchema;
public function preFlush()
{
if (null !== $this->webinarSchema) {
$this->webinarSchemaJson = json_encode($this->webinarSchema);
}
}
public function postLoad()
{
if (null !== $this->webinarSchemaJson) {
$this->webinarSchema = WebinarSchema::fromJson($this->webinarSchemaJson);
}
$this->initializeDisplayableProperties();
}
/**
* @throws Exception
*/
public function setWebinarSchema(WebinarSchema $webinarSchema): Webinar
{
if (null === $this->meetingId) {
$this->meetingId = $webinarSchema->id;
} elseif ($this->meetingId != $webinarSchema->id) {
throw new Exception('the Meeting identifier differs from the MeetingInfoGet identifier');
}
$this->webinarSchema = $webinarSchema;
$this->initializeDisplayableProperties();
return $this;
}
public function getWebinarSchema(): WebinarSchema
{
return $this->webinarSchema;
}
public function hasCloudAutoRecordingEnabled(): bool
{
return $this->webinarSchema->settings->auto_recording !== ZoomPlugin::RECORDING_TYPE_NONE;
}
public function requiresDateAndDuration(): bool
{
return WebinarSchema::TYPE_WEBINAR == $this->webinarSchema->type;
}
public function requiresRegistration(): bool
{
return in_array(
$this->webinarSchema->settings->approval_type,
[
WebinarSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE,
WebinarSettings::APPROVAL_TYPE_MANUALLY_APPROVE,
]
);
}
public function getTopic(): string
{
return $this->webinarSchema->topic;
}
public function getAgenda(): ?string
{
return $this->webinarSchema->agenda;
}
protected function initializeDisplayableProperties()
{
$zoomPlugin = ZoomPlugin::create();
$namedTypes = [
WebinarSchema::TYPE_WEBINAR => $zoomPlugin->get_lang('Webinar'),
WebinarSchema::TYPE_RECURRING_NO_FIXED_TIME => $zoomPlugin->get_lang('RecurringWithNoFixedTime'),
WebinarSchema::TYPE_RECURRING_FIXED_TIME => $zoomPlugin->get_lang('RecurringWithFixedTime'),
];
$this->typeName = $namedTypes[$this->webinarSchema->type];
if ($this->webinarSchema->start_time) {
$this->startDateTime = new DateTime(
$this->webinarSchema->start_time,
new \DateTimeZone(api_get_timezone())
);
$this->formattedStartTime = $this->startDateTime->format('Y-m-d H:i');
}
if ($this->webinarSchema->duration) {
$now = new DateTime();
$later = new DateTime();
$later->add(
new DateInterval('PT'.$this->webinarSchema->duration.'M')
);
$this->durationInterval = $now->diff($later);
$this->formattedDuration = $this->durationInterval->format(
$zoomPlugin->get_lang('DurationFormat')
);
}
}
}

@ -25,10 +25,14 @@ required to authenticate with JWT. To get them, create a JWT App:
- End Meeting - End Meeting
- Participant/Host joined meeting - Participant/Host joined meeting
- Participant/Host left meeting - Participant/Host left meeting
- Start Webinar
- End Webinar
- Participant/Host joined webinar
- Participant/Host left webinar
- All Recordings have completed - All Recordings have completed
- Recording transcript files have completed - Recording transcript files have completed
Then click on Done then on Save and copy your Verification Token to the field below. Then click on Done then on Save and copy your Verification Token to the field below.
10. click on Continue 10. click on Continue
## Changelog ## Changelog
@ -38,29 +42,31 @@ required to authenticate with JWT. To get them, create a JWT App:
Added signed attendance to allow you to configure an attendance sheet where participants register their signature. The Added signed attendance to allow you to configure an attendance sheet where participants register their signature. The
signed attendance functionality is similar to that found in the Exercise Signature plugin but does not reuse it. signed attendance functionality is similar to that found in the Exercise Signature plugin but does not reuse it.
## Meetings ## Meetings - Webinars
A **meeting** or **webinar** can be linked to a local **user** and/or a local **course**/**session**:
A **meeting** can be linked to a local **user** and/or a local **course**/**session**: * a meeting/webinar with a course is a _course meeting/webinar_;
* a meeting/webinar with a user and no course is a _user meeting/webinar_;
* a meeting/webinar with no course nor user is a _global meeting/webinar_.
* a meeting with a course is a _course meeting_; A webinar only can be creadted when your Zoom account has a plan with the webinars feature.
* a meeting with a user and no course is a _user meeting_;
* a meeting with no course nor user is a _global meeting_.
## Registrants ## Registrants
A **registrant** is the registration of a local user to a meeting. A **registrant** is the registration of a local user to a meeting/webinar.
Users do not register themselves to meetings. Users do not register themselves to meetings.
* They are registered to a course meeting by the course manager. * They are registered to a course meeting/webinar by the course manager.
* They are registered to a user meeting by that user. * They are registered to a user meeting/webinar by that user.
* They are registered automatically to the global meeting, when they enter it. * They are registered automatically to the global meeting/webinar, when they enter it.
## Recordings ## Recordings
A **recording** is the list of files created during a past meeting instance. A **recording** is the list of files created during a past meeting/webinar instance.
Course meeting files can be copied to the course by the course manager. Course meeting/webinar files can be copied to the course by the course manager.
# Required Zoom user account # Required Zoom user account
@ -68,7 +74,8 @@ 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. For a non-paying Zoom user, this plugin still works but participants will join anonymously.
The user that starts the meeting will be identified as the Zoom account that is defined in the plugin. Socreate a generic account that works for all the users that start meetings. The user that starts the meeting/webinar will be identified as the Zoom account that is defined in the plugin. Socreate
a generic account that works for all the users that start meetings.
# Upgrade database to v0.4 # Upgrade database to v0.4

@ -0,0 +1,61 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\Meeting;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
$cidReset = true;
require_once __DIR__.'/config.php';
api_protect_admin_script();
$request = HttpRequest::createFromGlobals();
$plugin = ZoomPlugin::create();
$user = api_get_user_entity(api_get_user_id());
$action = $request->get('a');
if ($action == 'get_events') {
$startDate = $request->query->get('start');
$endDate = $request->query->get('end');
$startDate = api_get_utc_datetime($startDate, true, true);
$endDate = api_get_utc_datetime($endDate, true, true);
$meetings = $plugin
->getMeetingRepository()
->periodMeetings($startDate, $endDate);
$meetingsAsEvents = array_map(
function (Meeting $meeting) {
$meetingInfo = $meeting->getMeetingInfoGet();
$endDate = new DateTime($meeting->formattedStartTime);
$endDate->sub($meeting->durationInterval);
return [
'id' => 'meeting_'.$meeting->getId(),
'title' => $meetingInfo->topic,
'editable' => false,
'start' => $meeting->formattedStartTime,
'start_date_localtime' => $meeting->formattedStartTime,
'end' => $endDate->format('Y-m-d H:i'),
'end_date_localtime' => $endDate->format('Y-m-d H:i'),
'duration' => $meeting->formattedDuration,
'description' => $meetingInfo->agenda,
'allDay' => false,
'accountEmail' => $meeting->getAccountEmail(),
];
},
$meetings
);
$response = JsonResponse::create($meetingsAsEvents);
$response->send();
}

@ -0,0 +1,47 @@
<?php
/* For license terms, see /license.txt */
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
$cidReset = true;
require_once __DIR__.'/config.php';
api_protect_admin_script();
$plugin = ZoomPlugin::create();
$toolName = $plugin->get_lang('ZoomVideoConferences');
$defaultView = api_get_setting('default_calendar_view');
if (empty($defaultView)) {
$defaultView = 'month';
}
$regionValue = api_get_language_isocode();
$htmlHeadXtra[] = api_get_asset('qtip2/jquery.qtip.min.js');
$htmlHeadXtra[] = api_get_asset('fullcalendar/dist/fullcalendar.js');
$htmlHeadXtra[] = api_get_asset('fullcalendar/dist/locale-all.js');
$htmlHeadXtra[] = api_get_css_asset('fullcalendar/dist/fullcalendar.min.css');
$htmlHeadXtra[] = api_get_css_asset('qtip2/jquery.qtip.min.css');
$tpl = new Template($toolName);
$tpl->assign('web_agenda_ajax_url', 'calendar.ajax.php?sec_token='.Security::get_token());
$tpl->assign('default_view', $defaultView);
$tpl->assign('region_value', 'en' === $regionValue ? 'en-GB' : $regionValue);
$onHoverInfo = Agenda::returnOnHoverInfo();
$tpl->assign('on_hover_info', $onHoverInfo);
$extraSettings = Agenda::returnFullCalendarExtraSettings();
$tpl->assign('fullcalendar_settings', $extraSettings);
$content = $tpl->fetch('zoom/view/calendar.tpl');
$tpl->assign('actions', $plugin->getToolbar());
$tpl->assign('content', $content);
$tpl->display_one_col_template();

@ -78,6 +78,11 @@ switch ($objectType) {
} }
$em->flush(); $em->flush();
break; break;
case 'webinar':
$meeting->addActivity($activity);
$em->persist($meeting);
$em->flush();
break;
case 'recording': case 'recording':
$recordingRepository = $em->getRepository(Recording::class); $recordingRepository = $em->getRepository(Recording::class);

@ -15,6 +15,8 @@ $strings['enableGlobalConference'] = 'Enable global conference';
$strings['enableGlobalConferencePerUser'] = 'Enable global conference per user'; $strings['enableGlobalConferencePerUser'] = 'Enable global conference per user';
$strings['globalConferenceAllowRoles'] = "Global conference link only visible for these user roles"; $strings['globalConferenceAllowRoles'] = "Global conference link only visible for these user roles";
$strings['globalConferencePerUserAllowRoles'] = "Global conference per user link only visible for these user roles"; $strings['globalConferencePerUserAllowRoles'] = "Global conference per user link only visible for these user roles";
$strings['accountSelector'] = 'Account selector';
$strings['accountSelector_help'] = 'It allows you to declare the emails of the different accounts with whom you want to open the Zoom videos. Separated by semicolons (account_one@example.come;account_two@exaple.com).';
$strings['tool_enable_help'] = "Choose whether you want to enable the Zoom videoconference tool. $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 : Once enabled, it will show as an additional course tool in all courses' homepage :
@ -43,6 +45,10 @@ and add these event types:
<br/>- End Meeting <br/>- End Meeting
<br/>- Participant/Host joined meeting <br/>- Participant/Host joined meeting
<br/>- Participant/Host left meeting <br/>- Participant/Host left meeting
<br/>- Start Webinar
<br/>- End Webinar
<br/>- Participant/Host joined webinar
<br/>- Participant/Host left webinar
<br/>- All Recordings have completed <br/>- All Recordings have completed
<br/>- Recording transcript files have completed <br/>- Recording transcript files have completed
<br/>then click on <em>Done</em> then on <em>Save</em> <br/>then click on <em>Done</em> then on <em>Save</em>
@ -147,3 +153,15 @@ $strings['ReasonToSign'] = 'Reason to sign attendance';
$strings['ConferenceWithAttendance'] = "Conference with attendance sign"; $strings['ConferenceWithAttendance'] = "Conference with attendance sign";
$strings['Sign'] = "Sign"; $strings['Sign'] = "Sign";
$strings['Signature'] = "Signature"; $strings['Signature'] = "Signature";
$strings['Meeting'] = "Meeting";
$strings['Webinar'] = "Webinar";
$strings['AudienceType'] = 'Audience type';
$strings['AccountEmail'] = 'Account email';
$strings['NewWebinarCreated'] = "New webinar created";
$strings['UpdateWebinar'] = 'Update webinar';
$strings['WebinarUpdated'] = "Webinar updated";
$strings['DeleteWebinar'] = "Delete webinar";
$strings['WebinarDeleted'] = "Webinar deleted";
$strings['UrlForSelfRegistration'] = "URL for self registration";
$strings['RegisterMeToConference'] = "Register me to conference";
$strings['UnregisterMeToConference'] = "Unregister me to conference";

@ -14,6 +14,8 @@ $strings['enableGlobalConference'] = "Activar las conferencias globales";
$strings['enableGlobalConferencePerUser'] = "Activar las conferencias globales por usuario"; $strings['enableGlobalConferencePerUser'] = "Activar las conferencias globales por usuario";
$strings['globalConferenceAllowRoles'] = "Visibilidad del enlace global de videoconferencia para los perfiles siguientes"; $strings['globalConferenceAllowRoles'] = "Visibilidad del enlace global de videoconferencia para los perfiles siguientes";
$strings['globalConferencePerUserAllowRoles'] = "Visibilidad del enlace global de videoconferencia por usuario para los perfiles siguientes"; $strings['globalConferencePerUserAllowRoles'] = "Visibilidad del enlace global de videoconferencia por usuario para los perfiles siguientes";
$strings['accountSelector'] = 'Selector de cuentas';
$strings['accountSelector_help'] = 'Te permite declarar los correos de las diferentes cuentas con las que quieres abrir los videos de Zoom. Separados por punto y coma (account_one@example.come;account_two@exaple.com).';
$strings['tool_enable_help'] = "Escoja si desea activar la herramienta Zoom. $strings['tool_enable_help'] = "Escoja si desea activar la herramienta Zoom.
Una vez activada, aparecerá en las páginas principales de todos los cursos. Los profesores podrán Una vez activada, aparecerá en las páginas principales de todos los cursos. Los profesores podrán
@ -42,6 +44,10 @@ y agrega este tipo de eventos:
<br/>- End Meeting <br/>- End Meeting
<br/>- Participant/Host joined meeting <br/>- Participant/Host joined meeting
<br/>- Participant/Host left meeting <br/>- Participant/Host left meeting
<br/>- Start Webinar
<br/>- End Webinar
<br/>- Participant/Host joined webinar
<br/>- Participant/Host left webinar
<br/>- All Recordings have completed <br/>- All Recordings have completed
<br/>- Recording transcript files have completed <br/>- Recording transcript files have completed
<br/>de clic en <em>Done</em> y luego en <em>Save</em> <br/>de clic en <em>Done</em> y luego en <em>Save</em>
@ -141,3 +147,15 @@ $strings['ReasonToSign'] = 'Razón para firmar asistencia';
$strings['ConferenceWithAttendance'] = "Conferencia con registro de asistencia"; $strings['ConferenceWithAttendance'] = "Conferencia con registro de asistencia";
$strings['Sign'] = "Firmar"; $strings['Sign'] = "Firmar";
$strings['Signature'] = "Firma"; $strings['Signature'] = "Firma";
$strings['Meeting'] = "Conferencia";
$strings['Webinar'] = "Seminario web";
$strings['AudienceType'] = 'Tipo de público';
$strings['AccountEmail'] = 'Correo electrónico de la cuenta';
$strings['NewWebinarCreated'] = "Nuevo seminario web creado";
$strings['UpdateWebinar'] = 'Actualizar seminario web';
$strings['WebinarUpdated'] = "Seminario web actualizado";
$strings['DeleteWebinar'] = "Borrar seminario web";
$strings['WebinarDeleted'] = "Seminario web borrado";
$strings['UrlForSelfRegistration'] = "URL para auto registro";
$strings['RegisterMeToConference'] = "Registrarme a la conferencia";
$strings['UnregisterMeToConference'] = "Cancelar registro a la conferencia";

@ -0,0 +1,26 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class FollowUpUsers
{
use JsonDeserializableTrait;
/**
* @var bool
*/
public $enable;
/**
* @var
*/
public $type;
public function itemClass($propertyName)
{
throw new Exception("No such array property $propertyName");
}
}

@ -60,9 +60,13 @@ class Meeting
* *
* @return MeetingInfoGet meeting * @return MeetingInfoGet meeting
*/ */
public function create() public function create($userId = null)
{ {
return MeetingInfoGet::fromJson(Client::getInstance()->send('POST', 'users/me/meetings', [], $this)); $userId = empty($userId) ? 'me' : $userId;
return MeetingInfoGet::fromJson(
Client::getInstance()->send('POST', "users/$userId/meetings", [], $this)
);
} }
/** /**

@ -77,17 +77,17 @@ class MeetingInfoGet extends MeetingInfo
/** /**
* Adds a registrant to the meeting. * Adds a registrant to the meeting.
* *
* @param MeetingRegistrant $registrant with at least 'email' and 'first_name'. * @param RegistrantSchema $registrant with at least 'email' and 'first_name'.
* 'last_name' will also be recorded by Zoom. * 'last_name' will also be recorded by Zoom.
* Other properties remain ignored, or not returned by Zoom * Other properties remain ignored, or not returned by Zoom
* (at least while using profile "Pro") * (at least while using profile "Pro")
* @param string $occurrenceIds separated by comma * @param string $occurrenceIds separated by comma
* *
* @throws Exception * @throws Exception
* *
* @return CreatedRegistration with unique join_url and registrant_id properties * @return CreatedRegistration with unique join_url and registrant_id properties
*/ */
public function addRegistrant($registrant, $occurrenceIds = '') public function addRegistrant(RegistrantSchema $registrant, string $occurrenceIds = ''): CreatedRegistration
{ {
return CreatedRegistration::fromJson( return CreatedRegistration::fromJson(
Client::getInstance()->send( Client::getInstance()->send(

@ -4,102 +4,19 @@
namespace Chamilo\PluginBundle\Zoom\API; namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/** /**
* Class MeetingRegistrant. * Class Registrant.
*
* Structure of the information to send the server in order to register someone to a meeting. * Structure of the information to send the server in order to register someone to a meeting.
*/ */
class MeetingRegistrant class MeetingRegistrant extends RegistrantSchema
{ {
use JsonDeserializableTrait; public $auto_approve;
/** @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_questions;
/**
* MeetingRegistrant constructor.
*/
public function __construct() public function __construct()
{ {
$this->custom_questions = []; parent::__construct();
}
/**
* @param string $email
* @param string $firstName
* @param string $lastName
*
* @return MeetingRegistrant
*/
public static function fromEmailAndFirstName($email, $firstName, $lastName = null)
{
$instance = new static();
$instance->first_name = $firstName;
$instance->email = $email;
if (null !== $lastName) {
$instance->last_name = $lastName;
}
return $instance; $this->auto_approve = true;
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('custom_questions' === $propertyName) {
return CustomQuestion::class;
}
throw new Exception("no such array property $propertyName");
} }
} }

@ -0,0 +1,22 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class Ocurrence
{
use JsonDeserializableTrait;
public $occurrence_id;
public $start_time;
public $duration;
public $status;
public function itemClass($propertyName)
{
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,23 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class QuestionAndAnswer
{
use JsonDeserializableTrait;
public $enable;
public $allow_anonymous_questions;
public $answer_questions;
public $attendees_can_upvote;
public $attendees_can_comment;
public function itemClass($propertyName)
{
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,103 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
abstract class RegistrantSchema
{
use JsonDeserializableTrait;
/** @var string */
public $email;
/**
* @var string
*/
public $status;
/** @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_questions;
/**
* @var string
*/
public $language;
/**
* MeetingRegistrant constructor.
*/
public function __construct()
{
$this->status = 'approved';
$this->custom_questions = [];
}
public static function fromEmailAndFirstName(string $email, string $firstName, string $lastName = null): RegistrantSchema
{
$instance = new static();
$instance->first_name = $firstName;
$instance->email = $email;
if (null !== $lastName) {
$instance->last_name = $lastName;
}
return $instance;
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName): string
{
if ('custom_questions' === $propertyName) {
return CustomQuestion::class;
}
throw new Exception("no such array property $propertyName");
}
}

@ -0,0 +1,9 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
class WebinarRegistrantSchema extends RegistrantSchema
{
}

@ -0,0 +1,143 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
use stdClass;
class WebinarSchema
{
use JsonDeserializableTrait;
const TYPE_WEBINAR = 5;
const TYPE_RECURRING_NO_FIXED_TIME = 6;
const TYPE_RECURRING_FIXED_TIME = 9;
public $uuid;
public $id;
public $host_id;
public $host_email;
public $topic;
public $type;
public $start_time;
public $duration;
public $timezone;
public $agenda;
public $created_at;
public $start_url;
public $join_url;
public $registration_url;
public $password;
/**
* @var WebinarSettings
*/
public $settings;
public $registrants_confirmation_email;
/**
* @var array<int, TrackingField>
*/
public $tracking_fields;
public $recurrence;
public $template_id;
/**
* @var array<int, Ocurrence>
*/
public $ocurrences;
protected function __construct()
{
$this->tracking_fields = [];
$this->settings = new WebinarSettings();
$this->ocurrences = [];
}
public function itemClass($propertyName): string
{
if ('tracking_fields' === $propertyName) {
return TrackingField::class;
}
if ('ocurrences' === $propertyName) {
return Ocurrence::class;
}
throw new Exception("no such array property $propertyName");
}
public static function fromTopicAndType($topic, $type = self::TYPE_WEBINAR): WebinarSchema
{
$instance = new static();
$instance->topic = $topic;
$instance->type = $type;
return $instance;
}
/**
* @throws Exception
*/
public function create($userId = null): WebinarSchema
{
$client = Client::getInstance();
$userId = empty($userId) ? 'me' : $userId;
return self::fromJson(
$client->send('POST', "users/$userId/webinars", [], $this)
);
}
/**
* @throws Exception
*/
public function update()
{
Client::getInstance()->send('PATCH', 'webinars/'.$this->id, [], $this);
}
/**
* @throws Exception
*/
public function delete()
{
Client::getInstance()->send('DELETE', "webinars/$this->id");
}
/**
* @throws Exception
*/
public function addRegistrant(RegistrantSchema $registrant, string $ocurrenceIds = ''): CreatedRegistration
{
return CreatedRegistration::fromJson(
Client::getInstance()->send(
'POST',
"webinars/$this->id/registrants",
empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds],
$registrant
)
);
}
/**
* @throws Exception
*/
public function removeRegistrants(array $registrants, string $occurrenceIds = '')
{
if (empty($registrants)) {
return;
}
$requestBody = new stdClass();
$requestBody->action = 'cancel';
$requestBody->registrants = $registrants;
Client::getInstance()->send(
'PUT',
"webinars/$this->id/registrants/status",
empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds],
$requestBody
);
}
}

@ -0,0 +1,201 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
class WebinarSettings
{
use JsonDeserializableTrait;
const APPROVAL_TYPE_AUTOMATICALLY_APPROVE = 0;
const APPROVAL_TYPE_MANUALLY_APPROVE = 1;
const APPROVAL_TYPE_NO_REGISTRATION_REQUIRED = 2;
const REGISTRATION_TYPE_REGISTER_ONCE_ATTEND_ANY = 1;
const REGISTRATION_TYPE_REGISTER_EACH = 2;
const REGISTRATION_TYPE_REGISTER_ONCE_CHOOSE = 3;
/**
* @var bool
*/
public $host_video;
/**
* @var bool
*/
public $panelists_video;
/**
* @var int
*/
public $approval_type;
/**
* @var string
*/
public $audio;
/**
* @var string
*/
public $auto_recording;
/**
* @var bool
*/
public $enforce_login;
/**
* @var string
*/
public $enforce_login_domains;
/**
* @var string
*/
public $alternative_hosts;
/**
* @var bool
*/
public $close_registration;
/**
* @var bool
*/
public $show_share_button;
/**
* @var bool
*/
public $allow_multiple_devices;
/**
* @var bool
*/
public $practice_session;
/**
* @var bool
*/
public $hd_video;
/**
* @var object
*/
public $question_answer;
/**
* @var bool
*/
public $registrants_confirmation_email;
/**
* @var bool
*/
public $on_demand;
/**
* @var bool
*/
public $request_permission_to_unmute_participants;
/**
* @var array<int,string>
*/
public $global_dial_in_countries;
/**
* @var array<int,GlobalDialInNumber>
*/
public $global_dial_in_numbers;
/**
* @var string
*/
public $contact_name;
/**
* @var string
*/
public $contact_email;
/**
* @var int
*/
public $registrants_restrict_number;
/**
* @var bool
*/
public $registrants_email_notification;
/**
* @var bool
*/
public $post_webinar_survey;
/**
* @var bool
*/
public $meeting_authentication;
/**
* @var QuestionAndAnswer
*/
public $question_and_answer;
/**
* @var bool
*/
public $hd_video_for_attendees;
/**
* @var bool
*/
public $send_1080p_video_to_attendees;
/**
* @var string
*/
public $email_language;
/**
* @var bool
*/
public $panelists_invitation_email_notification;
/**
* @var FollowUpUsers
*/
public $attendees_and_panelists_reminder_email_notification;
/**
* @var FollowUpUsers
*/
public $follow_up_attendees_email_notification;
/**
* @var FollowUpUsers
*/
public $follow_up_absentees_email_notification;
/**
* @var int
*/
public $registration_type;
/**
* @var string
*/
public $auto;
/**
* @var string
*/
public $survey_url;
/**
* @var string
*/
public $authentication_option;
/**
* @var string
*/
public $authentication_domains;
/**
* @var string
*/
public $authentication_name;
public function __construct()
{
$this->global_dial_in_countries = [];
$this->global_dial_in_numbers = [];
$this->question_and_answer = new QuestionAndAnswer();
$this->attendees_and_panelists_reminder_email_notification = new FollowUpUsers();
$this->follow_up_absentees_email_notification = new FollowUpUsers();
$this->follow_up_attendees_email_notification = new FollowUpUsers();
}
public function itemClass($propertyName): string
{
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");
}
}

@ -11,6 +11,9 @@ use Chamilo\PluginBundle\Zoom\API\MeetingRegistrant;
use Chamilo\PluginBundle\Zoom\API\MeetingSettings; use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
use Chamilo\PluginBundle\Zoom\API\RecordingFile; use Chamilo\PluginBundle\Zoom\API\RecordingFile;
use Chamilo\PluginBundle\Zoom\API\RecordingList; use Chamilo\PluginBundle\Zoom\API\RecordingList;
use Chamilo\PluginBundle\Zoom\API\WebinarRegistrantSchema;
use Chamilo\PluginBundle\Zoom\API\WebinarSchema;
use Chamilo\PluginBundle\Zoom\API\WebinarSettings;
use Chamilo\PluginBundle\Zoom\Meeting; use Chamilo\PluginBundle\Zoom\Meeting;
use Chamilo\PluginBundle\Zoom\MeetingActivity; use Chamilo\PluginBundle\Zoom\MeetingActivity;
use Chamilo\PluginBundle\Zoom\MeetingRepository; use Chamilo\PluginBundle\Zoom\MeetingRepository;
@ -19,6 +22,7 @@ use Chamilo\PluginBundle\Zoom\RecordingRepository;
use Chamilo\PluginBundle\Zoom\Registrant; use Chamilo\PluginBundle\Zoom\Registrant;
use Chamilo\PluginBundle\Zoom\RegistrantRepository; use Chamilo\PluginBundle\Zoom\RegistrantRepository;
use Chamilo\PluginBundle\Zoom\Signature; use Chamilo\PluginBundle\Zoom\Signature;
use Chamilo\PluginBundle\Zoom\Webinar;
use Chamilo\UserBundle\Entity\User; use Chamilo\UserBundle\Entity\User;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\OptimisticLockException;
@ -75,6 +79,7 @@ class ZoomPlugin extends Plugin
], ],
'attributes' => ['multiple' => 'multiple'], 'attributes' => ['multiple' => 'multiple'],
], ],
'accountSelector' => 'text',
] ]
); );
@ -156,12 +161,12 @@ class ZoomPlugin extends Plugin
$items = []; $items = [];
foreach ($meetings as $registrant) { foreach ($meetings as $registrant) {
$meeting = $registrant->getMeeting(); $meeting = $registrant->getMeeting();
$meetingInfoGet = $meeting->getMeetingInfoGet();
$items[sprintf( $items[sprintf(
$this->get_lang('DateMeetingTitle'), $this->get_lang('DateMeetingTitle'),
$meeting->formattedStartTime, $meeting->formattedStartTime,
$meetingInfoGet->topic $meeting->getTopic()
)] = sprintf($linkTemplate, $meetingInfoGet->id); )] = sprintf($linkTemplate, $meeting->getMeetingId());
} }
return $items; return $items;
@ -204,6 +209,7 @@ class ZoomPlugin extends Plugin
(new SchemaTool($em))->createSchema( (new SchemaTool($em))->createSchema(
[ [
$em->getClassMetadata(Meeting::class), $em->getClassMetadata(Meeting::class),
$em->getClassMetadata(Webinar::class),
$em->getClassMetadata(MeetingActivity::class), $em->getClassMetadata(MeetingActivity::class),
$em->getClassMetadata(Recording::class), $em->getClassMetadata(Recording::class),
$em->getClassMetadata(Registrant::class), $em->getClassMetadata(Registrant::class),
@ -243,6 +249,7 @@ class ZoomPlugin extends Plugin
(new SchemaTool($em))->dropSchema( (new SchemaTool($em))->dropSchema(
[ [
$em->getClassMetadata(Meeting::class), $em->getClassMetadata(Meeting::class),
$em->getClassMetadata(Webinar::class),
$em->getClassMetadata(MeetingActivity::class), $em->getClassMetadata(MeetingActivity::class),
$em->getClassMetadata(Recording::class), $em->getClassMetadata(Recording::class),
$em->getClassMetadata(Registrant::class), $em->getClassMetadata(Registrant::class),
@ -389,6 +396,75 @@ class ZoomPlugin extends Plugin
return $form; return $form;
} }
/**
* @throws Exception
*/
public function getEditWebinarForm(Webinar $webinar): FormValidator
{
$schema = $webinar->getWebinarSchema();
$requiresDateAndDuration = $webinar->requiresDateAndDuration();
$form = new FormValidator('edit', 'post', $_SERVER['REQUEST_URI']);
$form->addHeader($this->get_lang('UpdateWebinar'));
$form->addText('topic', $this->get_lang('Topic'));
if ($requiresDateAndDuration) {
$startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
$durationNumeric = $form->addNumeric('duration', $this->get_lang('DurationInMinutes'));
$form->setRequired($startTimeDatePicker);
$form->setRequired($durationNumeric);
}
$form->addTextarea('agenda', get_lang('Agenda'), ['maxlength' => 2000]);
$form->addButtonUpdate(get_lang('Update'));
if ($form->validate()) {
$formValues = $form->exportValues();
if ($requiresDateAndDuration) {
$schema->start_time = (new DateTime($formValues['startTime']))
->format(DATE_ATOM);
$schema->timezone = date_default_timezone_get();
$schema->duration = (int) $formValues['duration'];
}
$schema->topic = $formValues['topic'];
$schema->agenda = $formValues['agenda'];
try {
$schema->update();
$webinar->setWebinarSchema($schema);
$em = Database::getManager();
$em->persist($webinar);
$em->flush();
Display::addFlash(
Display::return_message($this->get_lang('WebinarUpdated'), 'success')
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
}
$defaults = [
'topic' => $schema->topic,
'agenda' => $schema->agenda,
];
if ($requiresDateAndDuration) {
$defaults['startTime'] = $webinar->startDateTime->format('Y-m-d H:i');
$defaults['duration'] = $schema->duration;
}
$form->setDefaults($defaults);
return $form;
}
/** /**
* Generates a meeting delete form and deletes the meeting on validation. * Generates a meeting delete form and deletes the meeting on validation.
* *
@ -411,6 +487,19 @@ class ZoomPlugin extends Plugin
return $form; return $form;
} }
public function getDeleteWebinarForm(Webinar $webinar, string $returnURL): FormValidator
{
$id = $webinar->getMeetingId();
$form = new FormValidator('delete', 'post', api_get_self()."?meetingId=$id");
$form->addButtonDelete($this->get_lang('DeleteWebinar'));
if ($form->validate()) {
$this->deleteWebinar($webinar, $returnURL);
}
return $form;
}
/** /**
* @param Meeting $meeting * @param Meeting $meeting
* @param string $returnURL * @param string $returnURL
@ -442,6 +531,26 @@ class ZoomPlugin extends Plugin
} }
} }
public function deleteWebinar(Webinar $webinar, string $returnURL)
{
$em = Database::getManager();
try {
$webinar->getWebinarSchema()->delete();
$em->remove($webinar);
$em->flush();
Display::addFlash(
Display::return_message($this->get_lang('WebinarDeleted'), 'success')
);
api_location($returnURL);
} catch (Exception $exception) {
$this->handleException($exception);
}
}
/** /**
* @param Exception $exception * @param Exception $exception
*/ */
@ -476,6 +585,26 @@ class ZoomPlugin extends Plugin
$userIdSelect->setMultiple(true); $userIdSelect->setMultiple(true);
$form->addButtonSend($this->get_lang('UpdateRegisteredUserList')); $form->addButtonSend($this->get_lang('UpdateRegisteredUserList'));
$selfRegistrationUrl = api_get_path(WEB_PLUGIN_PATH)
.'zoom/subscription.php?meetingId='.$meeting->getMeetingId();
$form->addHtml(
'<div class="form-group"><div class="col-sm-8 col-sm-offset-2">
<hr style="margin-top: 0;">
<label for="frm-registration__txt-self-registration">'
.$this->get_lang('UrlForSelfRegistration').'</label>
<div class="input-group">
<input type="text" class="form-control" id="frm-registration__txt-self-registration" value="'
.$selfRegistrationUrl.'">
<span class="input-group-btn">
<button class="btn btn-default" type="button"
onclick="copyTextToClipBoard(\'frm-registration__txt-self-registration\');">'
.$this->get_lang('CopyTextToClipboard').'</button>
</span>
</div>
</div></div>'
);
$users = $meeting->getRegistrableUsers(); $users = $meeting->getRegistrableUsers();
foreach ($users as $user) { foreach ($users as $user) {
$userIdSelect->addOption( $userIdSelect->addOption(
@ -786,6 +915,17 @@ class ZoomPlugin extends Plugin
} }
$form = new FormValidator('scheduleMeetingForm', 'post', api_get_self().'?'.$extraUrl); $form = new FormValidator('scheduleMeetingForm', 'post', api_get_self().'?'.$extraUrl);
$form->addHeader($this->get_lang('ScheduleAMeeting')); $form->addHeader($this->get_lang('ScheduleAMeeting'));
$form->addSelect(
'conference_type',
$this->get_lang('ConferenceType'),
[
'meeting' => $this->get_lang('Meeting'),
'webinar' => $this->get_lang('Webinar'),
]
);
$form->addRule('conference_type', get_lang('ThisFieldIsRequired'), 'required');
$startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime')); $startTimeDatePicker = $form->addDateTimePicker('startTime', get_lang('StartTime'));
$form->setRequired($startTimeDatePicker); $form->setRequired($startTimeDatePicker);
@ -803,7 +943,7 @@ class ZoomPlugin extends Plugin
if (1 === count($options)) { if (1 === count($options)) {
$form->addHidden('type', key($options)); $form->addHidden('type', key($options));
} else { } else {
$form->addSelect('type', $this->get_lang('ConferenceType'), $options); $form->addSelect('type', $this->get_lang('AudienceType'), $options);
} }
} }
} else { } else {
@ -851,13 +991,20 @@ class ZoomPlugin extends Plugin
$form->addCheckBox('sign_attendance', $this->get_lang('SignAttendance'), get_lang('Yes')); $form->addCheckBox('sign_attendance', $this->get_lang('SignAttendance'), get_lang('Yes'));
$form->addTextarea('reason_to_sign', $this->get_lang('ReasonToSign'), ['rows' => 5]); $form->addTextarea('reason_to_sign', $this->get_lang('ReasonToSign'), ['rows' => 5]);
$accountEmails = $this->getAccountEmails();
if (!empty($accountEmails)) {
$form->addSelect('account_email', $this->get_lang('AccountEmail'), $accountEmails);
}
$form->addButtonCreate(get_lang('Save')); $form->addButtonCreate(get_lang('Save'));
if ($form->validate()) { if ($form->validate()) {
$formValues = $form->exportValues(); $formValues = $form->exportValues();
$type = $form->getSubmitValue('type'); $conferenceType = $formValues['conference_type'];
$password = substr(uniqid('z', true), 0, 10);
switch ($type) { switch ($formValues['type']) {
case 'everyone': case 'everyone':
$user = null; $user = null;
$group = null; $group = null;
@ -879,24 +1026,53 @@ class ZoomPlugin extends Plugin
break; break;
} }
try { $accountEmail = $formValues['account_email'] ?? null;
$newMeeting = $this->createScheduleMeeting( $accountEmail = $accountEmail && in_array($accountEmail, $accountEmails) ? $accountEmail : null;
$user,
$course,
$group,
$session,
new DateTime($form->getSubmitValue('startTime')),
$form->getSubmitValue('duration'),
$form->getSubmitValue('topic'),
$form->getSubmitValue('agenda'),
substr(uniqid('z', true), 0, 10),
isset($formValues['sign_attendance']),
$formValues['reason_to_sign']
);
Display::addFlash( try {
Display::return_message($this->get_lang('NewMeetingCreated')) $startTime = new DateTime($formValues['startTime']);
);
if ('meeting' === $conferenceType) {
$newMeeting = $this->createScheduleMeeting(
$user,
$course,
$group,
$session,
$startTime,
$formValues['duration'],
$formValues['topic'],
$formValues['agenda'],
$password,
isset($formValues['sign_attendance']),
$formValues['reason_to_sign'],
$accountEmail
);
Display::addFlash(
Display::return_message($this->get_lang('NewWebinarCreated'))
);
} elseif ('webinar' === $conferenceType) {
$newMeeting = $this->createScheduleWebinar(
$user,
$course,
$group,
$session,
$startTime,
$formValues['duration'],
$formValues['topic'],
$formValues['agenda'],
$password,
isset($formValues['sign_attendance']),
$formValues['reason_to_sign'],
$accountEmail
);
Display::addFlash(
Display::return_message($this->get_lang('NewMeetingCreated'))
);
} else {
throw new Exception('Invalid conference type');
}
if ($newMeeting->isCourseMeeting()) { if ($newMeeting->isCourseMeeting()) {
if ('RegisterAllCourseUsers' === $form->getSubmitValue('userRegistration')) { if ('RegisterAllCourseUsers' === $form->getSubmitValue('userRegistration')) {
@ -964,16 +1140,19 @@ class ZoomPlugin extends Plugin
* Returns the URL to enter (start or join) a meeting or null if not possible to enter the meeting, * 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. * The returned URL depends on the meeting current status (waiting, started or finished) and the current user.
* *
* @param Meeting $meeting
*
* @throws OptimisticLockException * @throws OptimisticLockException
* @throws Exception * @throws Exception
* *
* @return string|null * @return string|null
*/ */
public function getStartOrJoinMeetingURL($meeting) public function getStartOrJoinMeetingURL(Meeting $meeting)
{ {
$status = $meeting->getMeetingInfoGet()->status; if ($meeting instanceof Webinar) {
$status = 'started';
} else {
$status = $meeting->getMeetingInfoGet()->status;
}
$userId = api_get_user_id(); $userId = api_get_user_id();
$currentUser = api_get_user_entity($userId); $currentUser = api_get_user_entity($userId);
$isGlobal = 'true' === $this->get('enableGlobalConference') && $meeting->isGlobalMeeting(); $isGlobal = 'true' === $this->get('enableGlobalConference') && $meeting->isGlobalMeeting();
@ -999,7 +1178,9 @@ class ZoomPlugin extends Plugin
case 'started': case 'started':
// User per conference. // User per conference.
if ($currentUser === $meeting->getUser()) { if ($currentUser === $meeting->getUser()) {
return $meeting->getMeetingInfoGet()->join_url; return $meeting instanceof Webinar
? $meeting->getWebinarSchema()->start_url
: $meeting->getMeetingInfoGet()->join_url;
} }
// The participant is not registered, he can join only the global meeting (automatic registration). // The participant is not registered, he can join only the global meeting (automatic registration).
@ -1009,7 +1190,9 @@ class ZoomPlugin extends Plugin
if ($meeting->isCourseMeeting()) { if ($meeting->isCourseMeeting()) {
if ($this->userIsCourseConferenceManager()) { if ($this->userIsCourseConferenceManager()) {
return $meeting->getMeetingInfoGet()->start_url; return $meeting instanceof Webinar
? $meeting->getWebinarSchema()->start_url
: $meeting->getMeetingInfoGet()->start_url;
} }
$sessionId = api_get_session_id(); $sessionId = api_get_session_id();
@ -1039,7 +1222,9 @@ class ZoomPlugin extends Plugin
} }
} }
if (\Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT == $meeting->getMeetingInfoGet()->type) { if (!$meeting instanceof Webinar
&& \Chamilo\PluginBundle\Zoom\API\Meeting::TYPE_INSTANT == $meeting->getMeetingInfoGet()->type
) {
return $meeting->getMeetingInfoGet()->join_url; return $meeting->getMeetingInfoGet()->join_url;
} }
@ -1052,7 +1237,7 @@ class ZoomPlugin extends Plugin
//if ('true' === $this->get('enableParticipantRegistration')) { //if ('true' === $this->get('enableParticipantRegistration')) {
//if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) { //if ('true' === $this->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {
// the participant must be registered // the participant must be registered
$registrant = $meeting->getRegistrant($currentUser); $registrant = $meeting->getRegistrantByUser($currentUser);
if (null == $registrant) { if (null == $registrant) {
throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting')); throw new Exception($this->get_lang('YouAreNotRegisteredToThisMeeting'));
} }
@ -1190,6 +1375,10 @@ class ZoomPlugin extends Plugin
} }
if (api_is_platform_admin()) { if (api_is_platform_admin()) {
$actionsLeft .= Display::url(
Display::return_icon('agenda.png', get_lang('Calendar'), [], ICON_SIZE_MEDIUM),
'calendar.php'
);
$actionsLeft .= $actionsLeft .=
Display::url( Display::url(
Display::return_icon('settings.png', get_lang('Settings'), null, ICON_SIZE_MEDIUM), Display::return_icon('settings.png', get_lang('Settings'), null, ICON_SIZE_MEDIUM),
@ -1323,6 +1512,98 @@ class ZoomPlugin extends Plugin
} }
} }
/**
* @throws Exception
*/
public function createWebinarFromSchema(Webinar $webinar, WebinarSchema $schema): Webinar
{
$currentUser = api_get_user_entity(api_get_user_id());
$schema->settings->contact_email = $currentUser->getEmail();
$schema->settings->contact_name = $currentUser->getFullname();
$schema->settings->auto_recording = $this->getRecordingSetting();
$schema->settings->registrants_email_notification = false;
$schema->settings->attendees_and_panelists_reminder_email_notification->enable = false;
$schema->settings->follow_up_attendees_email_notification->enable = false;
$schema->settings->follow_up_absentees_email_notification->enable = false;
$schema = $schema->create($webinar->getAccountEmail());
$webinar->setWebinarSchema($schema);
$em = Database::getManager();
$em->persist($webinar);
$em->flush();
return $webinar;
}
public function getAccountEmails(): array
{
$currentValue = $this->get('accountSelector');
if (empty($currentValue)) {
return [];
}
$emails = explode(';', $currentValue);
$trimmed = array_map('trim', $emails);
$filtered = array_filter($trimmed);
return array_combine($filtered, $filtered);
}
/**
* Register users to a meeting.
*
* @param User[] $users
*
* @throws OptimisticLockException
*
* @return User[] failed registrations [ user id => errorMessage ]
*/
public function registerUsers(Meeting $meeting, array $users)
{
$failedUsers = [];
foreach ($users as $user) {
try {
$this->registerUser($meeting, $user, false);
} catch (Exception $exception) {
$failedUsers[$user->getId()] = $exception->getMessage();
}
}
Database::getManager()->flush();
return $failedUsers;
}
/**
* Removes registrants from a meeting.
*
* @param Registrant[] $registrants
*
* @throws Exception
*/
public function unregister(Meeting $meeting, array $registrants)
{
$meetingRegistrants = [];
foreach ($registrants as $registrant) {
$meetingRegistrants[] = $registrant->getMeetingRegistrant();
}
if ($meeting instanceof Webinar) {
$meeting->getWebinarSchema()->removeRegistrants($meetingRegistrants);
} else {
$meeting->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
}
$em = Database::getManager();
foreach ($registrants as $registrant) {
$em->remove($registrant);
}
$em->flush();
}
/** /**
* Updates meeting registrants list. Adds the missing registrants and removes the extra. * Updates meeting registrants list. Adds the missing registrants and removes the extra.
* *
@ -1363,31 +1644,6 @@ class ZoomPlugin extends Plugin
$this->unregister($meeting, $registrantsToRemove); $this->unregister($meeting, $registrantsToRemove);
} }
/**
* Register users to a meeting.
*
* @param Meeting $meeting
* @param User[] $users
*
* @throws OptimisticLockException
*
* @return User[] failed registrations [ user id => errorMessage ]
*/
private function registerUsers($meeting, $users)
{
$failedUsers = [];
foreach ($users as $user) {
try {
$this->registerUser($meeting, $user, false);
} catch (Exception $exception) {
$failedUsers[$user->getId()] = $exception->getMessage();
}
}
Database::getManager()->flush();
return $failedUsers;
}
/** /**
* @throws Exception * @throws Exception
* @throws OptimisticLockException * @throws OptimisticLockException
@ -1400,17 +1656,32 @@ class ZoomPlugin extends Plugin
throw new Exception($this->get_lang('CannotRegisterWithoutEmailAddress')); throw new Exception($this->get_lang('CannotRegisterWithoutEmailAddress'));
} }
$meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName( if ($meeting instanceof Webinar) {
$user->getEmail(), $meetingRegistrant = WebinarRegistrantSchema::fromEmailAndFirstName(
$user->getFirstname(), $user->getEmail(),
$user->getLastname() $user->getFirstname(),
); $user->getLastname()
);
} else {
$meetingRegistrant = MeetingRegistrant::fromEmailAndFirstName(
$user->getEmail(),
$user->getFirstname(),
$user->getLastname()
);
}
$registrantEntity = (new Registrant()) $registrantEntity = (new Registrant())
->setMeeting($meeting) ->setMeeting($meeting)
->setUser($user) ->setUser($user)
->setMeetingRegistrant($meetingRegistrant) ->setMeetingRegistrant($meetingRegistrant)
->setCreatedRegistration($meeting->getMeetingInfoGet()->addRegistrant($meetingRegistrant)); ;
if ($meeting instanceof Webinar) {
$registrantEntity->setCreatedRegistration($meeting->getWebinarSchema()->addRegistrant($meetingRegistrant));
} else {
$registrantEntity->setCreatedRegistration($meeting->getMeetingInfoGet()->addRegistrant($meetingRegistrant));
}
Database::getManager()->persist($registrantEntity); Database::getManager()->persist($registrantEntity);
if ($andFlush) { if ($andFlush) {
@ -1420,28 +1691,6 @@ class ZoomPlugin extends Plugin
return $registrantEntity; return $registrantEntity;
} }
/**
* Removes registrants from a meeting.
*
* @param Meeting $meeting
* @param Registrant[] $registrants
*
* @throws Exception
*/
private function unregister($meeting, $registrants)
{
$meetingRegistrants = [];
foreach ($registrants as $registrant) {
$meetingRegistrants[] = $registrant->getMeetingRegistrant();
}
$meeting->getMeetingInfoGet()->removeRegistrants($meetingRegistrants);
$em = Database::getManager();
foreach ($registrants as $registrant) {
$em->remove($registrant);
}
$em->flush();
}
/** /**
* Starts a new instant meeting and redirects to its start url. * Starts a new instant meeting and redirects to its start url.
* *
@ -1490,7 +1739,11 @@ class ZoomPlugin extends Plugin
//$meeting->getMeetingInfoGet()->settings->alternative_hosts = $currentUser->getEmail(); //$meeting->getMeetingInfoGet()->settings->alternative_hosts = $currentUser->getEmail();
// Send create to Zoom. // Send create to Zoom.
$meeting->setMeetingInfoGet($meeting->getMeetingInfoGet()->create()); $meeting->setMeetingInfoGet(
$meeting->getMeetingInfoGet()->create(
$meeting->getAccountEmail()
)
);
Database::getManager()->persist($meeting); Database::getManager()->persist($meeting);
Database::getManager()->flush(); Database::getManager()->flush();
@ -1548,7 +1801,8 @@ class ZoomPlugin extends Plugin
$agenda, $agenda,
$password, $password,
bool $signAttendance = false, bool $signAttendance = false,
string $reasonToSignAttendance = '' string $reasonToSignAttendance = '',
string $accountEmail = null
) { ) {
$meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED); $meetingInfoGet = MeetingInfoGet::fromTopicAndType($topic, MeetingInfoGet::TYPE_SCHEDULED);
$meetingInfoGet->duration = $duration; $meetingInfoGet->duration = $duration;
@ -1569,9 +1823,50 @@ class ZoomPlugin extends Plugin
->setSession($session) ->setSession($session)
->setSignAttendance($signAttendance) ->setSignAttendance($signAttendance)
->setReasonToSignAttendance($reasonToSignAttendance) ->setReasonToSignAttendance($reasonToSignAttendance)
->setAccountEmail($accountEmail)
); );
} }
/**
* @throws Exception
*/
private function createScheduleWebinar(
?User $user,
?Course $course,
?CGroupInfo $group,
?Session $session,
DateTime $startTime,
$duration,
$topic,
$agenda,
$password,
bool $signAttendance = false,
string $reasonToSignAttendance = '',
string $accountEmail = null
): Webinar {
$webinarSchema = WebinarSchema::fromTopicAndType($topic);
$webinarSchema->duration = $duration;
$webinarSchema->start_time = $startTime->format(DATE_ATOM);
$webinarSchema->agenda = $agenda;
$webinarSchema->password = $password;
if ('true' === $this->get('enableParticipantRegistration')) {
$webinarSchema->settings->approval_type = WebinarSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE;
}
$webinar = (new Webinar())
->setUser($user)
->setCourse($course)
->setGroup($group)
->setSession($session)
->setSignAttendance($signAttendance)
->setReasonToSignAttendance($reasonToSignAttendance)
->setAccountEmail($accountEmail)
;
return $this->createWebinarFromSchema($webinar, $webinarSchema);
}
/** /**
* Registers all the course users to a course meeting. * Registers all the course users to a course meeting.
* *

@ -3,6 +3,7 @@
/* For license terms, see /license.txt */ /* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\Meeting; use Chamilo\PluginBundle\Zoom\Meeting;
use Chamilo\PluginBundle\Zoom\Webinar;
require_once __DIR__.'/config.php'; require_once __DIR__.'/config.php';
@ -55,8 +56,14 @@ $tpl = new Template($meeting->getMeetingId());
if ($plugin->userIsConferenceManager($meeting)) { if ($plugin->userIsConferenceManager($meeting)) {
// user can edit, start and delete meeting // user can edit, start and delete meeting
$tpl->assign('isConferenceManager', true); $tpl->assign('isConferenceManager', true);
$tpl->assign('editMeetingForm', $plugin->getEditMeetingForm($meeting)->returnForm());
$tpl->assign('deleteMeetingForm', $plugin->getDeleteMeetingForm($meeting, $returnURL)->returnForm()); if ($meeting instanceof Webinar) {
$tpl->assign('editMeetingForm', $plugin->getEditWebinarForm($meeting)->returnForm());
$tpl->assign('deleteMeetingForm', $plugin->getDeleteWebinarForm($meeting, $returnURL)->returnForm());
} elseif ($meeting instanceof Meeting) {
$tpl->assign('editMeetingForm', $plugin->getEditMeetingForm($meeting)->returnForm());
$tpl->assign('deleteMeetingForm', $plugin->getDeleteMeetingForm($meeting, $returnURL)->returnForm());
}
if (false === $meeting->isGlobalMeeting() && false == $meeting->isCourseMeeting()) { if (false === $meeting->isGlobalMeeting() && false == $meeting->isCourseMeeting()) {
if ('true' === $plugin->get('enableParticipantRegistration') && $meeting->requiresRegistration()) { if ('true' === $plugin->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {

@ -0,0 +1,117 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\Meeting;
use Chamilo\UserBundle\Entity\User;
require_once __DIR__.'/config.php';
api_block_anonymous_users();
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
if (empty($_REQUEST['meetingId'])) {
api_not_allowed(true);
}
$plugin = ZoomPlugin::create();
/** @var Meeting $meeting */
$meeting = $plugin->getMeetingRepository()->findOneBy(['meetingId' => $_REQUEST['meetingId']]);
if (null === $meeting) {
api_not_allowed(true, $plugin->get_lang('MeetingNotFound'));
}
if (false !== $meeting->isGlobalMeeting()
|| false != $meeting->isCourseMeeting()
|| 'true' !== $plugin->get('enableParticipantRegistration')
|| !$meeting->requiresRegistration()
) {
api_not_allowed(true);
}
$currentUser = api_get_user_entity(api_get_user_id());
$userRegistrant = $meeting->getRegistrantByUser($currentUser);
if ($meeting->isCourseMeeting()) {
api_protect_course_script(true);
if (api_is_in_group()) {
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
'name' => get_lang('Groups'),
];
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group_space.php?'.api_get_cidreq(),
'name' => get_lang('GroupSpace').' '.$meeting->getGroup()->getName(),
];
}
}
$form = new FormValidator('subscription');
$form->addHidden('meetingId', $meeting->getMeetingId());
if (!empty($userRegistrant)) {
$form->addButton(
'unregister',
$plugin->get_lang('UnregisterMeToConference'),
'user-times',
'warning'
);
$form->addHtml(
'<div class="form-group"><div class="col-sm-8 col-sm-offset-2">'
.Display::url(
$plugin->get_lang('ViewMeeting'),
api_get_path(WEB_PLUGIN_PATH).'zoom/join_meeting.php?meetingId='.$meeting->getMeetingId(),
['class' => 'btn btn-primary']
)
.'</div></div>'
);
} else {
$filtered = array_filter(
$meeting->getRegistrableUsers(),
function (User $registableUser) use ($currentUser) {
return $registableUser->getId() === $currentUser->getId();
}
);
if (empty($filtered)) {
api_not_allowed(true);
}
$form->addButton(
'register',
$plugin->get_lang('RegisterMeToConference'),
'user-plus',
'success'
);
}
if ($form->validate()) {
$values = $form->exportValues();
if (isset($values['unregister'])) {
$plugin->unregister($meeting, [$userRegistrant]);
} else {
$plugin->registerUsers($meeting, [$currentUser]);
}
Display::addFlash(
Display::return_message($plugin->get_lang('RegisteredUserListWasUpdated'), 'success')
);
api_location('?meetingId='.$meeting->getMeetingId());
} else {
$form->protect();
}
$view = new Template('');
$view->assign('meeting', $meeting);
$view->assign('frm_register_unregister', $form->returnForm());
$content = $view->fetch('zoom/view/subscription.tpl');
$view->assign('content', $content);
$view->display_one_col_template();

@ -0,0 +1,312 @@
<div id="loading" style="margin-left:150px;position:absolute;display:none">
{{ "Loading"|get_lang }} &hellip;
</div>
<div id="calendar"></div>
<div class="modal fade" tabindex="-1" role="dialog" id="simple-dialog-form">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'Close'|get_lang }}">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">{{ 'Details'|get_lang }}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-4 control-label">{{ "Date"|get_lang }}</label>
<div class="col-sm-8">
<p class="form-static-control">
<span id="simple_start_date"></span>
<span id="simple_end_date"></span>
</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{{ "Title"|get_lang }}</label>
<div class="col-sm-8">
<p class="form-static-control" id="simple_title"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{{ "Description"|get_lang }}</label>
<div class="col-sm-8">
<p class="form-static-control" id="simple_content"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{{ "AccountEmail"|get_plugin_lang('ZoomPlugin') }}</label>
<div class="col-sm-8">
<p class="form-static-control" id="simple_account"></p>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'Close'|get_lang }}</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
var cookieData = Cookies.getJSON('agenda_cookies');
var defaultView = (cookieData && cookieData.view) || '{{ default_view }}';
var defaultStartDate = (cookieData && cookieData.start) || moment.now();
var CustomListViewGrid = ListViewGrid.extend({
fgSegHtml: function (seg) {
var view = this.view;
var classes = ['fc-list-item'].concat(this.getSegCustomClasses(seg));
var bgColor = this.getSegBackgroundColor(seg);
var event = seg.event;
var url = event.url;
var timeHtml;
if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day
if (seg.isStart || seg.isEnd) { // outer segment that probably lasts part of the day
timeHtml = htmlEscape(this.getEventTimeText(seg));
} else { // inner segment that lasts the whole day
timeHtml = view.getAllDayHtml();
}
} else {
// Display the normal time text for the *event's* times
timeHtml = htmlEscape(this.getEventTimeText(event));
}
if (url) {
classes.push('fc-has-url');
}
return '<tr class="' + classes.join(' ') + '">' +
(this.displayEventTime
? '<td class="fc-list-item-time ' + view.widgetContentClass + '">' + (timeHtml || '') + '</td>'
: ''
) +
'<td class="fc-list-item-marker ' + view.widgetContentClass + '">' +
'<span class="fc-event-dot"' +
(bgColor ? ' style="background-color:' + bgColor + '"' : '') +
'></span>' +
'</td>' +
'<td class="fc-list-item-title ' + view.widgetContentClass + '">' +
'<a' + (url ? ' href="' + htmlEscape(url) + '"' : '') + '>' +
htmlEscape(seg.event.title || '') + (seg.event.description || '') +
'</a>' +
'</td>' +
'</tr>';
},
// render the event segments in the view
renderSegList: function (allSegs) {
var segsByDay = this.groupSegsByDay(allSegs); // sparse array
var dayIndex;
var daySegs;
var i;
var tableEl = $('<table class="fc-list-table"><tbody/></table>');
var tbodyEl = tableEl.find('tbody');
var eventList = [];
for (dayIndex = 0; dayIndex < segsByDay.length; dayIndex++) {
daySegs = segsByDay[dayIndex];
if (daySegs) { // sparse array, so might be undefined
this.sortEventSegs(daySegs);
for (i = 0; i < daySegs.length; i++) {
var event = daySegs[i].event;
if (jQuery.inArray(event.id, eventList) !== -1) {
continue;
}
eventList.push(event.id);
// append a day header
tbodyEl.append(this.dayHeaderHtml(
this.view.start.clone().add(dayIndex, 'days'),
event
));
tbodyEl.append(daySegs[i].el); // append event row
}
}
}
this.el.empty().append(tableEl);
},
// generates the HTML for the day headers that live amongst the event rows
dayHeaderHtml: function (dayDate, event) {
var view = this.view;
var mainFormat = 'LL';
var altFormat = 'dddd';
var checkIfSame = true;
if (event.end) {
checkIfSame = event.end.format(mainFormat) === dayDate.format(mainFormat);
}
return '<tr class="fc-list-heading" data-date="' + dayDate.format('YYYY-MM-DD') + '">' +
'<td class="' + view.widgetHeaderClass + '" colspan="3">' +
(
mainFormat
? view.buildGotoAnchorHtml(
dayDate,
{ 'class': 'fc-list-heading-main' },
htmlEscape(dayDate.format(mainFormat)) // inner HTML
)
: ''
) +
(
(checkIfSame === false && mainFormat)
? view.buildGotoAnchorHtml(
dayDate,
{ 'class': 'fc-list-heading-main' },
'&nbsp;-&nbsp; ' + htmlEscape(event.end.format(mainFormat)) // inner HTML
)
: ''
) +
(
altFormat
? view.buildGotoAnchorHtml(
dayDate,
{ 'class': 'fc-list-heading-alt' },
htmlEscape(dayDate.format(altFormat)) // inner HTML
)
: ''
) +
'</td>' +
'</tr>'
}
})
var FC = $.fullCalendar; // a reference to FullCalendar's root namespace
var View = ListView; // the class that all views must inherit from
var CustomView; // our subclass
CustomView = View.extend({ // make a subclass of View
initialize: function () {
this.grid = new CustomListViewGrid(this);
this.scroller = new Scroller({
overflowX: 'hidden',
overflowY: 'auto'
});
}
})
FC.views.CustomView = CustomView; // register our class with the view system
var height = '';
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
height = 'auto';
}
$('#calendar').fullCalendar({
height: height,
header: {
left: 'today,prev,next',
center: 'title',
right: 'month,agendaWeek,agendaDay,CustomView'
},
views: {
CustomView: { // name of view
type: 'list',
buttonText: '{{ 'AgendaList'|get_lang | escape('js') }}',
duration: { month: 1 },
defaults: {
'listDayAltFormat': 'dddd' // day-of-week is nice-to-have
}
},
month: {
'displayEventEnd': true
}
},
locale: '{{ region_value }}',
defaultView: defaultView,
defaultDate: defaultStartDate,
firstHour: 8,
firstDay: 1,
{% if fullcalendar_settings %}
{{ fullcalendar_settings }}
{% endif %}
selectable: false,
selectHelper: true,
viewRender: function (view, element) {
var data = {
'view': view.name,
'start': view.intervalStart.format('YYYY-MM-DD')
};
Cookies.set('agenda_cookies', data, 1); // Expires 1 day
},
eventRender: function (event, element) {
{% if on_hover_info.description %}
if (event.description) {
element.qtip({
content: event.description,
position: {
at: 'top center',
my: 'bottom center',
viewport: $(window)
}
});
}
{% endif %}
},
eventClick: function (calEvent, jsEvent, view) {
var start = calEvent.start;
var end = calEvent.end;
var diffDays = moment(end).diff(start, 'days');
var endDateMinusOne = '';
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
// If event is not editable then just return the qtip
{% if on_hover_info.description %}
if (calEvent.description) {
$(this).qtip({
overwrite: false,
show: { ready: true },
content: calEvent.description,
position: {
at: 'top center',
my: 'bottom center',
viewport: $(window)
}
});
}
{% endif %}
return;
}
var clone = end.clone();
endDateMinusOne = clone.subtract(1, 'days').format('{{ js_format_date }}');
var startDateToString = start.format("{{ js_format_date }}");
// Simple form
$('#simple_start_date').html(startDateToString);
if (diffDays > 1) {
$('#simple_end_date').html(' - ' + endDateMinusOne);
} else if (diffDays == 0) {
var start_date_value = start.format('ll');
var startTime = start.format('LT');
var endTime = end.format('LT');
$('#simple_start_date').html('');
$('#simple_end_date').html(start_date_value + ' (' + startTime + ' - ' + endTime + ') ');
} else {
$('#simple_end_date').html('');
}
$('#simple_title').html(calEvent.title);
$('#simple_content').html(calEvent.description);
$('#simple_account').html(calEvent.accountEmail);
$('#simple-dialog-form').modal('show');
},
editable: false,
events: "{{ web_agenda_ajax_url }}&a=get_events",
axisFormat: 'H(:mm)', // pm-am format -> h(:mm)a
timeFormat: 'H:mm', // pm-am format -> h:mm
loading: function (bool) {
if (bool) {
$('#loading').show();
} else {
$('#loading').hide();
}
}
})
})
</script>

@ -1,5 +1,8 @@
<h4> <h4>
{{ meeting.typeName }} {{ meeting.meetingId }} ({{ meeting.meetingInfoGet.status }}) {{ meeting.typeName }} {{ meeting.meetingId }}
{% if meeting.meetingInfoGet.status %}
({{ meeting.meetingInfoGet.status }})
{% endif %}
</h4> </h4>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
@ -62,7 +65,10 @@
{% if isConferenceManager %} {% if isConferenceManager %}
{{ editMeetingForm }} {{ editMeetingForm }}
{{ deleteMeetingForm }} {{ deleteMeetingForm }}
{{ registerParticipantForm }} {% if registerParticipantForm %}
<hr>
{{ registerParticipantForm }}
{% endif %}
{{ fileForm }} {{ fileForm }}
{# {% if registrants and meeting.meetingInfoGet.settings.approval_type != 2 %}#} {# {% if registrants and meeting.meetingInfoGet.settings.approval_type != 2 %}#}
@ -103,18 +109,5 @@
</table> </table>
{% endif %} {% endif %}
{% else %} {% else %}
<h2>{{ meeting.meetingInfoGet.topic }}</h2> {% include 'zoom/view/meeting_details.tpl' %}
{% if meeting.meetingInfoGet.agenda %}
<blockquote>{{ meeting.meetingInfoGet.agenda| nl2br }}</blockquote>
{% endif %}
{% if meeting.meetingInfoGet.type == 2 or meeting.meetingInfoGet.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 %}
{% endif %} {% endif %}

@ -0,0 +1,39 @@
{% if meeting.meetingInfoGet %}
<h2>
{{ meeting.meetingInfoGet.topic }}
<small>{{ meeting.typeName }}</small>
</h2>
{% if meeting.meetingInfoGet.agenda %}
<p class="lead">{{ meeting.meetingInfoGet.agenda|nl2br }}</p>
{% endif %}
{% if meeting.meetingInfoGet.type == 2 or meeting.meetingInfoGet.type == 8 %}
<dl class="meeting_properties dl-horizontal">
<dt>{{ 'StartTime'|get_lang }}</dt>
<dd>{{ meeting.formattedStartTime }}</dd>
<dt>{{ 'Duration'|get_lang }}</dt>
<dd>{{ meeting.formattedDuration }}</dd>
</dl>
{% endif %}
{% elseif meeting.webinarSchema %}
<h2>
{{ meeting.webinarSchema.topic }}
<small>{{ meeting.typeName }}</small>
</h2>
{% if meeting.webinarSchema.agenda %}
<p class="lead">{{ meeting.webinarSchema.agenda|nl2br }}</p>
{% endif %}
{% if meeting.webinarSchema.type == 5 or meeting.webinarSchema.type == 9 %}
<dl class="meeting_properties dl-horizontal">
<dt>{{ 'StartTime'|get_lang }}</dt>
<dd>{{ meeting.formattedStartTime }}</dd>
<dt>{{ 'Duration'|get_lang }}</dt>
<dd>{{ meeting.formattedDuration }}</dd>
</dl>
{% endif %}
{% endif %}

@ -8,6 +8,7 @@
<table class="table table-hover table-striped"> <table class="table table-hover table-striped">
<thead> <thead>
<tr> <tr>
<th>{{ 'Type'|get_lang }}</th>
<th>{{ 'Topic'|get_plugin_lang('ZoomPlugin') }}</th> <th>{{ 'Topic'|get_plugin_lang('ZoomPlugin') }}</th>
<th>{{ 'StartTime'|get_lang }}</th> <th>{{ 'StartTime'|get_lang }}</th>
<th>{{ 'ForEveryone'|get_plugin_lang('ZoomPlugin') }}</th> <th>{{ 'ForEveryone'|get_plugin_lang('ZoomPlugin') }}</th>
@ -22,9 +23,11 @@
<tbody> <tbody>
{% for meeting in meetings %} {% for meeting in meetings %}
<tr> <tr>
<td>{{ meeting.typeName }}</td>
<td>{{ meeting.meetingInfoGet.topic }}</td> <td>{{ meeting.meetingInfoGet.topic }}</td>
<td>{{ meeting.webinarSchema.topic }}</td>
<td>{{ meeting.formattedStartTime }}</td> <td>{{ meeting.formattedStartTime }}</td>
<td>{{ meeting.user ? 'No' : 'Yes' }}</td> <td>{{ meeting.user ? 'No'|get_lang : 'Yes'|get_lang }}</td>
{# <td>{{ meeting.course ? meeting.course : '-' }}</td>#} {# <td>{{ meeting.course ? meeting.course : '-' }}</td>#}
{# <td>{{ meeting.session ? meeting.session : '-' }}</td>#} {# <td>{{ meeting.session ? meeting.session : '-' }}</td>#}
<td> <td>

@ -12,6 +12,7 @@
</div> </div>
<table class="table"> <table class="table">
<tr> <tr>
<th>{{ 'Type'|get_lang }}</th>
<th>{{ 'Topic'|get_plugin_lang('ZoomPlugin') }}</th> <th>{{ 'Topic'|get_plugin_lang('ZoomPlugin') }}</th>
<th>{{ 'Agenda'|get_plugin_lang('ZoomPlugin') }}</th> <th>{{ 'Agenda'|get_plugin_lang('ZoomPlugin') }}</th>
<th>{{ 'StartTime'|get_lang }}</th> <th>{{ 'StartTime'|get_lang }}</th>
@ -20,11 +21,14 @@
</tr> </tr>
{% for meeting in meetings %} {% for meeting in meetings %}
<tr> <tr>
<td>{{ meeting.typeName }}</td>
<td> <td>
{{ meeting.meetingInfoGet.topic }} {{ meeting.meetingInfoGet.topic }}
{{ meeting.webinarSchema.topic }}
</td> </td>
<td> <td>
{{ meeting.meetingInfoGet.agenda|nl2br }} {{ meeting.meetingInfoGet.agenda|nl2br }}
{{ meeting.webinarSchema.agenda|nl2br }}
</td> </td>
<td>{{ meeting.formattedStartTime }}</td> <td>{{ meeting.formattedStartTime }}</td>
<td>{{ meeting.formattedDuration }}</td> <td>{{ meeting.formattedDuration }}</td>

@ -0,0 +1,3 @@
{% include 'zoom/view/meeting_details.tpl' %}
{{ frm_register_unregister }}
Loading…
Cancel
Save