Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions _routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
$app->get(
'/bookings/{event:guess|all|\d+}[/{option:page|order|clear_filter}/{value:\d+}]',
[BookingsController::class, 'listBookings']
)->setName('events_bookings');
)->setName('events_bookings')->add(Authenticate::class);

//bookings list filtering
$app->post(
Expand Down Expand Up @@ -158,7 +158,7 @@
$app->post(
'/activity/add',
[ActivitiesController::class, 'doAdd']
)->setName('events_storeactivity_add');
)->setName('events_storeactivity_add')->add(Authenticate::class);

$app->post(
'/activity/store',
Expand Down
5 changes: 4 additions & 1 deletion calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ $(function() {
} else {
_modal_actions[1].click = _booking_action;
}
var _elt = $('<div class="ui tiny modal"><div class="header">' + _infos.name + ' (' + _infos.begin_date_fmt + ' - ' + _infos.end_date_fmt + ')</div><div class="content">' + _infos.description + '</div></div>');
//description is built and escaped server side, other values must be displayed as text
var _elt = $('<div class="ui tiny modal"><div class="header"></div><div class="content"></div></div>');
_elt.find('.header').text(_infos.name + ' (' + _infos.begin_date_fmt + ' - ' + _infos.end_date_fmt + ')');
_elt.find('.content').html(_infos.description);
_elt.appendTo('body');
_elt.modal({
onApprove: function() {
Expand Down
81 changes: 72 additions & 9 deletions lib/GaletteEvents/Booking.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Galette\Core\Login;
use Galette\Entity\Adherent;
use Galette\Entity\PaymentType;
use Galette\Repository\Groups;
use Analog\Analog;

/**
Expand Down Expand Up @@ -174,8 +175,12 @@ public function check(array $values): array|bool
if (!isset($values['event']) || empty($values['event']) || $values['event'] == -1) {
$this->errors[] = _T('Event is mandatory', 'events');
} else {
$event_changed = $this->getId() === null || $this->getEventId() !== (int)$values['event'];
$this->event = (int)$values['event'];
$event = $this->getEvent();
if ($event_changed && !$this->canBook($event)) {
$this->errors[] = _T('This event cannot be booked.', 'events');
}
$activities = $event->getActivities();
foreach ($activities as $aid => $entry) {
if (
Expand Down Expand Up @@ -236,18 +241,28 @@ public function check(array $values): array|bool
}

//booking information
if (!isset($values['member']) || empty($values['member'])) {
if (!$this->login->isAdmin() && !$this->login->isStaff() && !$this->login->isGroupManager()) {
//members book for themselves only
$this->member = $this->login->id;
} elseif (!isset($values['member']) || empty($values['member'])) {
$this->errors[] = _T('Member is mandatory', 'events');
} else {
$member = (int)$values['member'];
if (
$this->login->isAdmin()
|| $this->login->isStaff()
|| $this->login->isGroupManager()
!$this->login->isAdmin()
&& !$this->login->isStaff()
&& $member !== $this->login->id
&& $member !== $this->getMemberId()
) {
$this->errors[] = _T('Member is mandatory', 'events');
} else {
$this->member = $this->login->id;
//group managers book for members of the groups they manage, on events of those groups
$group = $this->getEvent()?->getGroup() ?: null;
if (!(new Adherent($this->zdb, $member))->canShow($this->login)) {
$this->errors[] = _T("- Please select a member from a group you manage.");
} elseif ($group === null || !$this->login->isGroupManager($group)) {
$this->errors[] = _T('You can only book other members on events of groups you manage.', 'events');
}
}
} else {
$this->member = (int)$values['member'];
$this->member = $member;
}

if (isset($values['number_people'])) {
Expand Down Expand Up @@ -735,6 +750,54 @@ public function getActivities(): array
return $this->activities;
}

/**
* Can current logged-in user book an event
*
* Admins and staff members can book any event, others open events
* that are public or restricted to one of their groups.
*
* @param Event $event Event
*/
private function canBook(Event $event): bool
{
if ($this->login->isAdmin() || $this->login->isStaff()) {
return $event->getId() !== null;
}

if ($event->getId() === null || !$event->isOpen()) {
return false;
}

//public events have no group, loaded as 0
$group = $event->getGroup() ?: null;
return $group === null
|| $this->login->isGroupManager($group)
|| in_array($group, array_map('intval', Groups::loadGroups($this->login->id, false, false)), true);
}

/**
* Can current logged-in user edit booking
*
* Admins and staff members can edit any booking, members their own ones,
* and group managers the ones on events of the groups they manage.
*
* @param Login $login Login instance
*/
public function canEdit(Login $login): bool
{
if ($login->isAdmin() || $login->isStaff()) {
return true;
}

if ($this->getMemberId() !== null && $this->getMemberId() === $login->id) {
return true;
}

//public events have no group, loaded as 0
$group = $this->getEvent()?->getGroup() ?: null;
return $group !== null && $login->isGroupManager($group);
}

/**
* Get row class related to current fee status
*
Expand Down
74 changes: 74 additions & 0 deletions lib/GaletteEvents/Controllers/Crud/BookingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace GaletteEvents\Controllers\Crud;

use Analog\Analog;
use Galette\Entity\Adherent;
use Galette\Repository\Groups;
use Galette\Repository\Members;
Expand Down Expand Up @@ -220,6 +221,22 @@ public function handleBatch(Request $request, Response $response): Response
{
$post = $request->getParsedBody();

foreach (['mailing', 'csv', 'csvbooking', 'labels'] as $action) {
if (isset($post[$action]) && !$this->canBatch($action)) {
Analog::log(
'Logged in member ' . $this->login->login
. ' has tried to run "' . $action . '" batch action on bookings'
. ' without the right to do so.',
Analog::WARNING
);
return $this->redirectWithErrors(
response: $response,
errors: [_T("You do not have permission for requested URL.")],
redirect_url: $this->routeparser->urlFor('events_bookings', ['event' => 'all'])
);
}
}

if (isset($post['entries_sel'])) {
if (isset($this->session->filter_bookings)) {
$filters = clone $this->session->filter_bookings;
Expand All @@ -230,11 +247,21 @@ public function handleBatch(Request $request, Response $response): Response
//$this->session->filter_bookings = $filters;
$filters->selected = $post['entries_sel'];

//selection is restricted to bookings current logged-in user can list
$bookings = new Bookings($this->zdb, $this->login, $filters);
$members = [];
foreach ($bookings->getList() as $booking) {
$members[] = $booking->getMemberId();
}
if (count($members) === 0) {
$this->flash->addMessage(
'error_detected',
_T("No booking was selected, please check at least one.", "events")
);
return $response
->withStatus(301)
->withHeader('Location', $this->routeparser->urlFor('events_events'));
}
$mfilter = new MembersList();
$mfilter->selected = $members;

Expand Down Expand Up @@ -302,6 +329,25 @@ public function handleBatch(Request $request, Response $response): Response
->withHeader('Location', $this->routeparser->urlFor('events_events'));
}

/**
* Can current logged-in user run a batch action on bookings
*
* Group managers run exports and mailings as core preferences allow them to.
*
* @param string $action Batch action
*/
private function canBatch(string $action): bool
{
if ($this->login->isAdmin() || $this->login->isStaff()) {
return true;
}

if ($action === 'mailing') {
return (bool)$this->preferences->pref_bool_groupsmanagers_mailings;
}
return (bool)$this->preferences->pref_bool_groupsmanagers_exports;
}

// /CRUD - Read
// CRUD - Update

Expand All @@ -328,6 +374,10 @@ public function edit(Request $request, Response $response, ?int $id = null, stri
$booking->load($id);
}

if ($booking->getId() !== null && !$booking->canEdit($this->login)) {
return $this->redirectForbidden($response, $booking);
}

// template variable declaration
$title = _T("Booking", "events");
if ($booking->getId() != '') {
Expand Down Expand Up @@ -422,6 +472,10 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st
$booking->load((int)$post['id']);
}

if ($booking->getId() !== null && !$booking->canEdit($this->login)) {
return $this->redirectForbidden($response, $booking);
}

if (isset($post['cancel'])) {
$redirect_url = $this->routeparser->urlFor(
'events_bookings',
Expand Down Expand Up @@ -527,6 +581,26 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st
->withHeader('Location', $redirect_url);
}

/**
* Redirect when current logged-in user cannot edit a booking
*
* @param Booking $booking Booking
*/
private function redirectForbidden(Response $response, Booking $booking): Response
{
Analog::log(
'Logged in member ' . $this->login->login
. ' has tried to edit booking #' . $booking->getId()
. ' without the right to do so.',
Analog::WARNING
);
return $this->redirectWithErrors(
response: $response,
errors: [_T("You do not have permission for requested URL.")],
redirect_url: $this->routeparser->urlFor('events_bookings', ['event' => 'all'])
);
}

// /CRUD - Update
// CRUD - Delete

Expand Down
24 changes: 23 additions & 1 deletion lib/GaletteEvents/Controllers/CsvController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace GaletteEvents\Controllers;

use Analog\Analog;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
use Galette\IO\Csv;
Expand All @@ -32,12 +33,33 @@ class CsvController extends \Galette\Controllers\CsvController
*/
public function bookingsExport(Request $request, Response $response, ?int $id = null): Response
{
if (
!$this->login->isAdmin()
&& !$this->login->isStaff()
&& !$this->preferences->pref_bool_groupsmanagers_exports
) {
Analog::log(
'Logged in member ' . $this->login->login
. ' has tried to export bookings without the right to do so.',
Analog::WARNING
);
return $this->redirectWithErrors(
response: $response,
errors: [_T("You do not have permission for requested URL.")],
redirect_url: $this->routeparser->urlFor('events_bookings', ['event' => 'all'])
);
}

$post = $request->getParsedBody();
$get = $request->getQueryParams();
$csv = new CsvOut();

//filters come from bookings list, or from its batch actions
$session_var = $post['session_var'] ?? $get['session_var'] ?? 'filter_bookings';
if (isset($this->session->$session_var) && $id === null) {
if (!in_array($session_var, ['filter_bookings', 'plugin-events-bookings'], true)) {
$session_var = 'filter_bookings';
}
if ($id === null && ($this->session->$session_var ?? null) instanceof BookingsList) {
$filters = $this->session->$session_var;
} else {
$filters = new BookingsList();
Expand Down
26 changes: 10 additions & 16 deletions lib/GaletteEvents/PluginGaletteEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,20 @@ public function getMenus(): array
'name' => 'events_calendar',
]
],
[
'label' => _T('Bookings', 'events'),
'route' => [
'name' => 'events_bookings',
'args' => [
'event' => 'all'
],
'aliases' => ['events_booking_add', 'events_booking_edit']
]
],
]
];
}

$menus['plugin_events']['items'] = array_merge(
$menus['plugin_events']['items'],
[
[
'label' => _T('Bookings', 'events'),
'route' => [
'name' => 'events_bookings',
'args' => [
'event' => 'all'
],
'aliases' => ['events_booking_add', 'events_booking_edit']
]
]
]
);

if ($login->isAdmin() || $login->isStaff()) {
$menus['plugin_events']['items'] = array_merge(
$menus['plugin_events']['items'],
Expand Down
Loading
Loading