From 6c617c57985d9891514c8c154697e9c115d02c65 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Fri, 31 Mar 2023 11:58:41 -0500 Subject: [PATCH 01/10] Calendar: Add agenda_event_subscriptions configuration setting - refs BT#20637 It allows to other users to subscribe for events. Requires DB changes: ```sql ALTER TABLE personal_agenda ADD subscription_visibility INT DEFAULT 0 NOT NULL, ADD max_subscriptions INT DEFAULT 0 NOT NULL; ``` Then uncomment the "use EventSubscribableTrait;" line in the PersonalAgenda class. --- main/calendar/agenda.php | 12 +- main/calendar/agenda_js.php | 33 +++++ main/inc/ajax/agenda.ajax.php | 6 +- main/inc/lib/agenda.lib.php | 117 ++++++++++++++++-- main/inc/lib/template.lib.php | 1 + main/install/configuration.dist.php | 8 ++ main/template/default/agenda/month.tpl | 58 +++++++++ .../Entity/AgendaEventSubscription.php | 12 ++ .../CoreBundle/Entity/PersonalAgenda.php | 3 + .../Traits/EventSubscribableTrait.php | 48 +++++++ 10 files changed, 285 insertions(+), 13 deletions(-) create mode 100644 src/Chamilo/CoreBundle/Entity/AgendaEventSubscription.php create mode 100644 src/Chamilo/CoreBundle/Traits/EventSubscribableTrait.php diff --git a/main/calendar/agenda.php b/main/calendar/agenda.php index a3bc2c04e1..2ffd706966 100755 --- a/main/calendar/agenda.php +++ b/main/calendar/agenda.php @@ -174,6 +174,8 @@ if ($allowToEdit) { $notificationPeriod = $_REQUEST['notification_period'] ?? []; $careerId = $_REQUEST['career_id'] ?? 0; $promotionId = $_REQUEST['promotion_id'] ?? 0; + $subscriptionVisibility = (int) ($_REQUEST['subscription_visibility'] ?? 0); + $maxSubscriptions = (int) ($_REQUEST['max_subscriptions'] ?? 0); $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; @@ -194,7 +196,9 @@ if ($allowToEdit) { $values['collective'] ?? false, $reminders, (int) $careerId, - (int) $promotionId + (int) $promotionId, + $subscriptionVisibility, + $maxSubscriptions ); if (!empty($values['repeat']) && !empty($eventId)) { @@ -254,6 +258,8 @@ if ($allowToEdit) { $notificationPeriod = $_REQUEST['notification_period'] ?? []; $careerId = $_REQUEST['career_id'] ?? 0; $promotionId = $_REQUEST['promotion_id'] ?? 0; + $subscriptionVisibility = (int) ($_REQUEST['subscription_visibility'] ?? 0); + $maxSubscriptions = (int) ($_REQUEST['max_subscriptions'] ?? 0); $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; @@ -307,7 +313,9 @@ if ($allowToEdit) { $values['collective'] ?? false, $reminders, (int) $careerId, - (int) $promotionId + (int) $promotionId, + $subscriptionVisibility, + $maxSubscriptions ); if (!empty($values['repeat']) && !empty($eventId)) { diff --git a/main/calendar/agenda_js.php b/main/calendar/agenda_js.php index 5a21831ed7..5eec4c61aa 100755 --- a/main/calendar/agenda_js.php +++ b/main/calendar/agenda_js.php @@ -3,6 +3,8 @@ /* For licensing terms, see /license.txt */ // use anonymous mode when accessing this course tool +use Chamilo\CoreBundle\Entity\AgendaEventSubscription; + $use_anonymous = true; $typeList = ['personal', 'course', 'admin', 'platform']; // Calendar type @@ -292,6 +294,37 @@ if (api_get_configuration_value('agenda_collective_invitations') && 'personal' = $form->addCheckBox('collective', '', get_lang('IsItEditableByTheInvitees')); } +if ( + api_is_platform_admin() + && api_get_configuration_value('agenda_event_subscriptions') && 'personal' === $agenda->type +) { + $form->addHeader(get_lang('Subscriptions')); + $form->addHtml('
'); + $form->addSelect( + 'subscription_visibility', + get_lang('AllowSubscriptions'), + [ + AgendaEventSubscription::SUBSCRIPTION_NO => get_lang('No'), + AgendaEventSubscription::SUBSCRIPTION_ALL => get_lang('AllUsersOfThePlatform'), + ], + [ + 'onchange' => 'document.getElementById(\'max_subscriptions\').disabled = this.value == 0;', + ] + ); + $form->addNumeric( + 'max_subscriptions', + ['', get_lang('MaxSubscriptionsLeaveEmptyToNotLimit')], + [ + 'disabled' => 'disabled', + 'step' => 1, + 'min' => 0, + 'value' => 0, + ] + ); + $form->addHtml('
'); + $form->addHtml(''); +} + if (api_get_configuration_value('agenda_reminders')) { $tpl->assign( 'agenda_reminders_js', diff --git a/main/inc/ajax/agenda.ajax.php b/main/inc/ajax/agenda.ajax.php index 71353b7d69..e90c7d9680 100755 --- a/main/inc/ajax/agenda.ajax.php +++ b/main/inc/ajax/agenda.ajax.php @@ -51,6 +51,8 @@ switch ($action) { $notificationPeriod = $_REQUEST['notification_period'] ?? []; $careerId = $_REQUEST['career_id'] ?? 0; $promotionId = $_REQUEST['promotion_id'] ?? 0; + $subscriptionVisibility = (int) ($_REQUEST['subscription_visibility'] ?? 0); + $maxSubscriptions = (int) ($_REQUEST['max_subscriptions'] ?? 0); $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; @@ -71,7 +73,9 @@ switch ($action) { $isCollective, $reminders, (int) $careerId, - (int) $promotionId + (int) $promotionId, + $subscriptionVisibility, + $maxSubscriptions ); echo $eventId; diff --git a/main/inc/lib/agenda.lib.php b/main/inc/lib/agenda.lib.php index d284c28a18..f6da33237e 100644 --- a/main/inc/lib/agenda.lib.php +++ b/main/inc/lib/agenda.lib.php @@ -4,6 +4,7 @@ use Chamilo\CoreBundle\Entity\AgendaEventInvitation; use Chamilo\CoreBundle\Entity\AgendaEventInvitee; +use Chamilo\CoreBundle\Entity\AgendaEventSubscription; use Chamilo\CoreBundle\Entity\AgendaReminder; use Chamilo\UserBundle\Entity\User; @@ -254,7 +255,9 @@ class Agenda bool $isCollective = false, array $reminders = [], int $careerId = 0, - int $promotionId = 0 + int $promotionId = 0, + int $subscriptionVisibility = 0, + int $maxSubscriptions = 0 ) { $start = api_get_utc_datetime($start); $end = api_get_utc_datetime($end); @@ -281,6 +284,17 @@ class Agenda if (api_get_configuration_value('agenda_collective_invitations')) { Agenda::saveCollectiveProperties($inviteesList, $isCollective, $id); } + + 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]] + ); + } break; case 'course': $attributes = [ @@ -865,7 +879,9 @@ class Agenda bool $isCollective = false, array $remindersList = [], int $careerId = 0, - int $promotionId = 0 + int $promotionId = 0, + int $subscriptionVisibility = 0, + int $maxSubscriptions = 0 ) { $id = (int) $id; $start = api_get_utc_datetime($start); @@ -909,6 +925,17 @@ class Agenda if (api_get_configuration_value('agenda_collective_invitations')) { Agenda::saveCollectiveProperties($inviteesList, $isCollective, $id); } + + 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]] + ); + } break; case 'course': $eventInfo = $this->get_event($id); @@ -1759,6 +1786,7 @@ class Agenda $endCondition = ''; $agendaCollectiveInvitations = api_get_configuration_value('agenda_collective_invitations'); + $agendaEventSubscriptions = api_get_configuration_value('agenda_event_subscriptions'); if ($start !== 0) { $startDate = api_get_utc_datetime($start, true, true); @@ -1770,8 +1798,37 @@ class Agenda } $user_id = api_get_user_id(); + $userCondition = "user = $user_id"; + + if ($agendaEventSubscriptions) { + $objGroup = new UserGroup(); + $groupList = $objGroup->get_groups_by_user($user_id); + + $userCondition = "( + $userCondition + OR ( + subscription_visibility = ".AgendaEventSubscription::SUBSCRIPTION_ALL; + + if ($groupList) { + $userCondition .= " + OR ( + subscription_visibility = ".AgendaEventSubscription::SUBSCRIPTION_CLASS." + AND subscription_item_id IN (".implode(', ', array_keys($groupList)).") + ) + "; + } + + $userCondition .= " + ) + ) + "; + } + $sql = "SELECT * FROM ".$this->tbl_personal_agenda." - WHERE user = $user_id $startCondition $endCondition"; + WHERE $userCondition + $startCondition + $endCondition + "; $result = Database::query($sql); $my_events = []; @@ -1782,7 +1839,7 @@ class Agenda $event['title'] = $row['title']; $event['className'] = 'personal'; $event['borderColor'] = $event['backgroundColor'] = $this->event_personal_color; - $event['editable'] = true; + $event['editable'] = $user_id === (int) $row['user']; $event['sent_to'] = get_lang('Me'); $event['type'] = 'personal'; @@ -1806,6 +1863,11 @@ class Agenda $event['invitees'] = self::getInviteesForPersonalEvent($row['id']); } + if ($agendaEventSubscriptions) { + $event['subscription_visibility'] = (int) $row['subscription_visibility']; + $event['max_subscriptions'] = (int) $row['max_subscriptions']; + } + $my_events[] = $event; $this->events[] = $event; } @@ -2680,6 +2742,9 @@ class Agenda $action = isset($params['action']) ? Security::remove_XSS($params['action']) : null; $id = isset($params['id']) ? (int) $params['id'] : 0; + $em = Database::getManager(); + $personalEvent = $id ? $em->find('ChamiloCoreBundle:PersonalAgenda', $id) : null; + $url = api_get_self().'?action='.$action.'&id='.$id.'&type='.$this->type; if ($this->type == 'course') { $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&id='.$id.'&type='.$this->type; @@ -2894,14 +2959,11 @@ class Agenda $agendaCollectiveInvitations = api_get_configuration_value('agenda_collective_invitations'); if ($agendaCollectiveInvitations && 'personal' === $this->type) { - $em = Database::getManager(); - $invitees = []; $isCollective = false; - if ($id) { - $event = $em->find('ChamiloCoreBundle:PersonalAgenda', $id); - $eventInvitation = $event->getInvitation(); + if ($personalEvent) { + $eventInvitation = $personalEvent->getInvitation(); if ($eventInvitation) { foreach ($eventInvitation->getInvitees() as $invitee) { @@ -2911,7 +2973,7 @@ class Agenda } } - $isCollective = $event->isCollective(); + $isCollective = $personalEvent->isCollective(); } $form->addSelectAjax( @@ -2924,6 +2986,7 @@ class Agenda ] ); $form->addCheckBox('collective', '', get_lang('IsItEditableByTheInvitees')); + $form->addHtml('
'); $params['invitees'] = array_keys($invitees); $params['collective'] = $isCollective; @@ -2941,6 +3004,40 @@ class Agenda $form->addHtml('
'); } + if (api_is_platform_admin() + && true === api_get_configuration_value('agenda_event_subscriptions') + ) { + $form->addSelect( + 'subscription_visibility', + get_lang('AllowSubscriptions'), + [ + AgendaEventSubscription::SUBSCRIPTION_NO => get_lang('No'), + AgendaEventSubscription::SUBSCRIPTION_ALL => get_lang('AllUsersOfThePlatform'), + ] + ); + $form->addNumeric( + 'max_subscriptions', + ['', get_lang('MaxSubscriptionsLeaveEmptyToNotLimit')], + [ + 'disabled' => 'disabled', + 'step' => 1, + 'min' => 0, + 'value' => 0, + ] + ); + $form->addHtml(" + "); + $form->addHtml('
'); + } + if (api_get_configuration_value('allow_careers_in_global_agenda') && 'admin' === $this->type) { Career::addCareerFieldsToForm($form); $form->addHtml('
'); diff --git a/main/inc/lib/template.lib.php b/main/inc/lib/template.lib.php index 3677014057..200d46055f 100755 --- a/main/inc/lib/template.lib.php +++ b/main/inc/lib/template.lib.php @@ -200,6 +200,7 @@ class Template $functions = [ ['name' => 'get_tutors_names', 'callable' => 'Template::returnTutorsNames'], ['name' => 'get_teachers_names', 'callable' => 'Template::returnTeachersNames'], + ['name' => 'api_is_platform_admin', 'callable' => 'api_is_platform_admin'], ]; foreach ($functions as $function) { diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index 42c54d3472..8dfe1bc0c8 100644 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -462,6 +462,14 @@ CREATE UNIQUE INDEX UNIQ_D8612460AF68C6B ON personal_agenda (agenda_event_invita // Then add the "@" symbol to AgendaEventInvitation and AgendaEventInvitee classes in the ORM\Entity() line. // Then uncomment the "use EventCollectiveTrait;" line in the PersonalAgenda class. //$_configuration['agenda_collective_invitations'] = false; + +// 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; +*/ +// Then uncomment the "use EventSubscribableTrait;" line in the PersonalAgenda class. +//$_configuration['agenda_event_subscriptions'] = false; + // Enable reminders for agenda events. Requires database changes: /* CREATE TABLE agenda_reminder (id BIGINT AUTO_INCREMENT NOT NULL, type VARCHAR(255) NOT NULL, event_id INT NOT NULL, date_interval VARCHAR(255) NOT NULL COMMENT '(DC2Type:dateinterval)', sent TINYINT(1) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB; diff --git a/main/template/default/agenda/month.tpl b/main/template/default/agenda/month.tpl index 536d787f49..0720b578c6 100755 --- a/main/template/default/agenda/month.tpl +++ b/main/template/default/agenda/month.tpl @@ -1,4 +1,5 @@ {% set agenda_collective_invitations = 'agenda_collective_invitations'|api_get_configuration_value %} +{% set agenda_event_subscriptions = 'agenda_event_subscriptions'|api_get_configuration_value %} {% set agenda_reminders = 'agenda_reminders'|api_get_configuration_value %} {% set career_in_global_events = 'allow_careers_in_global_agenda'|api_get_configuration_value %} @@ -340,6 +341,15 @@ $(function() { //Reset the CKEditor content that persist in memory CKEDITOR.instances['content'].setData(''); allFields.removeClass("ui-state-error"); + + $('#add_event_form').get(0).reset(); + + {% if agenda_event_subscriptions and 'personal' == type and api_is_platform_admin() %} + $('#form_subscription_visibility').trigger('change').selectpicker('refresh'); + $('#form_subscriptions_container').show(''); + $('#form_subscriptions_edit').hide().html(''); + {% endif %} + $("#dialog-form").dialog("open"); $("#dialog-form").dialog({ buttons: { @@ -582,6 +592,13 @@ $(function() { } {% endif %} + {% if agenda_event_subscriptions and 'personal' == type and api_is_platform_admin() %} + $('#form_subscriptions_container').hide(); + $('#form_subscriptions_edit') + .html(showSubcriptionsContainer(calEvent)) + .show(); + {% endif %} + {% if agenda_reminders %} $('#notification_list').html('').next('.form-group').hide(); @@ -925,6 +942,10 @@ $(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"; @@ -1004,6 +1025,36 @@ $(function() { }); {{ agenda_reminders_js }} + + function showSubcriptionsContainer (calEvent) { + if (0 === calEvent.subscription_visibility) { + return ''; + } + + var html = ''; + html += '
'; + html += "
{{ 'AllowSubscriptions'|get_lang }}
"; + html += '
'; + + if (1 === calEvent.subscription_visibility) { + html += "{{ 'AllUsersOfThePlatform'|get_lang }}"; + } + + if (2 === calEvent.subscription_visibility) { + html += "{{ 'UsersInsideClass'|get_lang }}"; + } + + html += '
'; + + if (0 <= calEvent.max_subscriptions) { + html += "
{{ 'MaxSubcriptions'|get_lang }}
"; + html += '
' + calEvent.max_subscriptions + '
'; + } + + html += '
'; + + return html; + } }); {{ actions_div }} @@ -1090,6 +1141,13 @@ $(function() { {% endif %} + + {% if agenda_event_subscriptions and 'personal' == type %} +
+ +
+
+ {% endif %} diff --git a/src/Chamilo/CoreBundle/Entity/AgendaEventSubscription.php b/src/Chamilo/CoreBundle/Entity/AgendaEventSubscription.php new file mode 100644 index 0000000000..941de23faf --- /dev/null +++ b/src/Chamilo/CoreBundle/Entity/AgendaEventSubscription.php @@ -0,0 +1,12 @@ +subscriptionVisibility; + } + + public function setSubscriptionVisibility(int $subscriptionVisibility): self + { + $this->subscriptionVisibility = $subscriptionVisibility; + + return $this; + } + + public function getMaxSubscriptions(): int + { + return $this->maxSubscriptions; + } + + public function setMaxSubscriptions(int $maxSubscriptions): self + { + $this->maxSubscriptions = $maxSubscriptions; + + return $this; + } +} From 4682d74927a53cb1fe5566aa00f6823e1ddc6158 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Fri, 31 Mar 2023 17:48:10 -0500 Subject: [PATCH 02/10] Calendar: Allow to subscribe/unsubscribe users to events - refs BT#20637 --- main/inc/ajax/agenda.ajax.php | 26 ++ main/inc/lib/agenda.lib.php | 299 +++++++++++++----- main/install/configuration.dist.php | 6 +- main/template/default/agenda/month.tpl | 79 +++-- .../Entity/AgendaEventInvitation.php | 11 +- .../CoreBundle/Entity/AgendaEventInvitee.php | 6 + .../Entity/AgendaEventSubscriber.php | 14 + .../Entity/AgendaEventSubscription.php | 26 +- .../Repository/PersonalAgendaRepository.php | 11 + .../Traits/EventSubscribableTrait.php | 12 +- 10 files changed, 374 insertions(+), 116 deletions(-) create mode 100644 src/Chamilo/CoreBundle/Entity/AgendaEventSubscriber.php diff --git a/main/inc/ajax/agenda.ajax.php b/main/inc/ajax/agenda.ajax.php index e90c7d9680..21820c91f6 100755 --- a/main/inc/ajax/agenda.ajax.php +++ b/main/inc/ajax/agenda.ajax.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 ''; } diff --git a/main/inc/lib/agenda.lib.php b/main/inc/lib/agenda.lib.php index f6da33237e..7dcafa6c69 100644 --- a/main/inc/lib/agenda.lib.php +++ b/main/inc/lib/agenda.lib.php @@ -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('
'); + 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('
'); - $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 }) "); - $form->addHtml('
'); + + if ($personalEvent) { + $subscribers = array_map( + function (array $subscriberInfo) { + return '
  • '.$subscriberInfo['name'].'
  • '; + }, + self::getInviteesForPersonalEvent($personalEvent->getId(), AgendaEventSubscriber::class) + ); + + $form->addLabel( + get_lang('Subscribers'), + '' + ); + + /** @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); diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index 8dfe1bc0c8..a3547de65f 100644 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -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; diff --git a/main/template/default/agenda/month.tpl b/main/template/default/agenda/month.tpl index 0720b578c6..b7865a269e 100755 --- a/main/template/default/agenda/month.tpl +++ b/main/template/default/agenda/month.tpl @@ -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 += ''; if (0 <= calEvent.max_subscriptions) { - html += "
    {{ 'MaxSubcriptions'|get_lang }}
    "; + html += "
    {{ 'MaxSubscriptions'|get_lang }}
    "; html += '
    ' + calEvent.max_subscriptions + '
    '; } + html += "
    {{ 'Subscriptions'|get_lang }}
    " + calEvent.count_subscribers + "
    "; + + if (calEvent.subscribers) { + html += '
    {{ 'Subscribers'|get_lang }}
    '; + html += calEvent.subscribers + .map(function (invitee) { return invitee.name; }) + .join('
    '); + html += '
    ' + } + html += ''; return html; @@ -1143,10 +1181,7 @@ $(function() { {% endif %} {% if agenda_event_subscriptions and 'personal' == type %} -
    - -
    -
    +
    {% endif %} diff --git a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php index 4ed488621b..1b03154c9e 100644 --- a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php +++ b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php @@ -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; + } + ); + } } diff --git a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitee.php b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitee.php index f417e90fb7..898e991ae2 100644 --- a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitee.php +++ b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitee.php @@ -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 { diff --git a/src/Chamilo/CoreBundle/Entity/AgendaEventSubscriber.php b/src/Chamilo/CoreBundle/Entity/AgendaEventSubscriber.php new file mode 100644 index 0000000000..4ea5e7bb2b --- /dev/null +++ b/src/Chamilo/CoreBundle/Entity/AgendaEventSubscriber.php @@ -0,0 +1,14 @@ +maxAttendees; + } + + public function setMaxAttendees(int $maxAttendees): self + { + $this->maxAttendees = $maxAttendees; + + return $this; + } } diff --git a/src/Chamilo/CoreBundle/Entity/Repository/PersonalAgendaRepository.php b/src/Chamilo/CoreBundle/Entity/Repository/PersonalAgendaRepository.php index 9ab44d34f4..3fc63abb3a 100644 --- a/src/Chamilo/CoreBundle/Entity/Repository/PersonalAgendaRepository.php +++ b/src/Chamilo/CoreBundle/Entity/Repository/PersonalAgendaRepository.php @@ -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, ]; diff --git a/src/Chamilo/CoreBundle/Traits/EventSubscribableTrait.php b/src/Chamilo/CoreBundle/Traits/EventSubscribableTrait.php index 975d2f0dd2..3dc0593047 100644 --- a/src/Chamilo/CoreBundle/Traits/EventSubscribableTrait.php +++ b/src/Chamilo/CoreBundle/Traits/EventSubscribableTrait.php @@ -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; } From ddfe2251668b5d57cd3af87fac6016cd0c608b17 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Mon, 3 Apr 2023 12:50:05 -0500 Subject: [PATCH 03/10] Calendar: Fix form to edit personal event with invitations - refs BT#20637 --- main/inc/lib/agenda.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/inc/lib/agenda.lib.php b/main/inc/lib/agenda.lib.php index 7dcafa6c69..d46c261505 100644 --- a/main/inc/lib/agenda.lib.php +++ b/main/inc/lib/agenda.lib.php @@ -2844,7 +2844,7 @@ class Agenda $id = isset($params['id']) ? (int) $params['id'] : 0; $em = Database::getManager(); - $personalEvent = $id ? $em->find('ChamiloCoreBundle:PersonalAgenda', $id) : null; + $personalEvent = 'personal' === $this->type && $id ? $em->find('ChamiloCoreBundle:PersonalAgenda', $id) : null; $url = api_get_self().'?action='.$action.'&id='.$id.'&type='.$this->type; if ($this->type == 'course') { From e92e2c30b26640288efe0b9b1a151599be47cbea Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Mon, 3 Apr 2023 19:54:25 -0500 Subject: [PATCH 04/10] Calendar: Allow to set subscription visibility to members of usergroup (class) - refs BT#20637 --- main/calendar/agenda.php | 4 + main/calendar/agenda_js.php | 12 ++- main/inc/lib/agenda.lib.php | 121 ++++++++++++++++++------- main/template/default/agenda/month.tpl | 8 +- 4 files changed, 108 insertions(+), 37 deletions(-) diff --git a/main/calendar/agenda.php b/main/calendar/agenda.php index 2ffd706966..d3822b2248 100755 --- a/main/calendar/agenda.php +++ b/main/calendar/agenda.php @@ -175,6 +175,7 @@ if ($allowToEdit) { $careerId = $_REQUEST['career_id'] ?? 0; $promotionId = $_REQUEST['promotion_id'] ?? 0; $subscriptionVisibility = (int) ($_REQUEST['subscription_visibility'] ?? 0); + $subscriptionItemId = isset($_REQUEST['subscription_item']) ? (int) $_REQUEST['subscription_item_id'] : null; $maxSubscriptions = (int) ($_REQUEST['max_subscriptions'] ?? 0); $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; @@ -198,6 +199,7 @@ if ($allowToEdit) { (int) $careerId, (int) $promotionId, $subscriptionVisibility, + $subscriptionItemId, $maxSubscriptions ); @@ -259,6 +261,7 @@ if ($allowToEdit) { $careerId = $_REQUEST['career_id'] ?? 0; $promotionId = $_REQUEST['promotion_id'] ?? 0; $subscriptionVisibility = (int) ($_REQUEST['subscription_visibility'] ?? 0); + $subscriptionItemId = isset($_REQUEST['subscription_item']) ? (int) $_REQUEST['subscription_item'] : null; $maxSubscriptions = (int) ($_REQUEST['max_subscriptions'] ?? 0); $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; @@ -315,6 +318,7 @@ if ($allowToEdit) { (int) $careerId, (int) $promotionId, $subscriptionVisibility, + $subscriptionItemId, $maxSubscriptions ); diff --git a/main/calendar/agenda_js.php b/main/calendar/agenda_js.php index 5eec4c61aa..68732afc92 100755 --- a/main/calendar/agenda_js.php +++ b/main/calendar/agenda_js.php @@ -306,9 +306,19 @@ if ( [ AgendaEventSubscription::SUBSCRIPTION_NO => get_lang('No'), AgendaEventSubscription::SUBSCRIPTION_ALL => get_lang('AllUsersOfThePlatform'), + AgendaEventSubscription::SUBSCRIPTION_CLASS => get_lang('UsersInsideClass'), ], [ - 'onchange' => 'document.getElementById(\'max_subscriptions\').disabled = this.value == 0;', + 'onchange' => 'document.getElementById(\'max_subscriptions\').disabled = this.value == 0; document.getElementById(\'form_subscription_item\').disabled = this.value != 2', + ] + ); + $form->addSelectAjax( + 'subscription_item', + get_lang('SocialGroup').' / '.get_lang('Class'), + [], + [ + 'url' => api_get_path(WEB_AJAX_PATH).'usergroup.ajax.php?a=get_class_by_keyword', + 'disabled' => 'disabled', ] ); $form->addNumeric( diff --git a/main/inc/lib/agenda.lib.php b/main/inc/lib/agenda.lib.php index d46c261505..c3cf73e0a6 100644 --- a/main/inc/lib/agenda.lib.php +++ b/main/inc/lib/agenda.lib.php @@ -259,6 +259,7 @@ class Agenda int $careerId = 0, int $promotionId = 0, int $subscriptionVisibility = 0, + ?int $subscriptionItemId = null, int $maxSubscriptions = 0 ) { $start = api_get_utc_datetime($start); @@ -293,6 +294,7 @@ class Agenda $personalEvent = $em->find(PersonalAgenda::class, $id); $personalEvent ->setSubscriptionVisibility($subscriptionVisibility) + ->setSubscriptionItemId($subscriptionItemId ?: null) ; $subscription = (new AgendaEventSubscription()) @@ -893,6 +895,7 @@ class Agenda int $careerId = 0, int $promotionId = 0, int $subscriptionVisibility = 0, + ?int $subscriptionItemId = null, int $maxSubscriptions = 0 ) { $id = (int) $id; @@ -944,6 +947,7 @@ class Agenda $personalEvent = $em->find(PersonalAgenda::class, $id); $personalEvent ->setSubscriptionVisibility($subscriptionVisibility) + ->setSubscriptionItemId($subscriptionItemId ?: null) ; /** @var AgendaEventSubscription $subscription */ @@ -1371,10 +1375,22 @@ class Agenda return; } - if ($subscription->getInvitees()->count() >= $subscription->getMaxAttendees()) { + if ($subscription->getInvitees()->count() >= $subscription->getMaxAttendees() + && $subscription->getMaxAttendees() > 0 + ) { return; } + if (AgendaEventSubscription::SUBSCRIPTION_CLASS === $personalEvent->getSubscriptionVisibility()) { + $objGroup = new UserGroup(); + $groupList = $objGroup->getUserGroupListByUser($currentUser->getId(), UserGroup::NORMAL_CLASS); + $groupIdList = array_column($groupList, 'id'); + + if (!in_array($personalEvent->getSubscriptionItemId(), $groupIdList)) { + return; + } + } + $subscriber = (new AgendaEventSubscriber()) ->setUser($currentUser) ; @@ -1882,9 +1898,10 @@ class Agenda $queryParams['user_id'] = $user_id; $userCondition = "pa.user = :user_id"; + $objGroup = new UserGroup(); + if ($agendaEventSubscriptions) { - $objGroup = new UserGroup(); - $groupList = $objGroup->get_groups_by_user($user_id); + $groupList = $objGroup->getUserGroupListByUser($user_id, UserGroup::NORMAL_CLASS); $userCondition = "( $userCondition @@ -1895,7 +1912,7 @@ class Agenda $userCondition .= " OR ( pa.subscriptionVisibility = ".AgendaEventSubscription::SUBSCRIPTION_CLASS." - AND pa.subscriptionItemId IN (".implode(', ', array_keys($groupList)).") + AND pa.subscriptionItemId IN (".implode(', ', array_column($groupList, 'id')).") ) "; } @@ -1941,24 +1958,30 @@ class Agenda $event['parent_event_id'] = 0; $event['has_children'] = 0; - if ($agendaCollectiveInvitations) { - $event['collective'] = $row->isCollective(); - $event['invitees'] = self::getInviteesForPersonalEvent($row->getId()); - } - - if ($agendaEventSubscriptions) { - /** @var AgendaEventSubscription $subscription */ + if ($agendaCollectiveInvitations || $agendaEventSubscriptions) { $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 ($subscription instanceof AgendaEventSubscription) { + $subscribers = $subscription->getInvitees(); + + $event['subscription_visibility'] = $row->getSubscriptionVisibility(); + $event['max_subscriptions'] = $subscription->getMaxAttendees(); + $event['can_subscribe'] = $subscribers->count() < $subscription->getMaxAttendees() + || $subscription->getMaxAttendees() === 0; + $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); + if ($userIsAdmin) { + $event['subscribers'] = self::getInviteesForPersonalEvent($row->getId(), AgendaEventSubscriber::class); + } + + if (AgendaEventSubscription::SUBSCRIPTION_CLASS === $row->getSubscriptionVisibility()) { + $groupInfo = $objGroup->get($row->getSubscriptionItemId()); + $event['usergroup'] = $groupInfo['name']; + } + } else { + $event['collective'] = $row->isCollective(); + $event['invitees'] = self::getInviteesForPersonalEvent($row->getId()); } } @@ -3112,14 +3135,26 @@ class Agenda if (api_is_platform_admin() && true === api_get_configuration_value('agenda_event_subscriptions') ) { + $form->addHtml('
    '); $form->addSelect( 'subscription_visibility', get_lang('AllowSubscriptions'), [ AgendaEventSubscription::SUBSCRIPTION_NO => get_lang('No'), AgendaEventSubscription::SUBSCRIPTION_ALL => get_lang('AllUsersOfThePlatform'), + AgendaEventSubscription::SUBSCRIPTION_CLASS => get_lang('UsersInsideClass'), + ] + ); + $slctItem = $form->addSelectAjax( + 'subscription_item', + get_lang('SocialGroup').' / '.get_lang('Class'), + [], + [ + 'url' => api_get_path(WEB_AJAX_PATH).'usergroup.ajax.php?a=get_class_by_keyword', + 'disabled' => 'disabled', ] ); + $form->addNumeric( 'max_subscriptions', ['', get_lang('MaxSubscriptionsLeaveEmptyToNotLimit')], @@ -3134,7 +3169,8 @@ class Agenda $(function () { $('#add_event_subscription_visibility') .on('change', function () { - $('#max_subscriptions').prop('disabled', this.value == 0); + $('#max_subscriptions').prop('disabled', this.value == 0); + $('#add_event_subscription_item').prop('disabled', this.value != 2); }) .trigger('change'); }) @@ -3159,6 +3195,16 @@ class Agenda /** @var AgendaEventSubscription $subscription */ $subscription = $personalEvent->getInvitation(); $params['max_subscriptions'] = $subscription->getMaxAttendees(); + + $groupId = $personalEvent->getSubscriptionItemId(); + + if ($groupId) { + $objUserGroup = new UserGroup(); + + $groupInfo = $objUserGroup->get($groupId); + + $slctItem->addOption($groupInfo['name'], $groupId); + } } } @@ -4835,24 +4881,33 @@ class Agenda $event = $em->find('ChamiloCoreBundle:PersonalAgenda', $eventId); - $invitation = new AgendaEventInvitation(); - $invitation->setCreator(api_get_user_entity(api_get_user_id())); + $invitation = $event->getInvitation(); - $event - ->setCollective($isCollective) - ->setInvitation($invitation) - ; + if ($invitation instanceof AgendaEventSubscription) { + return; + } - $em->persist($event); + if (!$invitation) { + $invitation = new AgendaEventInvitation(); + $invitation->setCreator(api_get_user_entity(api_get_user_id())); + + $event->setInvitation($invitation); + } + + $event->setCollective($isCollective); foreach ($inviteeUserList as $inviteeId) { - $invitee = new AgendaEventInvitee(); - $invitee - ->setUser(api_get_user_entity($inviteeId)) - ->setInvitation($invitation) - ; + $userInvitee = api_get_user_entity($inviteeId); - $em->persist($invitee); + if (!$invitation->hasUserAsInvitee($userInvitee)) { + $invitee = new AgendaEventInvitee(); + $invitee + ->setUser($userInvitee) + ->setInvitation($invitation) + ; + + $em->persist($invitee); + } } $em->flush(); diff --git a/main/template/default/agenda/month.tpl b/main/template/default/agenda/month.tpl index b7865a269e..f14adfcfe8 100755 --- a/main/template/default/agenda/month.tpl +++ b/main/template/default/agenda/month.tpl @@ -1055,7 +1055,9 @@ $(function() { {{ agenda_reminders_js }} function showSubcriptionsContainer (calEvent) { - if (calEvent.invitees.length || !calEvent.subscription_visibility) { + if ((calEvent.invitees && calEvent.invitees.length) + || !calEvent.subscription_visibility + ) { return ''; } @@ -1069,12 +1071,12 @@ $(function() { } if (2 === calEvent.subscription_visibility) { - html += "{{ 'UsersInsideClass'|get_lang }}"; + html += "{{ 'UsersInsideClass'|get_lang }}
    " + calEvent.usergroup; } html += ''; - if (0 <= calEvent.max_subscriptions) { + if (calEvent.max_subscriptions) { html += "
    {{ 'MaxSubscriptions'|get_lang }}
    "; html += '
    ' + calEvent.max_subscriptions + '
    '; } From 0684ace026848d9d4dee4cbebe343d25609cd7d1 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Tue, 4 Apr 2023 08:34:18 -0500 Subject: [PATCH 05/10] Minor: Calendar: Improve form - refs BT#20637 --- main/calendar/agenda_js.php | 3 +++ main/inc/lib/agenda.lib.php | 2 +- main/template/default/agenda/month.tpl | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/main/calendar/agenda_js.php b/main/calendar/agenda_js.php index 68732afc92..6ab7acc697 100755 --- a/main/calendar/agenda_js.php +++ b/main/calendar/agenda_js.php @@ -239,6 +239,7 @@ $form = new FormValidator( null, ['id' => 'add_event_form'] ); +$form->addHeader(get_lang('Events')); $form->addHtml('
    '); @@ -282,6 +283,7 @@ if ('course' === $agenda->type) { } if (api_get_configuration_value('agenda_collective_invitations') && 'personal' === $agenda->type) { + $form->addHeader(get_lang('Invitations')); $form->addSelectAjax( 'invitees', get_lang('Invitees'), @@ -352,6 +354,7 @@ if (api_get_configuration_value('allow_careers_in_global_agenda') && 'admin' === } $form->addHtml(''); diff --git a/main/inc/lib/agenda.lib.php b/main/inc/lib/agenda.lib.php index c3cf73e0a6..414a8d3c53 100644 --- a/main/inc/lib/agenda.lib.php +++ b/main/inc/lib/agenda.lib.php @@ -3121,7 +3121,7 @@ class Agenda } if (api_get_configuration_value('agenda_reminders')) { - $form->addHtml('
    '); + $form->addHtml('
    '); if ($id) { $this->addFieldsForRemindersToForm($id, $form); diff --git a/main/template/default/agenda/month.tpl b/main/template/default/agenda/month.tpl index f14adfcfe8..6546076b1a 100755 --- a/main/template/default/agenda/month.tpl +++ b/main/template/default/agenda/month.tpl @@ -668,7 +668,7 @@ $(function() { {% if career_in_global_events %} var $careerFieldParent = $('#career_id').parents('.col-sm-8'); var $promotionFieldParent = $('#promotion_id').parents('.col-sm-8'); - + if ($careerFieldParent.find('#form_career_id_edit').length === 0) { $careerFieldParent.append('

    '); } From 8d988947dc20d6aa34fe47543a36d8f3fa310f45 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Tue, 4 Apr 2023 08:59:37 -0500 Subject: [PATCH 06/10] Calendar: Set instructions to enable agenda_event_subscriptions - refs BT#20637 --- main/install/configuration.dist.php | 8 +++++++- src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index a3547de65f..1e8869962b 100644 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -463,7 +463,9 @@ CREATE UNIQUE INDEX UNIQ_D8612460AF68C6B ON personal_agenda (agenda_event_invita // Then uncomment the "use EventCollectiveTrait;" line in the PersonalAgenda class. //$_configuration['agenda_collective_invitations'] = false; -// It allows to other users to subscribe for events. Requires DB changes: +// It allows to other users to subscribe for events. +// Requires enable agenda_collective_invitations before. +// Requires DB changes: /* 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; @@ -472,6 +474,10 @@ UPDATE agenda_event_invitation SET type = 'invitation'; UPDATE agenda_event_invitee SET type = 'invitee'; */ // Then uncomment the "use EventSubscribableTrait;" line in the PersonalAgenda class. +// Then add the "@" symbol in ORM\InheritanceType, ORM\DiscriminatorColumn and ORM\DiscriminatorMap lines in the AgendaEventInvitation class. +// Then add the "@" symbol in @ORM\Entity line in the AgendaEventSubscription class. +// Then add the "@" symbol in ORM\InheritanceType, ORM\DiscriminatorColumn and ORM\DiscriminatorMap lines in the AgendaEventInvitee class. +// Then add the "@" symbol in @ORM\Entity line in the AgendaEventSubscriber class. //$_configuration['agenda_event_subscriptions'] = false; // Enable reminders for agenda events. Requires database changes: diff --git a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php index 1b03154c9e..3fd41157bc 100644 --- a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php +++ b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php @@ -14,6 +14,12 @@ use Doctrine\ORM\Mapping as ORM; * @ORM\Table(name="agenda_event_invitation") * 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({ + * "invitation" = "Chamilo\CoreBundle\Entity\AgendaEventInvitation", + * "subscription" = "Chamilo\CoreBundle\Entity\AgendaEventSubscription" + * }) */ class AgendaEventInvitation { From 70174b6c40e701d6fb20109907af06ea3ca7c8ca Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 5 Apr 2023 17:55:53 -0500 Subject: [PATCH 07/10] Calendar: Fix save item id - refs BT#20637 --- main/calendar/agenda.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/calendar/agenda.php b/main/calendar/agenda.php index d3822b2248..b20f5b2157 100755 --- a/main/calendar/agenda.php +++ b/main/calendar/agenda.php @@ -175,7 +175,7 @@ if ($allowToEdit) { $careerId = $_REQUEST['career_id'] ?? 0; $promotionId = $_REQUEST['promotion_id'] ?? 0; $subscriptionVisibility = (int) ($_REQUEST['subscription_visibility'] ?? 0); - $subscriptionItemId = isset($_REQUEST['subscription_item']) ? (int) $_REQUEST['subscription_item_id'] : null; + $subscriptionItemId = isset($_REQUEST['subscription_item']) ? (int) $_REQUEST['subscription_item'] : null; $maxSubscriptions = (int) ($_REQUEST['max_subscriptions'] ?? 0); $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; From e58b2ac23d23da819ea21392fdefcaa4e2f63eae Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Mon, 10 Apr 2023 15:20:23 -0500 Subject: [PATCH 08/10] FormValidator: Allow to set select2's dropdownParent property to SelectAjax element - refs BT#20637 --- main/inc/lib/formvalidator/Element/SelectAjax.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/inc/lib/formvalidator/Element/SelectAjax.php b/main/inc/lib/formvalidator/Element/SelectAjax.php index f7d7e5974c..2121b13eb3 100644 --- a/main/inc/lib/formvalidator/Element/SelectAjax.php +++ b/main/inc/lib/formvalidator/Element/SelectAjax.php @@ -23,6 +23,8 @@ class SelectAjax extends HTML_QuickForm_select public function toHtml() { $iso = api_get_language_isocode(api_get_interface_language()); + $dropdownParent = $this->getAttribute('dropdownParent'); + $dropdownParentCondition = $dropdownParent ? "dropdownParent: '$dropdownParent'," : ''; $formatResult = $this->getAttribute('formatResult'); $formatSelection = $this->getAttribute('formatSelection'); $formatCondition = ''; @@ -93,6 +95,7 @@ class SelectAjax extends HTML_QuickForm_select width: '$width', minimumInputLength: '$minimumInputLength', tags: $tags, + $dropdownParentCondition ajax: { url: $url, delay: $delay, @@ -130,6 +133,7 @@ JS; $this->removeAttribute('class'); $this->removeAttribute('url'); $this->removeAttribute('url_function'); + $this->removeAttribute('dropdownParent'); $this->setAttribute('style', 'width: 100%;'); return parent::toHtml().$html; From 782927889dadf2062b38cd38d79f993e175bba67 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Mon, 10 Apr 2023 15:26:46 -0500 Subject: [PATCH 09/10] Calendar: Fix select_ajax to search classes - refs BT#20637 --- main/calendar/agenda_js.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/calendar/agenda_js.php b/main/calendar/agenda_js.php index 6ab7acc697..8733136035 100755 --- a/main/calendar/agenda_js.php +++ b/main/calendar/agenda_js.php @@ -301,7 +301,7 @@ if ( && api_get_configuration_value('agenda_event_subscriptions') && 'personal' === $agenda->type ) { $form->addHeader(get_lang('Subscriptions')); - $form->addHtml('
    '); + $form->addHtml('
    '); $form->addSelect( 'subscription_visibility', get_lang('AllowSubscriptions'), @@ -321,6 +321,7 @@ if ( [ 'url' => api_get_path(WEB_AJAX_PATH).'usergroup.ajax.php?a=get_class_by_keyword', 'disabled' => 'disabled', + 'dropdownParent' => '#form_subscriptions_container', ] ); $form->addNumeric( From e7e352f1e61ac254c201aa6f9c94dcb1812e1fc4 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Mon, 10 Apr 2023 18:48:23 -0500 Subject: [PATCH 10/10] Calendar: Allow to remove subscribers - refs BT#20637 --- main/calendar/agenda.php | 4 ++- main/inc/lib/agenda.lib.php | 34 +++++++++++-------- .../Entity/AgendaEventInvitation.php | 25 ++++++++++++++ 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/main/calendar/agenda.php b/main/calendar/agenda.php index b20f5b2157..1353206619 100755 --- a/main/calendar/agenda.php +++ b/main/calendar/agenda.php @@ -263,6 +263,7 @@ if ($allowToEdit) { $subscriptionVisibility = (int) ($_REQUEST['subscription_visibility'] ?? 0); $subscriptionItemId = isset($_REQUEST['subscription_item']) ? (int) $_REQUEST['subscription_item'] : null; $maxSubscriptions = (int) ($_REQUEST['max_subscriptions'] ?? 0); + $subscribers = $_REQUEST['subscribers'] ?? []; $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; @@ -319,7 +320,8 @@ if ($allowToEdit) { (int) $promotionId, $subscriptionVisibility, $subscriptionItemId, - $maxSubscriptions + $maxSubscriptions, + $subscribers ); if (!empty($values['repeat']) && !empty($eventId)) { diff --git a/main/inc/lib/agenda.lib.php b/main/inc/lib/agenda.lib.php index 414a8d3c53..befc9a7911 100644 --- a/main/inc/lib/agenda.lib.php +++ b/main/inc/lib/agenda.lib.php @@ -896,7 +896,8 @@ class Agenda int $promotionId = 0, int $subscriptionVisibility = 0, ?int $subscriptionItemId = null, - int $maxSubscriptions = 0 + int $maxSubscriptions = 0, + array $subscribers = [] ) { $id = (int) $id; $start = api_get_utc_datetime($start); @@ -945,15 +946,19 @@ class Agenda if (api_get_configuration_value('agenda_event_subscriptions') && api_is_platform_admin()) { $personalEvent = $em->find(PersonalAgenda::class, $id); - $personalEvent - ->setSubscriptionVisibility($subscriptionVisibility) - ->setSubscriptionItemId($subscriptionItemId ?: null) - ; + $personalEvent->setSubscriptionVisibility($subscriptionVisibility); /** @var AgendaEventSubscription $subscription */ $subscription = $personalEvent->getInvitation(); $subscription->setMaxAttendees($subscriptionVisibility > 0 ? $maxSubscriptions : 0); + if ($personalEvent->getSubscriptionItemId() != $subscriptionItemId) { + $personalEvent->setSubscriptionItemId($subscriptionItemId ?: null); + $subscription->removeInvitees(); + } else { + $subscription->removeInviteesNotInIdList($subscribers); + } + $em->flush(); } break; @@ -3178,18 +3183,19 @@ class Agenda "); if ($personalEvent) { - $subscribers = array_map( - function (array $subscriberInfo) { - return '
  • '.$subscriberInfo['name'].'
  • '; - }, - self::getInviteesForPersonalEvent($personalEvent->getId(), AgendaEventSubscriber::class) + $subscribers = self::getInviteesForPersonalEvent($personalEvent->getId(), AgendaEventSubscriber::class); + $subscribers = array_combine( + array_column($subscribers, 'id'), + array_column($subscribers, 'name') ); - $form->addLabel( + $params['subscribers'] = array_keys($subscribers); + + $form->addSelect( + 'subscribers', get_lang('Subscribers'), - '
      ' - .implode(PHP_EOL, $subscribers) - .'
    ' + $subscribers, + ['multiple' => 'multiple'] ); /** @var AgendaEventSubscription $subscription */ diff --git a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php index 3fd41157bc..c51b7938f7 100644 --- a/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php +++ b/src/Chamilo/CoreBundle/Entity/AgendaEventInvitation.php @@ -97,6 +97,13 @@ class AgendaEventInvitation return $this; } + public function removeInvitees(): self + { + $this->invitees = new ArrayCollection(); + + return $this; + } + public function getCreator(): User { return $this->creator; @@ -117,4 +124,22 @@ class AgendaEventInvitation } ); } + + public function removeInviteesNotInIdList(array $idList): self + { + $toRemove = []; + + /** @var AgendaEventInvitee $invitee */ + foreach ($this->invitees as $key => $invitee) { + if (!in_array($invitee->getUser()->getId(), $idList)) { + $toRemove[] = $key; + } + } + + foreach ($toRemove as $key) { + $this->invitees->remove($key); + } + + return $this; + } }