Calendar: Allow to subscribe/unsubscribe users to events - refs BT#20637

pull/4668/head
Angel Fernando Quiroz Campos 3 years ago
parent 6c617c5798
commit 4682d74927
  1. 26
      main/inc/ajax/agenda.ajax.php
  2. 299
      main/inc/lib/agenda.lib.php
  3. 6
      main/install/configuration.dist.php
  4. 79
      main/template/default/agenda/month.tpl
  5. 11
      src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php
  6. 6
      src/Chamilo/CoreBundle/Entity/AgendaEventInvitee.php
  7. 14
      src/Chamilo/CoreBundle/Entity/AgendaEventSubscriber.php
  8. 26
      src/Chamilo/CoreBundle/Entity/AgendaEventSubscription.php
  9. 11
      src/Chamilo/CoreBundle/Entity/Repository/PersonalAgendaRepository.php
  10. 12
      src/Chamilo/CoreBundle/Traits/EventSubscribableTrait.php

@ -228,6 +228,32 @@ switch ($action) {
);
}
break;
case 'event_subscribe':
if (!$agenda->getIsAllowedToEdit()) {
break;
}
if (false === Security::check_token('get')) {
exit;
}
$id = (int) explode('_', $_REQUEST['id'])[1];
$agenda->subscribeCurrentUserToEvent($id);
break;
case 'event_unsubscribe':
if (!$agenda->getIsAllowedToEdit()) {
break;
}
if (false === Security::check_token('get')) {
exit;
}
$id = (int) explode('_', $_REQUEST['id'])[1];
$agenda->unsubscribeCurrentUserToEvent($id);
break;
default:
echo '';
}

@ -4,8 +4,10 @@
use Chamilo\CoreBundle\Entity\AgendaEventInvitation;
use Chamilo\CoreBundle\Entity\AgendaEventInvitee;
use Chamilo\CoreBundle\Entity\AgendaEventSubscriber;
use Chamilo\CoreBundle\Entity\AgendaEventSubscription;
use Chamilo\CoreBundle\Entity\AgendaReminder;
use Chamilo\CoreBundle\Entity\PersonalAgenda;
use Chamilo\UserBundle\Entity\User;
/**
@ -264,6 +266,8 @@ class Agenda
$allDay = isset($allDay) && ($allDay === 'true' || $allDay == 1) ? 1 : 0;
$id = null;
$em = Database::getManager();
switch ($this->type) {
case 'personal':
$attributes = [
@ -286,14 +290,22 @@ class Agenda
}
if (api_get_configuration_value('agenda_event_subscriptions') && api_is_platform_admin()) {
Database::update(
$this->tbl_personal_agenda,
[
'subscription_visibility' => $subscriptionVisibility,
'max_subscriptions' => $subscriptionVisibility > 0 ? $maxSubscriptions : 0,
],
['id = ?' => [$id]]
);
$personalEvent = $em->find(PersonalAgenda::class, $id);
$personalEvent
->setSubscriptionVisibility($subscriptionVisibility)
;
$subscription = (new AgendaEventSubscription())
->setCreator(api_get_user_entity(api_get_user_id()))
->setMaxAttendees($subscriptionVisibility > 0 ? $maxSubscriptions : 0)
;
$personalEvent
->setCollective(false)
->setInvitation($subscription)
;
$em->flush();
}
break;
case 'course':
@ -890,6 +902,8 @@ class Agenda
$currentUserId = api_get_user_id();
$authorId = empty($authorId) ? $currentUserId : (int) $authorId;
$em = Database::getManager();
switch ($this->type) {
case 'personal':
$eventInfo = $this->get_event($id);
@ -927,14 +941,16 @@ class Agenda
}
if (api_get_configuration_value('agenda_event_subscriptions') && api_is_platform_admin()) {
Database::update(
$this->tbl_personal_agenda,
[
'subscription_visibility' => $subscriptionVisibility,
'max_subscriptions' => $subscriptionVisibility > 0 ? $maxSubscriptions : 0,
],
['id = ?' => [$id]]
);
$personalEvent = $em->find(PersonalAgenda::class, $id);
$personalEvent
->setSubscriptionVisibility($subscriptionVisibility)
;
/** @var AgendaEventSubscription $subscription */
$subscription = $personalEvent->getInvitation();
$subscription->setMaxAttendees($subscriptionVisibility > 0 ? $maxSubscriptions : 0);
$em->flush();
}
break;
case 'course':
@ -1333,6 +1349,68 @@ class Agenda
}
}
public function subscribeCurrentUserToEvent(int $id)
{
if (false === api_get_configuration_value('agenda_event_subscriptions')) {
return;
}
if ('personal' !== $this->type) {
return;
}
$em = Database::getManager();
$currentUser = api_get_user_entity(api_get_user_id());
$personalEvent = $em->find(PersonalAgenda::class, $id);
/** @var AgendaEventSubscription $subscription */
$subscription = $personalEvent ? $personalEvent->getInvitation() : null;
if (!$subscription) {
return;
}
if ($subscription->getInvitees()->count() >= $subscription->getMaxAttendees()) {
return;
}
$subscriber = (new AgendaEventSubscriber())
->setUser($currentUser)
;
$subscription->addInvitee($subscriber);
$em->flush();
}
public function unsubscribeCurrentUserToEvent(int $id)
{
if (false === api_get_configuration_value('agenda_event_subscriptions')) {
return;
}
if ('personal' !== $this->type) {
return;
}
$em = Database::getManager();
$currentUser = api_get_user_entity(api_get_user_id());
$personalEvent = $em->find(PersonalAgenda::class, $id);
/** @var AgendaEventSubscription $subscription */
$subscription = $personalEvent ? $personalEvent->getInvitation() : null;
if (!$subscription) {
return;
}
$subscription->removeInviteeUser($currentUser);
$em->flush();
}
/**
* Get agenda events.
*
@ -1787,18 +1865,22 @@ class Agenda
$agendaCollectiveInvitations = api_get_configuration_value('agenda_collective_invitations');
$agendaEventSubscriptions = api_get_configuration_value('agenda_event_subscriptions');
$userIsAdmin = api_is_platform_admin();
$queryParams = [];
if ($start !== 0) {
$startDate = api_get_utc_datetime($start, true, true);
$startCondition = "AND date >= '".$startDate->format('Y-m-d H:i:s')."'";
$queryParams['start_date'] = api_get_utc_datetime($start, true, true);
$startCondition = "AND pa.date >= :start_date";
}
if ($end !== 0) {
$endDate = api_get_utc_datetime($end, false, true);
$endCondition = "AND (enddate <= '".$endDate->format('Y-m-d H:i:s')."' OR enddate IS NULL)";
$queryParams['end_date'] = api_get_utc_datetime($end, false, true);
$endCondition = "AND (pa.enddate <= :end_date OR pa.enddate IS NULL)";
}
$user_id = api_get_user_id();
$userCondition = "user = $user_id";
$queryParams['user_id'] = $user_id;
$userCondition = "pa.user = :user_id";
if ($agendaEventSubscriptions) {
$objGroup = new UserGroup();
@ -1807,14 +1889,14 @@ class Agenda
$userCondition = "(
$userCondition
OR (
subscription_visibility = ".AgendaEventSubscription::SUBSCRIPTION_ALL;
pa.subscriptionVisibility = ".AgendaEventSubscription::SUBSCRIPTION_ALL;
if ($groupList) {
$userCondition .= "
OR (
subscription_visibility = ".AgendaEventSubscription::SUBSCRIPTION_CLASS."
AND subscription_item_id IN (".implode(', ', array_keys($groupList)).")
)
OR (
pa.subscriptionVisibility = ".AgendaEventSubscription::SUBSCRIPTION_CLASS."
AND pa.subscriptionItemId IN (".implode(', ', array_keys($groupList)).")
)
";
}
@ -1824,53 +1906,64 @@ class Agenda
";
}
$sql = "SELECT * FROM ".$this->tbl_personal_agenda."
WHERE $userCondition
$startCondition
$endCondition
";
$sql = "SELECT pa FROM ChamiloCoreBundle:PersonalAgenda AS pa WHERE $userCondition $startCondition $endCondition";
$result = Database::getManager()
->createQuery($sql)
->setParameters($queryParams)
->getResult();
$result = Database::query($sql);
$my_events = [];
if (Database::num_rows($result)) {
while ($row = Database::fetch_array($result, 'ASSOC')) {
$event = [];
$event['id'] = 'personal_'.$row['id'];
$event['title'] = $row['title'];
$event['className'] = 'personal';
$event['borderColor'] = $event['backgroundColor'] = $this->event_personal_color;
$event['editable'] = $user_id === (int) $row['user'];
$event['sent_to'] = get_lang('Me');
$event['type'] = 'personal';
if (!empty($row['date'])) {
$event['start'] = $this->formatEventDate($row['date']);
$event['start_date_localtime'] = api_get_local_time($row['date']);
}
if (!empty($row['enddate'])) {
$event['end'] = $this->formatEventDate($row['enddate']);
$event['end_date_localtime'] = api_get_local_time($row['enddate']);
}
/** @var PersonalAgenda $row */
foreach ($result as $row) {
$event = [];
$event['id'] = 'personal_'.$row->getId();
$event['title'] = $row->getTitle();
$event['className'] = 'personal';
$event['borderColor'] = $event['backgroundColor'] = $this->event_personal_color;
$event['editable'] = $user_id === (int) $row->getUser();
$event['sent_to'] = get_lang('Me');
$event['type'] = 'personal';
if (!empty($row->getDate())) {
$event['start'] = $this->formatEventDate($row->getDate());
$event['start_date_localtime'] = api_get_local_time($row->getDate());
}
$event['description'] = $row['text'];
$event['allDay'] = isset($row['all_day']) && $row['all_day'] == 1 ? $row['all_day'] : 0;
$event['parent_event_id'] = 0;
$event['has_children'] = 0;
if (!empty($row->getEnddate())) {
$event['end'] = $this->formatEventDate($row->getEnddate());
$event['end_date_localtime'] = api_get_local_time($row->getEnddate());
}
if ($agendaCollectiveInvitations) {
$event['collective'] = (bool) $row['collective'];
$event['invitees'] = self::getInviteesForPersonalEvent($row['id']);
}
$event['description'] = $row->getText();
$event['allDay'] = $row->getAllDay();
$event['parent_event_id'] = 0;
$event['has_children'] = 0;
if ($agendaEventSubscriptions) {
$event['subscription_visibility'] = (int) $row['subscription_visibility'];
$event['max_subscriptions'] = (int) $row['max_subscriptions'];
}
if ($agendaCollectiveInvitations) {
$event['collective'] = $row->isCollective();
$event['invitees'] = self::getInviteesForPersonalEvent($row->getId());
}
$my_events[] = $event;
$this->events[] = $event;
if ($agendaEventSubscriptions) {
/** @var AgendaEventSubscription $subscription */
$subscription = $row->getInvitation();
$subscribers = $subscription->getInvitees();
$event['subscription_visibility'] = $row->getSubscriptionVisibility();
$event['max_subscriptions'] = $subscription->getMaxAttendees();
$event['can_subscribe'] = $subscribers->count() < $subscription->getMaxAttendees();
$event['user_is_subscribed'] = $subscription->hasUserAsInvitee(api_get_user_entity($user_id));
$event['count_subscribers'] = $subscribers->count();
if ($userIsAdmin) {
$event['subscribers'] = self::getInviteesForPersonalEvent($row->getId(), AgendaEventSubscriber::class);
}
}
$my_events[] = $event;
$this->events[] = $event;
}
if ($agendaCollectiveInvitations) {
@ -1899,13 +1992,21 @@ class Agenda
return $my_events;
}
public static function getInviteesForPersonalEvent($eventId): array
public static function getInviteesForPersonalEvent($eventId, $type = AgendaEventInvitee::class): array
{
$em = Database::getManager();
$event = $em->find('ChamiloCoreBundle:PersonalAgenda', $eventId);
$inviteeRepo = $em->getRepository('ChamiloCoreBundle:AgendaEventInvitee');
$invitees = $inviteeRepo->findByInvitation($event->getInvitation());
$invitation = $event->getInvitation();
if ($invitation instanceof AgendaEventSubscription
&& AgendaEventInvitee::class === $type
) {
return [];
}
$inviteeRepo = $em->getRepository($type);
$invitees = $inviteeRepo->findByInvitation($invitation);
$inviteeList = [];
@ -2961,11 +3062,13 @@ class Agenda
if ($agendaCollectiveInvitations && 'personal' === $this->type) {
$invitees = [];
$isCollective = false;
$allowInvitees = true;
if ($personalEvent) {
$eventInvitation = $personalEvent->getInvitation();
$allowInvitees = !$eventInvitation instanceof AgendaEventSubscription;
if ($eventInvitation) {
if ($eventInvitation && $allowInvitees) {
foreach ($eventInvitation->getInvitees() as $invitee) {
$inviteeUser = $invitee->getUser();
@ -2976,20 +3079,22 @@ class Agenda
$isCollective = $personalEvent->isCollective();
}
$form->addSelectAjax(
'invitees',
get_lang('Invitees'),
$invitees,
[
'multiple' => 'multiple',
'url' => api_get_path(WEB_AJAX_PATH).'message.ajax.php?a=find_users',
]
);
$form->addCheckBox('collective', '', get_lang('IsItEditableByTheInvitees'));
$form->addHtml('<hr>');
if ($allowInvitees) {
$form->addSelectAjax(
'invitees',
get_lang('Invitees'),
$invitees,
[
'multiple' => 'multiple',
'url' => api_get_path(WEB_AJAX_PATH).'message.ajax.php?a=find_users',
]
);
$form->addCheckBox('collective', '', get_lang('IsItEditableByTheInvitees'));
$form->addHtml('<hr>');
$params['invitees'] = array_keys($invitees);
$params['collective'] = $isCollective;
$params['invitees'] = array_keys($invitees);
$params['collective'] = $isCollective;
}
}
if (api_get_configuration_value('agenda_reminders')) {
@ -3035,7 +3140,26 @@ class Agenda
})
</script>
");
$form->addHtml('<hr>');
if ($personalEvent) {
$subscribers = array_map(
function (array $subscriberInfo) {
return '<li>'.$subscriberInfo['name'].'</li>';
},
self::getInviteesForPersonalEvent($personalEvent->getId(), AgendaEventSubscriber::class)
);
$form->addLabel(
get_lang('Subscribers'),
'<ul class="form-control-static list-unstyled">'
.implode(PHP_EOL, $subscribers)
.'</ul>'
);
/** @var AgendaEventSubscription $subscription */
$subscription = $personalEvent->getInvitation();
$params['max_subscriptions'] = $subscription->getMaxAttendees();
}
}
if (api_get_configuration_value('allow_careers_in_global_agenda') && 'admin' === $this->type) {
@ -4686,10 +4810,15 @@ class Agenda
*/
public function formatEventDate($utcTime)
{
$utcTimeZone = new DateTimeZone('UTC');
if ($utcTime instanceof DateTime) {
$eventDate = $utcTime;
} else {
$utcTimeZone = new DateTimeZone('UTC');
$eventDate = new DateTime($utcTime, $utcTimeZone);
}
$platformTimeZone = new DateTimeZone(api_get_timezone());
$eventDate = new DateTime($utcTime, $utcTimeZone);
$eventDate->setTimezone($platformTimeZone);
return $eventDate->format(DateTime::ISO8601);

@ -465,7 +465,11 @@ CREATE UNIQUE INDEX UNIQ_D8612460AF68C6B ON personal_agenda (agenda_event_invita
// It allows to other users to subscribe for events. Requires DB changes:
/*
ALTER TABLE personal_agenda ADD subscription_visibility INT DEFAULT 0 NOT NULL, ADD max_subscriptions INT DEFAULT 0 NOT NULL;
ALTER TABLE personal_agenda ADD subscription_visibility INT DEFAULT 0 NOT NULL, ADD subscription_item_id INT DEFAULT NULL;
ALTER TABLE agenda_event_invitee ADD type VARCHAR(255) NOT NULL;
ALTER TABLE agenda_event_invitation ADD type VARCHAR(255) NOT NULL, ADD max_attendees INT DEFAULT 0;
UPDATE agenda_event_invitation SET type = 'invitation';
UPDATE agenda_event_invitee SET type = 'invitee';
*/
// Then uncomment the "use EventSubscribableTrait;" line in the PersonalAgenda class.
//$_configuration['agenda_event_subscriptions'] = false;

@ -942,10 +942,6 @@ $(function() {
});
{% endif %}
{% if agenda_event_subscriptions and 'personal' == type %}
$('#simple_subscriptions').html(showSubcriptionsContainer(calEvent));
{% endif %}
var buttons = {
'{{"ExportiCalConfidential"|get_lang}}' : function() {
url = "ical_export.php?id=" + calEvent.id+'&course_id='+calEvent.course_id+"&class=confidential";
@ -962,19 +958,51 @@ $(function() {
};
{% if agenda_collective_invitations and 'personal' == type %}
buttons['{{ "Delete"|get_lang }}'] = function () {
$.ajax({
url: delete_url,
success:function() {
calendar.fullCalendar('removeEvents',
calEvent
);
calendar.fullCalendar('refetchEvents');
calendar.fullCalendar('rerenderEvents');
$("#simple-dialog-form").dialog('close');
if (!calEvent.subscription_visibility) {
buttons['{{ "Delete"|get_lang }}'] = function () {
$.ajax({
url: delete_url,
success:function() {
calendar.fullCalendar('removeEvents',
calEvent
);
calendar.fullCalendar('refetchEvents');
calendar.fullCalendar('rerenderEvents');
$("#simple-dialog-form").dialog('close');
}
});
};
}
{% endif %}
{% if agenda_event_subscriptions and 'personal' == type %}
$('#simple_subscriptions').html(showSubcriptionsContainer(calEvent));
if (calEvent.subscription_visibility > 0) {
if (calEvent.user_is_subscribed) {
buttons["{{ 'Unsubscribe'|get_lang }}"] = function () {
$.ajax({
url: '{{ web_agenda_ajax_url }}&a=event_unsubscribe&id=' + calEvent.id,
success:function() {
calendar.fullCalendar('refetchEvents');
//calendar.fullCalendar('rerenderEvents');
$("#simple-dialog-form").dialog('close');
}
});
};
} else if (calEvent.can_subscribe) {
buttons["{{ 'Subscribe'|get_lang }}"] = function () {
$.ajax({
url: '{{ web_agenda_ajax_url }}&a=event_subscribe&id=' + calEvent.id,
success:function() {
calendar.fullCalendar('refetchEvents');
//calendar.fullCalendar('rerenderEvents');
$("#simple-dialog-form").dialog('close');
}
});
}
});
};
}
}
{% endif %}
if ('session_subscription' === calEvent.type) {
@ -1027,7 +1055,7 @@ $(function() {
{{ agenda_reminders_js }}
function showSubcriptionsContainer (calEvent) {
if (0 === calEvent.subscription_visibility) {
if (calEvent.invitees.length || !calEvent.subscription_visibility) {
return '';
}
@ -1047,10 +1075,20 @@ $(function() {
html += '</dd>';
if (0 <= calEvent.max_subscriptions) {
html += "<dt>{{ 'MaxSubcriptions'|get_lang }}</dt>";
html += "<dt>{{ 'MaxSubscriptions'|get_lang }}</dt>";
html += '<dd>' + calEvent.max_subscriptions + '</dd>';
}
html += "<dt>{{ 'Subscriptions'|get_lang }}</dt><dd>" + calEvent.count_subscribers + "</dd>";
if (calEvent.subscribers) {
html += '<dt>{{ 'Subscribers'|get_lang }}</dt><dd>';
html += calEvent.subscribers
.map(function (invitee) { return invitee.name; })
.join('<br>');
html += '</dd>'
}
html += '</dl>';
return html;
@ -1143,10 +1181,7 @@ $(function() {
{% endif %}
{% if agenda_event_subscriptions and 'personal' == type %}
<div class="form-group">
<label class="col-sm-3 control-label">{{ 'Subscriptions' }}</label>
<div class="col-sm-9" id="simple_subscriptions"></div>
</div>
<div class="form-group" id="simple_subscriptions"></div>
{% endif %}
</form>
</div>

@ -96,10 +96,19 @@ class AgendaEventInvitation
return $this->creator;
}
public function setCreator(User $creator): AgendaEventInvitation
public function setCreator(User $creator): self
{
$this->creator = $creator;
return $this;
}
public function hasUserAsInvitee(User $user): bool
{
return $this->invitees->exists(
function (int $key, AgendaEventInvitee $invitee) use ($user) {
return $invitee->getUser() === $user;
}
);
}
}

@ -12,6 +12,12 @@ use Doctrine\ORM\Mapping as ORM;
* @ORM\Table(name="agenda_event_invitee")
* Add @ to the next lineactivating the agenda_collective_invitations configuration setting.
* ORM\Entity()
* ORM\InheritanceType("SINGLE_TABLE")
* ORM\DiscriminatorColumn(name="type", type="string")
* ORM\DiscriminatorMap({
* "invitee" = "Chamilo\CoreBundle\Entity\AgendaEventInvitee",
* "subscriber" = "Chamilo\CoreBundle\Entity\AgendaEventSubscriber"
* })
*/
class AgendaEventInvitee
{

@ -0,0 +1,14 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ORM\Entity()
*/
class AgendaEventSubscriber extends AgendaEventInvitee
{
}

@ -4,9 +4,33 @@
namespace Chamilo\CoreBundle\Entity;
class AgendaEventSubscription
use Doctrine\ORM\Mapping as ORM;
/**
* ORM\Entity()
*/
class AgendaEventSubscription extends AgendaEventInvitation
{
public const SUBSCRIPTION_NO = 0;
public const SUBSCRIPTION_ALL = 1;
public const SUBSCRIPTION_CLASS = 2;
/**
* @var int
*
* @ORM\Column(name="max_attendees", type="integer", nullable=false, options={"default": 0})
*/
protected $maxAttendees = 0;
public function getMaxAttendees(): int
{
return $this->maxAttendees;
}
public function setMaxAttendees(int $maxAttendees): self
{
$this->maxAttendees = $maxAttendees;
return $this;
}
}

@ -4,6 +4,7 @@
namespace Chamilo\CoreBundle\Entity\Repository;
use Chamilo\CoreBundle\Entity\AgendaEventSubscription;
use Chamilo\CoreBundle\Entity\PersonalAgenda;
use Chamilo\UserBundle\Entity\User;
use Doctrine\ORM\EntityRepository;
@ -25,6 +26,16 @@ class PersonalAgendaRepository extends EntityRepository
)
;
if (api_get_configuration_value('agenda_event_subscriptions')) {
$qb
->andWhere(
$qb->expr()->not(
$qb->expr()->isInstanceOf('i', AgendaEventSubscription::class)
)
)
;
}
$params = [
'user' => $user,
];

@ -18,9 +18,9 @@ trait EventSubscribableTrait
/**
* @var int
*
* @ORM\Column(name="max_subscriptions", type="integer", options={"default": 0})
* @ORM\Column(name="subscription_item_id", type="integer", nullable=true)
*/
protected $maxSubscriptions = 0;
protected $subscriptionItemId = null;
public function getSubscriptionVisibility(): int
{
@ -34,14 +34,14 @@ trait EventSubscribableTrait
return $this;
}
public function getMaxSubscriptions(): int
public function getSubscriptionItemId(): ?int
{
return $this->maxSubscriptions;
return $this->subscriptionItemId;
}
public function setMaxSubscriptions(int $maxSubscriptions): self
public function setSubscriptionItemId(?int $subscriptionItemId): self
{
$this->maxSubscriptions = $maxSubscriptions;
$this->subscriptionItemId = $subscriptionItemId;
return $this;
}

Loading…
Cancel
Save