From 5c846fe448c14a2d7a775fa69e9135f87a2314f9 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Sun, 27 Sep 2026 11:38:31 +0200 Subject: [PATCH 1/9] Inject history instead of reading it from globals --- lib/GaletteEvents/Activity.php | 24 +++++++------- lib/GaletteEvents/Booking.php | 27 +++++++++------- .../Controllers/Crud/ActivitiesController.php | 10 +++--- .../Controllers/Crud/BookingsController.php | 18 +++++------ .../Controllers/Crud/EventsController.php | 12 +++---- .../Controllers/CsvController.php | 4 +-- lib/GaletteEvents/Event.php | 27 +++++++++------- lib/GaletteEvents/PluginGaletteEvents.php | 12 ++++--- lib/GaletteEvents/Repository/Activities.php | 8 +++-- lib/GaletteEvents/Repository/Bookings.php | 8 +++-- lib/GaletteEvents/Repository/Events.php | 8 +++-- .../Repository/tests/units/Bookings.php | 6 ++-- .../Repository/tests/units/Events.php | 4 +-- tests/GaletteEvents/tests/units/Activity.php | 16 +++++----- tests/GaletteEvents/tests/units/Booking.php | 20 ++++++------ tests/GaletteEvents/tests/units/Event.php | 32 +++++++++---------- 16 files changed, 129 insertions(+), 107 deletions(-) diff --git a/lib/GaletteEvents/Activity.php b/lib/GaletteEvents/Activity.php index 49e57f5..c8b071c 100644 --- a/lib/GaletteEvents/Activity.php +++ b/lib/GaletteEvents/Activity.php @@ -12,6 +12,7 @@ use ArrayObject; use Galette\Core\Db; +use Galette\Core\History; use Galette\Core\Login; use Analog\Analog; use Laminas\Db\Sql\Expression; @@ -32,6 +33,7 @@ class Activity private Db $zdb; private Login $login; + private History $history; /** @var array */ private array $errors = []; @@ -44,16 +46,18 @@ class Activity /** * Default constructor * - * @param Db $zdb Database instance - * @param Login $login Login instance - * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load - * a specific activity, or null to just - * instanciate object + * @param Db $zdb Database instance + * @param Login $login Login instance + * @param History $history History instance + * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load + * a specific activity, or null to just + * instanciate object */ - public function __construct(Db $zdb, Login $login, int|ArrayObject|null $args = null) + public function __construct(Db $zdb, Login $login, History $history, int|ArrayObject|null $args = null) { $this->zdb = $zdb; $this->login = $login; + $this->history = $history; if (is_int($args) && $args > 0) { $this->load($args); @@ -188,8 +192,6 @@ public function check(array $values): bool */ public function store(): bool { - global $hist; - try { $values = [ 'name' => $this->name, @@ -217,13 +219,13 @@ public function store(): bool } // logging - $hist->add( + $this->history->add( _T("Activity added", "events"), $this->name ); return true; } else { - $hist->add(_T("Fail to add new activity.", "events")); + $this->history->add(_T("Fail to add new activity.", "events")); throw new \Exception( 'An error occurred inserting new activity!' ); @@ -241,7 +243,7 @@ public function store(): bool //edit == 0 does not mean there were an error, but that there //were nothing to change if ($edit->count() > 0) { - $hist->add( + $this->history->add( _T("Activity updated", "events"), $this->name ); diff --git a/lib/GaletteEvents/Booking.php b/lib/GaletteEvents/Booking.php index 01fe5b7..266e756 100644 --- a/lib/GaletteEvents/Booking.php +++ b/lib/GaletteEvents/Booking.php @@ -12,6 +12,7 @@ use ArrayObject; use Galette\Core\Db; +use Galette\Core\History; use Galette\Core\Login; use Galette\Entity\Adherent; use Galette\Entity\PaymentType; @@ -30,6 +31,7 @@ class Booking private Db $zdb; private Login $login; + private History $history; /** @var array */ private array $errors; @@ -54,16 +56,18 @@ class Booking /** * Default constructor * - * @param Db $zdb Database instance - * @param Login $login Login instance - * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load - * a specific event, or null to just - * instanciate object + * @param Db $zdb Database instance + * @param Login $login Login instance + * @param History $history History instance + * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load + * a specific event, or null to just + * instanciate object */ - public function __construct(Db $zdb, Login $login, int|ArrayObject|null $args = null) + public function __construct(Db $zdb, Login $login, History $history, int|ArrayObject|null $args = null) { $this->zdb = $zdb; $this->login = $login; + $this->history = $history; if (is_int($args)) { $this->load($args); } elseif (is_object($args)) { @@ -361,8 +365,6 @@ public function check(array $values): array|bool */ public function store(): bool { - global $hist; - try { $this->zdb->connection->beginTransaction(); $values = [ @@ -398,12 +400,12 @@ public function store(): bool } // logging - $hist->add( + $this->history->add( _T("Booking added", "events"), $this->getEvent()->getName() ); } else { - $hist->add(_T("Fail to add new booking.", "events")); + $this->history->add(_T("Fail to add new booking.", "events")); throw new \Exception( 'An error occurred inserting new booking!' ); @@ -421,7 +423,7 @@ public function store(): bool //edit == 0 does not mean there were an error, but that there //were nothing to change if ($edit->count() > 0) { - $hist->add( + $this->history->add( _T("Booking updated", "events") ); } @@ -573,7 +575,7 @@ public function getEventId(): ?int public function getEvent(): ?Event { if (isset($this->event)) { - return new Event($this->zdb, $this->login, $this->event); + return new Event($this->zdb, $this->login, $this->history, $this->event); } return null; } @@ -749,6 +751,7 @@ public function loadActivities(): void 'activity' => new Activity( $this->zdb, $this->login, + $this->history, (int)$result[Activity::PK] ), 'checked' => $result['checked'] diff --git a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php index 8ea3de7..5dd9a7a 100644 --- a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php +++ b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php @@ -78,7 +78,7 @@ public function list(Request $request, Response $response, ?string $option = nul } } - $activities = new Activities($this->zdb, $this->login, $this->preferences, $filters); + $activities = new Activities($this->zdb, $this->login, $this->history, $this->preferences, $filters); $list = $activities->getList(); //assign pagination variables to the template and add pagination links @@ -145,7 +145,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri $activity = $this->session->plugin_events_activity; $this->session->plugin_events_activity = null; } else { - $activity = new Activity($this->zdb, $this->login); + $activity = new Activity($this->zdb, $this->login, $this->history); } if ($id !== null && $activity->getId() != $id) { @@ -184,7 +184,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri public function doEdit(Request $request, Response $response, ?int $id = null, string $action = 'edit'): Response { $post = $request->getParsedBody(); - $activity = new Activity($this->zdb, $this->login); + $activity = new Activity($this->zdb, $this->login, $this->history); if (isset($post['id']) && !empty($post['id'])) { $activity->load((int)$post['id']); } @@ -291,7 +291,7 @@ public function formUri(array $args): string */ public function confirmRemoveTitle(array $args): string { - $activity = new Activity($this->zdb, $this->login, (int)$args['id']); + $activity = new Activity($this->zdb, $this->login, $this->history, (int)$args['id']); return sprintf( //TRANS %1$s is activity name _T('Remove activity %1$s', 'events'), @@ -307,7 +307,7 @@ public function confirmRemoveTitle(array $args): string */ protected function doDelete(array $args, array $post): bool { - $activity = new Activity($this->zdb, $this->login, (int)$args['id']); + $activity = new Activity($this->zdb, $this->login, $this->history, (int)$args['id']); return $activity->remove(); } diff --git a/lib/GaletteEvents/Controllers/Crud/BookingsController.php b/lib/GaletteEvents/Controllers/Crud/BookingsController.php index 7e5d60a..3f67628 100644 --- a/lib/GaletteEvents/Controllers/Crud/BookingsController.php +++ b/lib/GaletteEvents/Controllers/Crud/BookingsController.php @@ -108,16 +108,16 @@ public function listBookings(Response $response, string|int $event, ?string $opt $event = null; if ($linked_event !== 'all') { $filters->event_filter = (int)$linked_event; - $event = new Event($this->zdb, $this->login, (int)$linked_event); + $event = new Event($this->zdb, $this->login, $this->history, (int)$linked_event); } //Groups $groups = new Groups($this->zdb, $this->login); $groups_list = $groups->getList(); - $bookings = new Bookings($this->zdb, $this->login, $filters); + $bookings = new Bookings($this->zdb, $this->login, $this->history, $filters); - $events = new Events($this->zdb, $this->login); + $events = new Events($this->zdb, $this->login, $this->history); $list = $bookings->getList(); $count = $bookings->getCount(); @@ -247,7 +247,7 @@ public function handleBatch(Request $request, Response $response): Response $filters->selected = $post['entries_sel']; //selection is restricted to bookings current logged-in user can list - $bookings = new Bookings($this->zdb, $this->login, $filters); + $bookings = new Bookings($this->zdb, $this->login, $this->history, $filters); $members = []; foreach ($bookings->getList() as $booking) { $members[] = $booking->getMemberId(); @@ -366,7 +366,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri $booking = $this->session->plugin_events_booking; $this->session->plugin_events_booking = null; } else { - $booking = new Booking($this->zdb, $this->login); + $booking = new Booking($this->zdb, $this->login, $this->history); } if ($id !== null && $booking->getId() != $id) { @@ -386,7 +386,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri } //Events - $events = new Events($this->zdb, $this->login); + $events = new Events($this->zdb, $this->login, $this->history); if ($action === 'add') { if (isset($get['event'])) { $booking->setEvent((int)$get['event']); @@ -466,7 +466,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri public function doEdit(Request $request, Response $response, ?int $id = null, string $action = 'edit'): Response { $post = $request->getParsedBody(); - $booking = new Booking($this->zdb, $this->login); + $booking = new Booking($this->zdb, $this->login, $this->history); if (isset($post['id']) && !empty($post['id'])) { $booking->load((int)$post['id']); } @@ -633,7 +633,7 @@ public function formUri(array $args): string */ public function confirmRemoveTitle(array $args): string { - $booking = new Booking($this->zdb, $this->login, (int)$args['id']); + $booking = new Booking($this->zdb, $this->login, $this->history, (int)$args['id']); $member = $booking->getMember(); $event = $booking->getEvent(); return sprintf( @@ -652,7 +652,7 @@ public function confirmRemoveTitle(array $args): string */ protected function doDelete(array $args, array $post): bool { - $booking = new Booking($this->zdb, $this->login, (int)$post['id']); + $booking = new Booking($this->zdb, $this->login, $this->history, (int)$post['id']); return $booking->remove(); } diff --git a/lib/GaletteEvents/Controllers/Crud/EventsController.php b/lib/GaletteEvents/Controllers/Crud/EventsController.php index 41c4ef5..45eb62b 100644 --- a/lib/GaletteEvents/Controllers/Crud/EventsController.php +++ b/lib/GaletteEvents/Controllers/Crud/EventsController.php @@ -86,7 +86,7 @@ public function list(Request $request, Response $response, ?string $option = nul } } - $events = new Events($this->zdb, $this->login, $filters); + $events = new Events($this->zdb, $this->login, $this->history, $filters); $events_list = $events->getList(); //assign pagination variables to the template and add pagination links @@ -159,7 +159,7 @@ public function ajaxCalendar(Request $request, Response $response): Response $filters->start_date_filter = date(__("Y-m-d"), $start); $filters->end_date_filter = date(__("Y-m-d"), $end); - $events = new Events($this->zdb, $this->login, $filters); + $events = new Events($this->zdb, $this->login, $this->history, $filters); return $this->withJson($response, $events->getList(false, true)); } @@ -208,7 +208,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri $event = $this->session->plugin_events_event; $this->session->plugin_events_event = null; } else { - $event = new Event($this->zdb, $this->login); + $event = new Event($this->zdb, $this->login, $this->history); } $can = $event->canCreate($this->login); @@ -260,7 +260,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri public function doEdit(Request $request, Response $response, ?int $id = null, string $action = 'edit'): Response { $post = $request->getParsedBody(); - $event = new Event($this->zdb, $this->login); + $event = new Event($this->zdb, $this->login, $this->history); $can = $event->canCreate($this->login); if (isset($post['id']) && !empty($post['id'])) { $event->load((int)$post['id']); @@ -411,7 +411,7 @@ public function formUri(array $args): string */ public function confirmRemoveTitle(array $args): string { - $event = new Event($this->zdb, $this->login, (int)$args['id']); + $event = new Event($this->zdb, $this->login, $this->history, (int)$args['id']); return sprintf( //TRANS: %1$s is the event name _T('Remove event \'%1$s\'', 'events'), @@ -427,7 +427,7 @@ public function confirmRemoveTitle(array $args): string */ protected function doDelete(array $args, array $post): bool { - $event = new Event($this->zdb, $this->login, (int)$post['id']); + $event = new Event($this->zdb, $this->login, $this->history, (int)$post['id']); return $event->remove(); } diff --git a/lib/GaletteEvents/Controllers/CsvController.php b/lib/GaletteEvents/Controllers/CsvController.php index 4cf1c06..08deb3f 100644 --- a/lib/GaletteEvents/Controllers/CsvController.php +++ b/lib/GaletteEvents/Controllers/CsvController.php @@ -79,7 +79,7 @@ public function bookingsExport(Request $request, Response $response, ?int $id = $filters->event_filter = $id; } - $bookings = new Bookings($this->zdb, $this->login, $filters); + $bookings = new Bookings($this->zdb, $this->login, $this->history, $filters); $bookings_list = $bookings->getList(true); $labels = [ @@ -98,7 +98,7 @@ public function bookingsExport(Request $request, Response $response, ?int $id = //activities are onl:y available for one event if ($filters->event_filter > 0) { - $event = new Event($this->zdb, $this->login, (int)$filters->event_filter); + $event = new Event($this->zdb, $this->login, $this->history, (int)$filters->event_filter); $activities = $event->getActivities(); foreach ($activities as $activity) { $labels[] = $activity['activity']->getName(); diff --git a/lib/GaletteEvents/Event.php b/lib/GaletteEvents/Event.php index 3a0bba4..24e883f 100644 --- a/lib/GaletteEvents/Event.php +++ b/lib/GaletteEvents/Event.php @@ -12,6 +12,7 @@ use ArrayObject; use Galette\Core\Db; +use Galette\Core\History; use Galette\Core\Login; use Galette\Entity\Group; use Analog\Analog; @@ -30,6 +31,7 @@ class Event private Db $zdb; private Login $login; + private History $history; /** @var array */ private array $errors; @@ -53,16 +55,18 @@ class Event /** * Default constructor * - * @param Db $zdb Database instance - * @param Login $login Login instance - * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load - * a specific event, or null to just - * instanciate object + * @param Db $zdb Database instance + * @param Login $login Login instance + * @param History $history History instance + * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load + * a specific event, or null to just + * instanciate object */ - public function __construct(Db $zdb, Login $login, int|ArrayObject|null $args = null) + public function __construct(Db $zdb, Login $login, History $history, int|ArrayObject|null $args = null) { $this->zdb = $zdb; $this->login = $login; + $this->history = $history; if ($args == null || is_int($args)) { if (is_int($args) && $args > 0) { $this->load($args); @@ -338,8 +342,6 @@ public function check(array $values): bool|array */ public function store(): bool { - global $hist; - try { $this->zdb->connection->beginTransaction(); $values = [ @@ -376,12 +378,12 @@ public function store(): bool } // logging - $hist->add( + $this->history->add( _T("Event added", "events"), $this->name ); } else { - $hist->add(_T("Fail to add new event.", "events")); + $this->history->add(_T("Fail to add new event.", "events")); throw new \Exception( 'An error occurred inserting new event!' ); @@ -399,7 +401,7 @@ public function store(): bool //edit == 0 does not mean there were an error, but that there //were nothing to change if ($edit->count() > 0) { - $hist->add( + $this->history->add( _T("Event updated", "events"), $this->name ); @@ -428,7 +430,7 @@ public function store(): bool */ private function getActiveActivity(int $id): ?Activity { - $activity = new Activity($this->zdb, $this->login, $id); + $activity = new Activity($this->zdb, $this->login, $this->history, $id); return $activity->getId() !== null && $activity->isActive() ? $activity : null; } @@ -698,6 +700,7 @@ public function loadActivities(): void 'activity' => new Activity( $this->zdb, $this->login, + $this->history, (int)$result[Activity::PK] ), 'status' => $result['status'] diff --git a/lib/GaletteEvents/PluginGaletteEvents.php b/lib/GaletteEvents/PluginGaletteEvents.php index 7320afa..7704311 100644 --- a/lib/GaletteEvents/PluginGaletteEvents.php +++ b/lib/GaletteEvents/PluginGaletteEvents.php @@ -12,6 +12,7 @@ use DI\Attribute\Inject; use Galette\Core\Db; +use Galette\Core\History; use Galette\Core\Login; use Galette\Core\Plugins\DashboardProviderInterface; use Galette\Core\Plugins\InstallableInterface; @@ -42,6 +43,9 @@ class PluginGaletteEvents extends GalettePlugin implements InstallableInterface, #[Inject] protected Login $login; + #[Inject] + protected History $history; + /** * Extra menus entries * @@ -49,11 +53,9 @@ class PluginGaletteEvents extends GalettePlugin implements InstallableInterface, */ public function getMenus(): array { - /** @var Login $login */ - global $login; $menus = []; - if ($login->isLogged()) { + if ($this->login->isLogged()) { $menus['plugin_events'] = [ 'title' => _T("Events", "events"), 'icon' => 'calendar alternate', @@ -85,7 +87,7 @@ public function getMenus(): array ]; } - if ($login->isAdmin() || $login->isStaff()) { + if ($this->login->isAdmin() || $this->login->isStaff()) { $menus['plugin_events']['items'] = array_merge( $menus['plugin_events']['items'], [ @@ -193,7 +195,7 @@ public function getNews(): ?Entry $filters = new EventsList(); $now = new \DateTime(); $filters->start_date_filter = $now->format(__('Y-m-d')); - $events = new Events($this->zdb, $this->login, $filters); + $events = new Events($this->zdb, $this->login, $this->history, $filters); $posts = []; $list = $events->getList(); diff --git a/lib/GaletteEvents/Repository/Activities.php b/lib/GaletteEvents/Repository/Activities.php index 593242e..5463e9d 100644 --- a/lib/GaletteEvents/Repository/Activities.php +++ b/lib/GaletteEvents/Repository/Activities.php @@ -19,6 +19,7 @@ use Laminas\Db\Sql\Expression; use Galette\Core\Login; use Galette\Core\Db; +use Galette\Core\History; use Laminas\Db\Sql\Select; /** @@ -28,6 +29,7 @@ */ class Activities extends Repository { + private History $history; private int $count; public const int ORDERBY_DATE = 0; @@ -38,13 +40,15 @@ class Activities extends Repository * * @param Db $zdb Database instance * @param Login $login Login instance + * @param History $history History instance * @param Preferences $preferences Preferences instance * @param ?ActivitiesList $filters Filtering */ - public function __construct(Db $zdb, Login $login, Preferences $preferences, ?ActivitiesList $filters = null) + public function __construct(Db $zdb, Login $login, History $history, Preferences $preferences, ?ActivitiesList $filters = null) { $this->zdb = $zdb; $this->login = $login; + $this->history = $history; parent::__construct($zdb, $preferences, $login, 'Activity', 'GaletteEvents', EVENTS_PREFIX); @@ -75,7 +79,7 @@ public function getList(): array|ResultSet $activities = []; foreach ($results as $row) { - $activity = new Activity($this->zdb, $this->login, $row); + $activity = new Activity($this->zdb, $this->login, $this->history, $row); $activities[] = $activity; } diff --git a/lib/GaletteEvents/Repository/Bookings.php b/lib/GaletteEvents/Repository/Bookings.php index 1dfebb9..9a6a718 100644 --- a/lib/GaletteEvents/Repository/Bookings.php +++ b/lib/GaletteEvents/Repository/Bookings.php @@ -16,6 +16,7 @@ use Laminas\Db\Sql\Predicate\PredicateSet; use Galette\Core\Login; use Galette\Core\Db; +use Galette\Core\History; use Galette\Entity\Adherent; use Galette\Entity\Group; use GaletteEvents\Event; @@ -32,6 +33,7 @@ class Bookings { private Db $zdb; private Login $login; + private History $history; private BookingsList $filters; private int $count; private float $sum; @@ -50,12 +52,14 @@ class Bookings * * @param Db $zdb Database instance * @param Login $login Login instance + * @param History $history History instance * @param ?BookingsList $filters Filtering */ - public function __construct(Db $zdb, Login $login, ?BookingsList $filters = null) + public function __construct(Db $zdb, Login $login, History $history, ?BookingsList $filters = null) { $this->zdb = $zdb; $this->login = $login; + $this->history = $history; if ($filters === null) { $this->filters = new BookingsList(); @@ -87,7 +91,7 @@ public function getList(bool $full = false): array $bookings = []; foreach ($results as $row) { - $booking = new Booking($this->zdb, $this->login, $row); + $booking = new Booking($this->zdb, $this->login, $this->history, $row); $bookings[] = $booking; } diff --git a/lib/GaletteEvents/Repository/Events.php b/lib/GaletteEvents/Repository/Events.php index af9e2a6..57f25e0 100644 --- a/lib/GaletteEvents/Repository/Events.php +++ b/lib/GaletteEvents/Repository/Events.php @@ -20,6 +20,7 @@ use Laminas\Db\Sql\Predicate\PredicateSet; use Galette\Core\Login; use Galette\Core\Db; +use Galette\Core\History; use Galette\Entity\Group; use Galette\Repository\Groups; use GaletteEvents\Event; @@ -35,6 +36,7 @@ class Events { private Db $zdb; private Login $login; + private History $history; private EventsList $filters; private int $count = 0; @@ -47,12 +49,14 @@ class Events * * @param Db $zdb Database instance * @param Login $login Login instance + * @param History $history History instance * @param ?EventsList $filters Filtering */ - public function __construct(Db $zdb, Login $login, ?EventsList $filters = null) + public function __construct(Db $zdb, Login $login, History $history, ?EventsList $filters = null) { $this->zdb = $zdb; $this->login = $login; + $this->history = $history; if ($filters === null) { $this->filters = new EventsList(); @@ -148,7 +152,7 @@ public function getList(bool $bookable = false, bool $fullcalendar = false, bool $events = []; foreach ($results as $row) { - $event = new Event($this->zdb, $this->login, $row); + $event = new Event($this->zdb, $this->login, $this->history, $row); if (!$this->filters->calendar_filter) { $events[] = $event; } else { diff --git a/tests/GaletteEvents/Repository/tests/units/Bookings.php b/tests/GaletteEvents/Repository/tests/units/Bookings.php index be44b2c..9925e30 100644 --- a/tests/GaletteEvents/Repository/tests/units/Bookings.php +++ b/tests/GaletteEvents/Repository/tests/units/Bookings.php @@ -42,7 +42,7 @@ public function tearDown(): void */ private function getVisibleBookings(): array { - $bookings = new \GaletteEvents\Repository\Bookings($this->zdb, $this->login); + $bookings = new \GaletteEvents\Repository\Bookings($this->zdb, $this->login, $this->history); $ids = array_map(fn(Booking $booking): ?int => $booking->getId(), $bookings->getList(true)); sort($ids); return ['ids' => $ids, 'sum' => $bookings->getSum()]; @@ -100,7 +100,7 @@ public function testListKeepsBookingValues(): void $this->insertBooking($event, $member_one->id, ['comment' => 'Booking comment', 'creation_date' => '2026-02-01']); $this->logSuperAdmin(); - $list = (new \GaletteEvents\Repository\Bookings($this->zdb, $this->login))->getList(); + $list = (new \GaletteEvents\Repository\Bookings($this->zdb, $this->login, $this->history))->getList(); $this->assertCount(1, $list); $this->assertSame('Booking comment', $list[0]->getComment()); $this->assertSame('2026-02-01', $list[0]->getCreationDate(false)); @@ -136,7 +136,7 @@ public function testFilters(): void foreach ($filters as $name => $value) { $bookings_filters->$name = $value; } - $bookings = new \GaletteEvents\Repository\Bookings($this->zdb, $this->login, $bookings_filters); + $bookings = new \GaletteEvents\Repository\Bookings($this->zdb, $this->login, $this->history, $bookings_filters); $ids = array_map(fn(Booking $booking): ?int => $booking->getId(), $bookings->getList()); return ['ids' => $ids, 'count' => $bookings->getCount(), 'sum' => $bookings->getSum()]; }; diff --git a/tests/GaletteEvents/Repository/tests/units/Events.php b/tests/GaletteEvents/Repository/tests/units/Events.php index 8094ea3..5238bea 100644 --- a/tests/GaletteEvents/Repository/tests/units/Events.php +++ b/tests/GaletteEvents/Repository/tests/units/Events.php @@ -44,7 +44,7 @@ public function tearDown(): void */ private function getListed(bool $bookable = false): array { - $events = new \GaletteEvents\Repository\Events($this->zdb, $this->login); + $events = new \GaletteEvents\Repository\Events($this->zdb, $this->login, $this->history); $names = []; foreach ($events->getList($bookable) as $event) { $this->assertInstanceOf(\GaletteEvents\Event::class, $event); @@ -65,7 +65,7 @@ private function getCalendar(): array $filters->calendar_filter = true; $filters->start_date_filter = date(__('Y-m-d'), strtotime('-1 month')); $filters->end_date_filter = date(__('Y-m-d'), strtotime('+1 month')); - $events = new \GaletteEvents\Repository\Events($this->zdb, $this->login, $filters); + $events = new \GaletteEvents\Repository\Events($this->zdb, $this->login, $this->history, $filters); $names = []; foreach ($events->getList(false, true) as $event) { $this->assertInstanceOf(\ArrayObject::class, $event); diff --git a/tests/GaletteEvents/tests/units/Activity.php b/tests/GaletteEvents/tests/units/Activity.php index 668aa59..27f90e1 100644 --- a/tests/GaletteEvents/tests/units/Activity.php +++ b/tests/GaletteEvents/tests/units/Activity.php @@ -38,7 +38,7 @@ public function tearDown(): void */ public function testEmpty(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login); + $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); $this->assertNull($activity->getId()); $this->assertSame('', $activity->getName()); @@ -53,8 +53,8 @@ public function testEmpty(): void */ public function testCrud(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login); - $activities = new \GaletteEvents\Repository\Activities($this->zdb, $this->login, $this->preferences); + $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); + $activities = new \GaletteEvents\Repository\Activities($this->zdb, $this->login, $this->history, $this->preferences); //ensure the table is empty $this->assertCount(0, $activities->getList()); @@ -113,7 +113,7 @@ public function testCrud(): void */ public function testLoadError(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login); + $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); $this->assertFalse($activity->load(999)); } @@ -122,7 +122,7 @@ public function testLoadError(): void */ public function testNoComment(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login); + $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); $this->assertTrue($activity->check(['name' => 'Dinner', 'active' => '1'])); $this->assertTrue($activity->store()); @@ -130,7 +130,7 @@ public function testNoComment(): void $update->set(['comment' => null])->where([\GaletteEvents\Activity::PK => $activity->getId()]); $this->zdb->execute($update); - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, (int)$activity->getId()); + $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history, (int)$activity->getId()); $this->assertSame('Dinner', $activity->getName()); $this->assertSame('', $activity->getComment()); } @@ -144,10 +144,10 @@ public function testCountAndRemove(): void $this->linkActivity($this->insertEvent('First event'), $id); $this->linkActivity($this->insertEvent('Second event'), $id); - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $id); + $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history, $id); $this->assertSame(2, $activity->countEvents()); $this->assertTrue($activity->remove()); - $this->assertFalse((new \GaletteEvents\Activity($this->zdb, $this->login))->load($id)); + $this->assertFalse((new \GaletteEvents\Activity($this->zdb, $this->login, $this->history))->load($id)); $select = $this->zdb->select(EVENTS_PREFIX . 'activitiesevents'); $select->where([\GaletteEvents\Activity::PK => $id]); diff --git a/tests/GaletteEvents/tests/units/Booking.php b/tests/GaletteEvents/tests/units/Booking.php index f58cf62..a7f28aa 100644 --- a/tests/GaletteEvents/tests/units/Booking.php +++ b/tests/GaletteEvents/tests/units/Booking.php @@ -46,7 +46,7 @@ public function testLoadNullValues(): void ['comment' => null, 'payment_amount' => null, 'number_people' => null, 'creation_date' => '2026-09-01'] ); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $id); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertSame('', $booking->getComment()); $this->assertNull($booking->getAmount()); $this->assertSame(1, $booking->getNumberPeople()); @@ -72,13 +72,13 @@ public function testActivitiesSync(): void 'booking_date' => date('Y-m-d'), 'number_people' => '1', ]; - $booking = new \GaletteEvents\Booking($this->zdb, $this->login); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); $this->assertTrue($booking->check($values + ['activities' => [(string)$dinner]])); $this->assertTrue($booking->store()); $id = (int)$booking->getId(); $this->assertSame([$dinner => true, $lodging => false], $this->getBookingActivities($id)); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $id); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertTrue($booking->check($values + ['activities' => [(string)$lodging]])); $this->assertTrue($booking->store()); $this->assertSame([$dinner => false, $lodging => true], $this->getBookingActivities($id)); @@ -87,7 +87,7 @@ public function testActivitiesSync(): void $delete = $this->zdb->delete(EVENTS_PREFIX . 'activitiesevents'); $delete->where([\GaletteEvents\Event::PK => $event, \GaletteEvents\Activity::PK => $dinner]); $this->zdb->execute($delete); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $id); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertTrue($booking->check($values + ['activities' => [(string)$lodging]])); $this->assertTrue($booking->store()); $this->assertSame([$lodging => true], $this->getBookingActivities($id)); @@ -107,16 +107,16 @@ public function testAmount(): void 'number_people' => '1', ]; - $booking = new \GaletteEvents\Booking($this->zdb, $this->login); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); $this->assertTrue($booking->check($values + ['amount' => '12,50'])); $this->assertSame(12.5, $booking->getAmount()); $this->assertTrue($booking->store()); $id = (int)$booking->getId(); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $id); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertTrue($booking->check($values + ['amount' => ''])); $this->assertTrue($booking->store()); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $id); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertNull($booking->getAmount()); $this->assertTrue($booking->check($values + ['amount' => '0', 'paid' => '1'])); @@ -159,7 +159,7 @@ public function testCheck(): void $event = $this->insertEvent('Event'); $this->insertBooking($event, $member_one->id); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); $this->assertSame( ['Event is mandatory', 'Member is mandatory', 'Booking date is mandatory!'], $booking->check(['number_people' => '1']) @@ -189,7 +189,7 @@ public function testMemberFinancialValues(): void $event = $this->insertEvent('Event'); $this->logMember($this->dataAdherentOne()); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); $this->assertTrue($booking->check($this->getFormValues([ 'event' => (string)$event, 'paid' => '1', @@ -218,7 +218,7 @@ public function testRemove(): void $insert->values([\GaletteEvents\Activity::PK => $dinner, \GaletteEvents\Booking::PK => $id]); $this->zdb->execute($insert); - $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $id); + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertTrue($booking->remove()); $this->assertSame(0, $this->countBookings($event)); $this->assertSame([], $this->getBookingActivities($id)); diff --git a/tests/GaletteEvents/tests/units/Event.php b/tests/GaletteEvents/tests/units/Event.php index d8270eb..d7e45aa 100644 --- a/tests/GaletteEvents/tests/units/Event.php +++ b/tests/GaletteEvents/tests/units/Event.php @@ -69,18 +69,18 @@ public function testManagerCreatesEvent(): void $other = $this->createGroup('Other group', [], [$member_two]); $this->logMember($this->dataAdherentTwo()); - $event = new \GaletteEvents\Event($this->zdb, $this->login); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); $this->assertSame( [_T('Please select a group you own!', 'events')], $event->check($this->getFormValues(['group' => (string)$other->getId()])) ); $this->expectLogEntry(\Analog\Analog::ERROR, 'Some errors has been threw attempting to edit/store an event'); - $event = new \GaletteEvents\Event($this->zdb, $this->login); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); $this->assertTrue($event->check($this->getFormValues(['group' => (string)$managed->getId()]))); $this->assertTrue($event->store()); - $event = new \GaletteEvents\Event($this->zdb, $this->login, (int)$event->getId()); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, (int)$event->getId()); $this->assertSame($managed->getId(), $event->getGroup()); } @@ -90,7 +90,7 @@ public function testManagerCreatesEvent(): void public function testStoreMandatoryValuesOnly(): void { $this->logSuperAdmin(); - $event = new \GaletteEvents\Event($this->zdb, $this->login); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); $this->assertTrue($event->check([ 'name' => 'Event', 'town' => 'Lille', @@ -98,7 +98,7 @@ public function testStoreMandatoryValuesOnly(): void ])); $this->assertTrue($event->store()); - $event = new \GaletteEvents\Event($this->zdb, $this->login, (int)$event->getId()); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, (int)$event->getId()); $this->assertSame('Event', $event->getName()); $this->assertSame('', $event->getAddress()); $this->assertNull($event->getGroup()); @@ -111,7 +111,7 @@ public function testLoadNullValues(): void { $id = $this->insertEvent('Event', ['comment' => null, 'country' => null]); - $event = new \GaletteEvents\Event($this->zdb, $this->login, $id); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $id); $this->assertSame('', $event->getComment()); $this->assertNull($event->getGroup()); $this->assertSame('', $event->getColor()); @@ -128,7 +128,7 @@ public function testActivitiesSync(): void $visit = $this->insertActivity('Visit'); $ids = array_map('strval', [$dinner, $lodging, $visit]); - $event = new \GaletteEvents\Event($this->zdb, $this->login); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); $this->assertTrue($event->check($this->getFormValues([ 'activities_ids' => $ids, 'activities_status' => ['1', '1', '2'], @@ -138,7 +138,7 @@ public function testActivitiesSync(): void $this->assertSame([$dinner => 1, $lodging => 1, $visit => 2], $this->getEventActivities($id)); //change status of activities that are not the last one - $event = new \GaletteEvents\Event($this->zdb, $this->login, $id); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $id); $this->assertTrue($event->check($this->getFormValues([ 'activities_ids' => $ids, 'activities_status' => ['2', '0', '2'], @@ -147,7 +147,7 @@ public function testActivitiesSync(): void $this->assertSame([$dinner => 2, $lodging => 0, $visit => 2], $this->getEventActivities($id)); //remove two activities before storing - $event = new \GaletteEvents\Event($this->zdb, $this->login, $id); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $id); $this->assertTrue($event->check($this->getFormValues([ 'remove_activity' => '1', 'detach_activity' => (string)$dinner, @@ -182,7 +182,7 @@ public function testInactiveActivities(): void ->where([\GaletteEvents\Activity::PK => $lodging]); $this->zdb->execute($update); - $event = new \GaletteEvents\Event($this->zdb, $this->login); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); $this->assertSame( [$dinner], array_map(fn($row): int => (int)$row[\GaletteEvents\Activity::PK], $event->availableActivities()) @@ -212,9 +212,9 @@ public function testCanEditChecksGivenLogin(): void $manager = new \Galette\Core\Login($this->zdb, $this->i18n); $this->assertTrue($manager->login($this->dataAdherentTwo()['login_adh'], $this->dataAdherentTwo()['mdp_adh'])); - $this->assertTrue((new \GaletteEvents\Event($this->zdb, $this->login, $managed_event))->canEdit($manager)); - $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login, $other_event))->canEdit($manager)); - $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login, $this->insertEvent('Public event')))->canEdit($manager)); + $this->assertTrue((new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $managed_event))->canEdit($manager)); + $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $other_event))->canEdit($manager)); + $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $this->insertEvent('Public event')))->canEdit($manager)); } /** @@ -223,7 +223,7 @@ public function testCanEditChecksGivenLogin(): void public function testCheck(): void { $this->logSuperAdmin(); - $event = new \GaletteEvents\Event($this->zdb, $this->login); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); $this->assertSame( ['Begin date is mandatory', 'Name is mandatory', 'Town is mandatory'], @@ -264,7 +264,7 @@ public function testRemove(): void $this->linkActivity($id, $this->insertActivity('Dinner')); $this->insertBooking($id, $member_one->id, ['number_people' => 3, 'is_paid' => $this->zdb->isPostgres() ? 'true' : 1]); - $event = new \GaletteEvents\Event($this->zdb, $this->login, $id); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $id); $attendees = []; foreach ($event->countAttendees() as $row) { $attendees[(int)(bool)$row['is_paid']] = (int)$row['count']; @@ -274,6 +274,6 @@ public function testRemove(): void $this->assertTrue($event->remove()); $this->assertSame(0, $this->countBookings($id)); $this->assertSame([], $this->getEventActivities($id)); - $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login))->load($id)); + $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login, $this->history))->load($id)); } } From 0ef60319fc486a2df9df0ac3b800b387fa66458e Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Sun, 27 Sep 2026 11:49:29 +0200 Subject: [PATCH 2/9] Throw on storage errors, catch and log them in controllers --- lib/GaletteEvents/Activity.php | 151 +++++++----------- lib/GaletteEvents/Booking.php | 89 +++++------ .../Controllers/Crud/ActivitiesController.php | 25 ++- .../Controllers/Crud/BookingsController.php | 26 ++- .../Controllers/Crud/EventsController.php | 16 +- lib/GaletteEvents/Event.php | 87 +++++----- .../Crud/tests/units/EventsController.php | 21 +++ tests/GaletteEvents/tests/units/Activity.php | 8 +- tests/GaletteEvents/tests/units/Booking.php | 52 +++--- tests/GaletteEvents/tests/units/Event.php | 69 +++++--- 10 files changed, 262 insertions(+), 282 deletions(-) diff --git a/lib/GaletteEvents/Activity.php b/lib/GaletteEvents/Activity.php index c8b071c..ff62ae0 100644 --- a/lib/GaletteEvents/Activity.php +++ b/lib/GaletteEvents/Activity.php @@ -110,39 +110,13 @@ private function loadFromRS(ArrayObject $r): void } /** - * Remove specified event + * Remove activity, with its links to events and bookings */ - public function remove(): bool + public function remove(): void { - $transaction = false; - - try { - if (!$this->zdb->connection->inTransaction()) { - $this->zdb->connection->beginTransaction(); - $transaction = true; - } - - $delete = $this->zdb->delete($this->getTableName()); - $delete->where([self::PK => $this->id]); - $this->zdb->execute($delete); - - //commit all changes - if ($transaction) { - $this->zdb->connection->commit(); - } - - return true; - } catch (\Exception $e) { - if ($transaction) { - $this->zdb->connection->rollBack(); - } - Analog::log( - 'Unable to delete activity ' . $this->name - . ' (' . $this->id . ') |' . $e->getMessage(), - Analog::ERROR - ); - return false; - } + $delete = $this->zdb->delete($this->getTableName()); + $delete->where([self::PK => $this->id]); + $this->zdb->execute($delete); } /** @@ -190,73 +164,62 @@ public function check(array $values): bool /** * Store the activity */ - public function store(): bool + public function store(): void { - try { - $values = [ - 'name' => $this->name, - 'is_active' => ($this->active ? $this->active - : ($this->zdb->isPostgres() ? 'false' : 0)), - 'comment' => $this->comment - ]; - - if (empty($this->id)) { - //we're inserting a new event - $this->creation_date = date("Y-m-d"); - $values['creation_date'] = $this->creation_date; - - $insert = $this->zdb->insert($this->getTableName()); - $insert->values($values); - $add = $this->zdb->execute($insert); - if ($add->count() > 0) { - if ($this->zdb->isPostgres()) { - /** @phpstan-ignore-next-line */ - $this->id = (int)$this->zdb->driver->getLastGeneratedValue( - PREFIX_DB . $this->getTableName() . '_id_seq' - ); - } else { - $this->id = (int)$this->zdb->driver->getLastGeneratedValue(); - } - - // logging - $this->history->add( - _T("Activity added", "events"), - $this->name - ); - return true; - } else { - $this->history->add(_T("Fail to add new activity.", "events")); - throw new \Exception( - 'An error occurred inserting new activity!' - ); - } + $values = [ + 'name' => $this->name, + 'is_active' => ($this->active ? $this->active + : ($this->zdb->isPostgres() ? 'false' : 0)), + 'comment' => $this->comment + ]; + + if (empty($this->id)) { + //we're inserting a new activity + $this->creation_date = date("Y-m-d"); + $values['creation_date'] = $this->creation_date; + + $insert = $this->zdb->insert($this->getTableName()); + $insert->values($values); + $add = $this->zdb->execute($insert); + if ($add->count() === 0) { + $this->history->add(_T("Fail to add new activity.", "events")); + throw new \RuntimeException( + 'An error occurred inserting new activity!' + ); + } + + if ($this->zdb->isPostgres()) { + /** @phpstan-ignore-next-line */ + $this->id = (int)$this->zdb->driver->getLastGeneratedValue( + PREFIX_DB . $this->getTableName() . '_id_seq' + ); } else { - //we're editing an existing event - $values[self::PK] = $this->id; - $update = $this->zdb->update($this->getTableName()); - $update - ->set($values) - ->where([self::PK => $this->id]); - - $edit = $this->zdb->execute($update); - - //edit == 0 does not mean there were an error, but that there - //were nothing to change - if ($edit->count() > 0) { - $this->history->add( - _T("Activity updated", "events"), - $this->name - ); - } - return true; + $this->id = (int)$this->zdb->driver->getLastGeneratedValue(); } - } catch (\Exception $e) { - Analog::log( - 'Something went wrong :\'( | ' . $e->getMessage() . "\n" - . $e->getTraceAsString(), - Analog::ERROR + + // logging + $this->history->add( + _T("Activity added", "events"), + $this->name ); - throw $e; + } else { + //we're editing an existing activity + $values[self::PK] = $this->id; + $update = $this->zdb->update($this->getTableName()); + $update + ->set($values) + ->where([self::PK => $this->id]); + + $edit = $this->zdb->execute($update); + + //edit == 0 does not mean there were an error, but that there + //were nothing to change + if ($edit->count() > 0) { + $this->history->add( + _T("Activity updated", "events"), + $this->name + ); + } } } diff --git a/lib/GaletteEvents/Booking.php b/lib/GaletteEvents/Booking.php index 266e756..1cb9374 100644 --- a/lib/GaletteEvents/Booking.php +++ b/lib/GaletteEvents/Booking.php @@ -33,7 +33,7 @@ class Booking private Login $login; private History $history; /** @var array */ - private array $errors; + private array $errors = []; private int $id; private int $event; @@ -129,39 +129,13 @@ private function loadFromRS(ArrayObject $r): void } /** - * Remove specified event + * Remove booking, with its activities */ - public function remove(): bool + public function remove(): void { - $transaction = false; - - try { - if (!$this->zdb->connection->inTransaction()) { - $this->zdb->connection->beginTransaction(); - $transaction = true; - } - - $delete = $this->zdb->delete($this->getTableName()); - $delete->where([self::PK => $this->id]); - $this->zdb->execute($delete); - - //commit all changes - if ($transaction) { - $this->zdb->connection->commit(); - } - - return true; - } catch (\Exception $e) { - if ($transaction) { - $this->zdb->connection->rollBack(); - } - Analog::log( - 'Unable to delete booking ' - . ' (' . $this->id . ') |' . $e->getMessage(), - Analog::ERROR - ); - return false; - } + $delete = $this->zdb->delete($this->getTableName()); + $delete->where([self::PK => $this->id]); + $this->zdb->execute($delete); } /** @@ -169,10 +143,8 @@ public function remove(): bool * * @param array $values All values to check, basically the $_POST array * after sending the form - * - * @return true|array */ - public function check(array $values): array|bool + public function check(array $values): bool { $this->errors = []; @@ -350,7 +322,7 @@ public function check(array $values): array|bool . print_r($this->errors, true), Analog::ERROR ); - return $this->errors; + return false; } else { Analog::log( 'Event checked successfully.', @@ -363,10 +335,15 @@ public function check(array $values): array|bool /** * Store the booking */ - public function store(): bool + public function store(): void { - try { + $new = empty($this->id); + $transaction = !$this->zdb->connection->inTransaction(); + if ($transaction) { $this->zdb->connection->beginTransaction(); + } + + try { $values = [ Event::PK => $this->event, Adherent::PK => $this->member, @@ -381,8 +358,8 @@ public function store(): bool 'comment' => $this->comment ]; - if (empty($this->id)) { - //we're inserting a new event + if ($new) { + //we're inserting a new booking $this->creation_date = date("Y-m-d"); $values['creation_date'] = $this->creation_date; @@ -406,7 +383,7 @@ public function store(): bool ); } else { $this->history->add(_T("Fail to add new booking.", "events")); - throw new \Exception( + throw new \RuntimeException( 'An error occurred inserting new booking!' ); } @@ -540,15 +517,17 @@ public function store(): bool ); } - $this->zdb->connection->commit(); - return true; - } catch (\Exception $e) { - $this->zdb->connection->rollBack(); - Analog::log( - 'Something went wrong :\'( | ' . $e->getMessage() . "\n" - . $e->getTraceAsString(), - Analog::ERROR - ); + if ($transaction) { + $this->zdb->connection->commit(); + } + } catch (\Throwable $e) { + if ($transaction) { + $this->zdb->connection->rollBack(); + } + if ($new) { + //nothing has been stored + unset($this->id); + } throw $e; } } @@ -815,6 +794,16 @@ public function canEdit(Login $login): bool return $group !== null && $login->isGroupManager($group); } + /** + * Get errors + * + * @return array + */ + public function getErrors(): array + { + return $this->errors; + } + /** * Get row class related to current fee status * diff --git a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php index 5dd9a7a..989cdc3 100644 --- a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php +++ b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php @@ -10,6 +10,7 @@ namespace GaletteEvents\Controllers\Crud; +use Analog\Analog; use Galette\Controllers\Crud\AbstractPluginController; use GaletteEvents\Filters\ActivitiesList; use GaletteEvents\Activity; @@ -193,28 +194,25 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st $error_detected = []; // Validation - $valid = $activity->check($post); - if ($valid !== true) { + if (!$activity->check($post)) { $error_detected = array_merge($error_detected, $activity->getErrors()); } if (count($error_detected) == 0) { //all goes well, we can proceed - - $new = false; - if ($activity->getId() == '') { - $new = true; - } - $store = $activity->store(); - if ($store === true) { - //member has been stored :) + $new = $activity->getId() === null; + try { + $activity->store(); if ($new) { $success_detected[] = _T("New activity has been successfully added.", "events"); } else { $success_detected[] = _T("Activity has been modified.", "events"); } - } else { - //something went wrong :'( + } catch (\Throwable $e) { + Analog::log( + 'Unable to store activity #' . ($activity->getId() ?? 'new') . ' | ' . $e->getMessage(), + Analog::ERROR + ); $error_detected[] = _T("An error occurred while storing the activity.", "events"); } } @@ -308,7 +306,8 @@ public function confirmRemoveTitle(array $args): string protected function doDelete(array $args, array $post): bool { $activity = new Activity($this->zdb, $this->login, $this->history, (int)$args['id']); - return $activity->remove(); + $activity->remove(); + return true; } // /CRUD - Delete diff --git a/lib/GaletteEvents/Controllers/Crud/BookingsController.php b/lib/GaletteEvents/Controllers/Crud/BookingsController.php index 3f67628..2c89068 100644 --- a/lib/GaletteEvents/Controllers/Crud/BookingsController.php +++ b/lib/GaletteEvents/Controllers/Crud/BookingsController.php @@ -491,28 +491,25 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st $goto_list = true; // Validation - $valid = $booking->check($post); - if ($valid !== true) { - $error_detected = array_merge($error_detected, $valid); + if (!$booking->check($post)) { + $error_detected = array_merge($error_detected, $booking->getErrors()); } if (count($error_detected) == 0 && isset($post['save'])) { //all goes well, we can proceed - - $new = false; - if ($booking->getId() == '') { - $new = true; - } - $store = $booking->store(); - if ($store === true) { - //member has been stored :) + $new = $booking->getId() === null; + try { + $booking->store(); if ($new) { $success_detected[] = _T("New booking has been successfully added.", "events"); } else { $success_detected[] = _T("Booking has been modified.", "events"); } - } else { - //something went wrong :'( + } catch (\Throwable $e) { + Analog::log( + 'Unable to store booking #' . ($booking->getId() ?? 'new') . ' | ' . $e->getMessage(), + Analog::ERROR + ); $error_detected[] = _T("An error occurred while storing the booking.", "events"); } } @@ -653,7 +650,8 @@ public function confirmRemoveTitle(array $args): string protected function doDelete(array $args, array $post): bool { $booking = new Booking($this->zdb, $this->login, $this->history, (int)$post['id']); - return $booking->remove(); + $booking->remove(); + return true; } // /CRUD - Delete diff --git a/lib/GaletteEvents/Controllers/Crud/EventsController.php b/lib/GaletteEvents/Controllers/Crud/EventsController.php index 45eb62b..c699b6c 100644 --- a/lib/GaletteEvents/Controllers/Crud/EventsController.php +++ b/lib/GaletteEvents/Controllers/Crud/EventsController.php @@ -294,17 +294,22 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st $success_detected[] = _T("Activity has been detached from event.", "events"); $warning_detected[] = _T('Do not forget to store the event', 'events'); } - } elseif ($valid !== true) { - $error_detected = array_merge($error_detected, $valid); + } elseif (!$valid) { + $error_detected = array_merge($error_detected, $event->getErrors()); } elseif (isset($post['save'])) { $new = $event->getId() === null; - if ($event->store() === true) { + try { + $event->store(); if ($new) { $success_detected[] = _T("New event has been successfully added.", "events"); } else { $success_detected[] = _T("Event has been modified.", "events"); } - } else { + } catch (\Throwable $e) { + Analog::log( + 'Unable to store event #' . ($event->getId() ?? 'new') . ' | ' . $e->getMessage(), + Analog::ERROR + ); $error_detected[] = _T("An error occurred while storing the event.", "events"); } } else { @@ -428,7 +433,8 @@ public function confirmRemoveTitle(array $args): string protected function doDelete(array $args, array $post): bool { $event = new Event($this->zdb, $this->login, $this->history, (int)$post['id']); - return $event->remove(); + $event->remove(); + return true; } // /CRUD - Delete diff --git a/lib/GaletteEvents/Event.php b/lib/GaletteEvents/Event.php index 24e883f..0cd7857 100644 --- a/lib/GaletteEvents/Event.php +++ b/lib/GaletteEvents/Event.php @@ -33,7 +33,7 @@ class Event private Login $login; private History $history; /** @var array */ - private array $errors; + private array $errors = []; private int $id; private string $name; @@ -135,39 +135,13 @@ private function loadFromRS(ArrayObject $r): void } /** - * Remove specified event + * Remove event, with its bookings and activities links */ - public function remove(): bool + public function remove(): void { - $transaction = false; - - try { - if (!$this->zdb->connection->inTransaction()) { - $this->zdb->connection->beginTransaction(); - $transaction = true; - } - - $delete = $this->zdb->delete($this->getTableName()); - $delete->where([self::PK => $this->id]); - $this->zdb->execute($delete); - - //commit all changes - if ($transaction) { - $this->zdb->connection->commit(); - } - - return true; - } catch (\Exception $e) { - if ($transaction) { - $this->zdb->connection->rollBack(); - } - Analog::log( - 'Unable to delete event ' . $this->name - . ' (' . $this->id . ') |' . $e->getMessage(), - Analog::ERROR - ); - return false; - } + $delete = $this->zdb->delete($this->getTableName()); + $delete->where([self::PK => $this->id]); + $this->zdb->execute($delete); } /** @@ -175,10 +149,8 @@ public function remove(): bool * * @param array $values All values to check, basically the $_POST array * after sending the form - * - * @return true|array */ - public function check(array $values): bool|array + public function check(array $values): bool { $this->errors = []; @@ -327,7 +299,7 @@ public function check(array $values): bool|array . print_r($this->errors, true), Analog::ERROR ); - return $this->errors; + return false; } else { Analog::log( 'Event checked successfully.', @@ -340,10 +312,15 @@ public function check(array $values): bool|array /** * Store the event */ - public function store(): bool + public function store(): void { - try { + $new = empty($this->id); + $transaction = !$this->zdb->connection->inTransaction(); + if ($transaction) { $this->zdb->connection->beginTransaction(); + } + + try { $values = [ 'name' => $this->name, 'address' => $this->address, @@ -359,7 +336,7 @@ public function store(): bool 'color' => $this->color ]; - if (empty($this->id)) { + if ($new) { //we're inserting a new event $this->creation_date = date("Y-m-d"); $values['creation_date'] = $this->creation_date; @@ -384,7 +361,7 @@ public function store(): bool ); } else { $this->history->add(_T("Fail to add new event.", "events")); - throw new \Exception( + throw new \RuntimeException( 'An error occurred inserting new event!' ); } @@ -410,15 +387,17 @@ public function store(): bool $this->storeActivities(); - $this->zdb->connection->commit(); - return true; - } catch (\Exception $e) { - $this->zdb->connection->rollBack(); - Analog::log( - 'Something went wrong :\'( | ' . $e->getMessage() . "\n" - . $e->getTraceAsString(), - Analog::ERROR - ); + if ($transaction) { + $this->zdb->connection->commit(); + } + } catch (\Throwable $e) { + if ($transaction) { + $this->zdb->connection->rollBack(); + } + if ($new) { + //nothing has been stored + unset($this->id); + } throw $e; } } @@ -786,6 +765,16 @@ public function canCreate(Login $login): bool return ($login->isAdmin() || $login->isStaff() || $login->isGroupManager()); } + /** + * Get errors + * + * @return array + */ + public function getErrors(): array + { + return $this->errors; + } + /** * Get foreground contrasted color for current background color */ diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php index 583d736..335b09d 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php @@ -210,6 +210,27 @@ public function testManagerCannotStoreOtherEvent(): void $this->expectNoLogEntry(); } + /** + * Storage errors are logged, and reported on the form + */ + public function testStoreError(): void + { + $this->logSuperAdmin(); + //leave the test transaction: storage opens its own one, as it does outside of tests + $this->zdb->connection->rollBack(); + + //an unknown group breaks the foreign key + $test_response = $this->postEvent(null, $this->getFormValues(['group' => '999999', 'save' => '1'])); + $this->assertSame( + ['Location' => [$this->routeparser->urlFor('events_event_add')]], + $test_response->getHeaders() + ); + $this->expectFlashData(['error_detected' => ['An error occurred while storing the event.']]); + $this->expectLogEntry(Analog::ERROR, 'Query error'); + $this->expectLogEntry(Analog::ERROR, 'Unable to store event #new | '); + $this->expectNoLogEntry(); + } + /** * Past events stay open in their form, so storing them does not close them */ diff --git a/tests/GaletteEvents/tests/units/Activity.php b/tests/GaletteEvents/tests/units/Activity.php index 27f90e1..2db8f19 100644 --- a/tests/GaletteEvents/tests/units/Activity.php +++ b/tests/GaletteEvents/tests/units/Activity.php @@ -76,7 +76,7 @@ public function testCrud(): void 'comment' => 'Test comment', ]; $this->assertTrue($activity->check($data)); - $this->assertTrue($activity->store()); + $activity->store(); $first_id = $activity->getId(); $this->assertGreaterThan(0, $first_id); //creation date column holds no time @@ -101,7 +101,7 @@ public function testCrud(): void $data['active'] = true; $data['name'] = 'Test activity edited'; $this->assertTrue($activity->check($data)); - $this->assertTrue($activity->store()); + $activity->store(); $this->assertTrue($activity->load($first_id)); $this->assertSame('Test activity edited', $activity->getName()); @@ -124,7 +124,7 @@ public function testNoComment(): void { $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); $this->assertTrue($activity->check(['name' => 'Dinner', 'active' => '1'])); - $this->assertTrue($activity->store()); + $activity->store(); $update = $this->zdb->update(EVENTS_PREFIX . \GaletteEvents\Activity::TABLE); $update->set(['comment' => null])->where([\GaletteEvents\Activity::PK => $activity->getId()]); @@ -146,7 +146,7 @@ public function testCountAndRemove(): void $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history, $id); $this->assertSame(2, $activity->countEvents()); - $this->assertTrue($activity->remove()); + $activity->remove(); $this->assertFalse((new \GaletteEvents\Activity($this->zdb, $this->login, $this->history))->load($id)); $select = $this->zdb->select(EVENTS_PREFIX . 'activitiesevents'); diff --git a/tests/GaletteEvents/tests/units/Booking.php b/tests/GaletteEvents/tests/units/Booking.php index a7f28aa..b7819a4 100644 --- a/tests/GaletteEvents/tests/units/Booking.php +++ b/tests/GaletteEvents/tests/units/Booking.php @@ -74,13 +74,13 @@ public function testActivitiesSync(): void ]; $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); $this->assertTrue($booking->check($values + ['activities' => [(string)$dinner]])); - $this->assertTrue($booking->store()); + $booking->store(); $id = (int)$booking->getId(); $this->assertSame([$dinner => true, $lodging => false], $this->getBookingActivities($id)); $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertTrue($booking->check($values + ['activities' => [(string)$lodging]])); - $this->assertTrue($booking->store()); + $booking->store(); $this->assertSame([$dinner => false, $lodging => true], $this->getBookingActivities($id)); //activity removed from event is removed from booking @@ -89,7 +89,7 @@ public function testActivitiesSync(): void $this->zdb->execute($delete); $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertTrue($booking->check($values + ['activities' => [(string)$lodging]])); - $this->assertTrue($booking->store()); + $booking->store(); $this->assertSame([$lodging => true], $this->getBookingActivities($id)); } @@ -110,26 +110,22 @@ public function testAmount(): void $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); $this->assertTrue($booking->check($values + ['amount' => '12,50'])); $this->assertSame(12.5, $booking->getAmount()); - $this->assertTrue($booking->store()); + $booking->store(); $id = (int)$booking->getId(); $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertTrue($booking->check($values + ['amount' => ''])); - $this->assertTrue($booking->store()); + $booking->store(); $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); $this->assertNull($booking->getAmount()); $this->assertTrue($booking->check($values + ['amount' => '0', 'paid' => '1'])); $this->assertSame(0.0, $booking->getAmount()); - $this->assertSame( - [_T('Please specify amount if booking has been paid ;)', 'events')], - $booking->check($values + ['amount' => '', 'paid' => '1']) - ); - $this->assertSame( - [_T('Amount must be a number.', 'events')], - $booking->check($values + ['amount' => 'ten']) - ); + $this->assertFalse($booking->check($values + ['amount' => '', 'paid' => '1'])); + $this->assertSame([_T('Please specify amount if booking has been paid ;)', 'events')], $booking->getErrors()); + $this->assertFalse($booking->check($values + ['amount' => 'ten'])); + $this->assertSame([_T('Amount must be a number.', 'events')], $booking->getErrors()); $this->expectLogEntry(\Analog\Analog::ERROR, 'Some errors has been threw attempting to edit/store a booking'); } @@ -160,23 +156,17 @@ public function testCheck(): void $this->insertBooking($event, $member_one->id); $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); - $this->assertSame( - ['Event is mandatory', 'Member is mandatory', 'Booking date is mandatory!'], - $booking->check(['number_people' => '1']) - ); - $this->assertSame( - ['There must be at least one person', '- Wrong date format (Y-m-d) for booking date!'], - $booking->check($this->getFormValues([ - 'event' => (string)$event, - 'member' => (string)$member_one->id, - 'number_people' => '0', - 'booking_date' => 'today', - ])) - ); - $this->assertSame( - [sprintf('A booking already exists for %1$s in %2$s', $member_one->sfullname, 'Event')], - $booking->check($this->getFormValues(['event' => (string)$event, 'member' => (string)$member_one->id])) - ); + $this->assertFalse($booking->check(['number_people' => '1'])); + $this->assertSame(['Event is mandatory', 'Member is mandatory', 'Booking date is mandatory!'], $booking->getErrors()); + $this->assertFalse($booking->check($this->getFormValues([ + 'event' => (string)$event, + 'member' => (string)$member_one->id, + 'number_people' => '0', + 'booking_date' => 'today', + ]))); + $this->assertSame(['There must be at least one person', '- Wrong date format (Y-m-d) for booking date!'], $booking->getErrors()); + $this->assertFalse($booking->check($this->getFormValues(['event' => (string)$event, 'member' => (string)$member_one->id]))); + $this->assertSame([sprintf('A booking already exists for %1$s in %2$s', $member_one->sfullname, 'Event')], $booking->getErrors()); $this->expectLogEntry(\Analog\Analog::ERROR, 'Some errors has been threw attempting to edit/store a booking'); } @@ -219,7 +209,7 @@ public function testRemove(): void $this->zdb->execute($insert); $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history, $id); - $this->assertTrue($booking->remove()); + $booking->remove(); $this->assertSame(0, $this->countBookings($event)); $this->assertSame([], $this->getBookingActivities($id)); } diff --git a/tests/GaletteEvents/tests/units/Event.php b/tests/GaletteEvents/tests/units/Event.php index d7e45aa..e1f1f85 100644 --- a/tests/GaletteEvents/tests/units/Event.php +++ b/tests/GaletteEvents/tests/units/Event.php @@ -70,15 +70,13 @@ public function testManagerCreatesEvent(): void $this->logMember($this->dataAdherentTwo()); $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); - $this->assertSame( - [_T('Please select a group you own!', 'events')], - $event->check($this->getFormValues(['group' => (string)$other->getId()])) - ); + $this->assertFalse($event->check($this->getFormValues(['group' => (string)$other->getId()]))); + $this->assertSame([_T('Please select a group you own!', 'events')], $event->getErrors()); $this->expectLogEntry(\Analog\Analog::ERROR, 'Some errors has been threw attempting to edit/store an event'); $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); $this->assertTrue($event->check($this->getFormValues(['group' => (string)$managed->getId()]))); - $this->assertTrue($event->store()); + $event->store(); $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, (int)$event->getId()); $this->assertSame($managed->getId(), $event->getGroup()); @@ -96,7 +94,7 @@ public function testStoreMandatoryValuesOnly(): void 'town' => 'Lille', 'begin_date' => date('Y-m-d', strtotime('+10 days')), ])); - $this->assertTrue($event->store()); + $event->store(); $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, (int)$event->getId()); $this->assertSame('Event', $event->getName()); @@ -133,7 +131,7 @@ public function testActivitiesSync(): void 'activities_ids' => $ids, 'activities_status' => ['1', '1', '2'], ]))); - $this->assertTrue($event->store()); + $event->store(); $id = (int)$event->getId(); $this->assertSame([$dinner => 1, $lodging => 1, $visit => 2], $this->getEventActivities($id)); @@ -143,7 +141,7 @@ public function testActivitiesSync(): void 'activities_ids' => $ids, 'activities_status' => ['2', '0', '2'], ]))); - $this->assertTrue($event->store()); + $event->store(); $this->assertSame([$dinner => 2, $lodging => 0, $visit => 2], $this->getEventActivities($id)); //remove two activities before storing @@ -160,7 +158,7 @@ public function testActivitiesSync(): void 'activities_ids' => [(string)$lodging, (string)$visit], 'activities_status' => ['0', '2'], ]))); - $this->assertTrue($event->store()); + $event->store(); $this->assertSame([$visit => 2], $this->getEventActivities($id)); //reloading does not keep activities of the previous event @@ -225,18 +223,12 @@ public function testCheck(): void $this->logSuperAdmin(); $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); - $this->assertSame( - ['Begin date is mandatory', 'Name is mandatory', 'Town is mandatory'], - $event->check(['begin_date' => '']) - ); - $this->assertSame( - ['- Wrong date format (Y-m-d) for Begin date!'], - $event->check($this->getFormValues(['begin_date' => 'tomorrow'])) - ); - $this->assertSame( - ['End date must be later or equal to begin date'], - $event->check($this->getFormValues(['begin_date' => '2026-10-10', 'end_date' => '2026-10-09'])) - ); + $this->assertFalse($event->check(['begin_date' => ''])); + $this->assertSame(['Begin date is mandatory', 'Name is mandatory', 'Town is mandatory'], $event->getErrors()); + $this->assertFalse($event->check($this->getFormValues(['begin_date' => 'tomorrow']))); + $this->assertSame(['- Wrong date format (Y-m-d) for Begin date!'], $event->getErrors()); + $this->assertFalse($event->check($this->getFormValues(['begin_date' => '2026-10-10', 'end_date' => '2026-10-09']))); + $this->assertSame(['End date must be later or equal to begin date'], $event->getErrors()); $this->expectLogEntry(\Analog\Analog::ERROR, 'Some errors has been threw attempting to edit/store an event'); //end date defaults to begin date @@ -271,9 +263,42 @@ public function testRemove(): void } $this->assertSame([1 => 3], $attendees); - $this->assertTrue($event->remove()); + $event->remove(); $this->assertSame(0, $this->countBookings($id)); $this->assertSame([], $this->getEventActivities($id)); $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login, $this->history))->load($id)); } + + /** + * Events are stored in the running transaction, and nothing is kept when storage fails + */ + public function testStoreTransactions(): void + { + $this->logSuperAdmin(); + //leave the test transaction: storage opens its own one, as it does outside of tests + $this->zdb->connection->rollBack(); + + //an unknown group breaks the foreign key + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); + $this->assertTrue($event->check($this->getFormValues(['group' => '999999']))); + try { + $event->store(); + $this->fail('An event of an unknown group must not be stored'); + } catch (\PDOException) { + //expected + } + $this->expectLogEntry(\Analog\Analog::ERROR, 'Query error'); + $this->assertNull($event->getId()); + $this->assertFalse($this->zdb->connection->inTransaction()); + $this->assertSame(0, $this->zdb->execute($this->zdb->select(EVENTS_PREFIX . \GaletteEvents\Event::TABLE))->count()); + + //the transaction opened by the caller is left to it + $this->zdb->connection->beginTransaction(); + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); + $this->assertTrue($event->check($this->getFormValues())); + $event->store(); + $this->assertTrue($this->zdb->connection->inTransaction()); + $this->zdb->connection->rollBack(); + $this->assertSame(0, $this->zdb->execute($this->zdb->select(EVENTS_PREFIX . \GaletteEvents\Event::TABLE))->count()); + } } From a3b6ac6dfc0cea2da3a1da540710034a579ba259 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Sun, 27 Sep 2026 11:57:41 +0200 Subject: [PATCH 3/9] Report unknown events, bookings and activities --- lib/GaletteEvents/Activity.php | 34 ++---- lib/GaletteEvents/Booking.php | 110 +++++++++--------- .../Controllers/Crud/ActivitiesController.php | 47 +++++++- .../Controllers/Crud/BookingsController.php | 66 ++++++++++- .../Controllers/Crud/EventsController.php | 47 +++++++- .../Controllers/CsvController.php | 22 +++- lib/GaletteEvents/Event.php | 57 ++++----- lib/GaletteEvents/NotFoundException.php | 20 ++++ .../Crud/tests/units/ActivitiesController.php | 15 +++ .../Crud/tests/units/BookingsController.php | 29 +++++ .../Crud/tests/units/EventsController.php | 24 ++++ .../Controllers/tests/units/CsvController.php | 18 +++ tests/GaletteEvents/tests/units/Activity.php | 10 +- tests/GaletteEvents/tests/units/Event.php | 5 +- 14 files changed, 376 insertions(+), 128 deletions(-) create mode 100644 lib/GaletteEvents/NotFoundException.php diff --git a/lib/GaletteEvents/Activity.php b/lib/GaletteEvents/Activity.php index ff62ae0..ffc5dd3 100644 --- a/lib/GaletteEvents/Activity.php +++ b/lib/GaletteEvents/Activity.php @@ -59,7 +59,7 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb $this->login = $login; $this->history = $history; - if (is_int($args) && $args > 0) { + if (is_int($args)) { $this->load($args); } elseif (is_object($args)) { $this->loadFromRS($args); @@ -67,32 +67,22 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb } /** - * Loads an activity from its id + * Load an activity from its id * - * @param int $id the identifiant for the activity to load + * @param int $id Activity identifier * - * @return bool true if query succeed, false otherwise + * @throws NotFoundException */ - public function load(int $id): bool + public function load(int $id): void { - try { - $select = $this->zdb->select($this->getTableName()); - $select->where([self::PK => $id]); - $results = $this->zdb->execute($select); - - if ($results->count() > 0) { - $this->loadFromRS($results->current()); - return true; - } else { - return false; - } - } catch (\Exception $e) { - Analog::log( - 'Cannot load activity #`' . $id . '` | ' . $e->getMessage(), - Analog::WARNING - ); - throw $e; + $select = $this->zdb->select($this->getTableName()); + $select->where([self::PK => $id]); + $results = $this->zdb->execute($select); + + if ($results->count() === 0) { + throw new NotFoundException('No activity #' . $id); } + $this->loadFromRS($results->current()); } /** diff --git a/lib/GaletteEvents/Booking.php b/lib/GaletteEvents/Booking.php index 1cb9374..126e1ec 100644 --- a/lib/GaletteEvents/Booking.php +++ b/lib/GaletteEvents/Booking.php @@ -77,34 +77,23 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb } /** - * Loads an event from its id + * Load a booking from its id * - * @param int $id the identifiant for the event to load + * @param int $id Booking identifier * - * @return bool true if query succeed, false otherwise + * @throws NotFoundException */ - public function load(int $id): bool + public function load(int $id): void { - try { - $select = $this->zdb->select($this->getTableName()); - $select->where([self::PK => $id]); - - $results = $this->zdb->execute($select); + $select = $this->zdb->select($this->getTableName()); + $select->where([self::PK => $id]); + $results = $this->zdb->execute($select); - if ($results->count() > 0) { - $this->loadFromRS($results->current()); - $this->loadActivities(); - return true; - } else { - return false; - } - } catch (\Exception $e) { - Analog::log( - 'Cannot load booking form id `' . $id . '` | ' . $e->getMessage(), - Analog::WARNING - ); - throw $e; + if ($results->count() === 0) { + throw new NotFoundException('No booking #' . $id); } + $this->loadFromRS($results->current()); + $this->loadActivities(); } /** @@ -153,38 +142,17 @@ public function check(array $values): bool $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)) { + try { + $event = new Event($this->zdb, $this->login, $this->history, (int)$values['event']); + } catch (NotFoundException) { + $event = null; + } + if ($event === null || ($event_changed && !$this->canBook($event))) { $this->errors[] = _T('This event cannot be booked.', 'events'); } - $activities = $event->getActivities(); - foreach ($activities as $aid => $entry) { - if ( - $event->isActivityRequired($aid) - && (!isset($values['activities']) || !in_array($aid, $values['activities'])) - ) { - $this->errors[] = sprintf( - //TRANS: %1$s is activity name - _T('%1$s is mandatory for this event!', 'events'), - $entry['activity']->getName() - ); - } else { - $act = [ - 'activity' => $entry['activity'], - 'checked' => (isset($values['activities']) && in_array($aid, $values['activities'])) - ]; - $this->activities[$aid] = $act; - } - } - foreach (array_keys($this->activities) as $aid) { - if (!isset($activities[$aid])) { - $this->activities_removed[$aid] = [ - Activity::PK => $aid, - self::PK => $this->id - ]; - unset($this->activities[$aid]); - } + if ($event !== null) { + $this->event = (int)$values['event']; + $this->checkActivities($event, $values['activities'] ?? []); } } @@ -332,6 +300,44 @@ public function check(array $values): bool } } + /** + * Check activities of the booking against the ones of its event + * + * @param Event $event Booked event + * @param array $checked Checked activities identifiers + */ + private function checkActivities(Event $event, array $checked): void + { + $activities = $event->getActivities(); + foreach ($activities as $aid => $entry) { + if ( + $event->isActivityRequired($aid) + && !in_array($aid, $checked) + ) { + $this->errors[] = sprintf( + //TRANS: %1$s is activity name + _T('%1$s is mandatory for this event!', 'events'), + $entry['activity']->getName() + ); + } else { + $act = [ + 'activity' => $entry['activity'], + 'checked' => in_array($aid, $checked) + ]; + $this->activities[$aid] = $act; + } + } + foreach (array_keys($this->activities) as $aid) { + if (!isset($activities[$aid])) { + $this->activities_removed[$aid] = [ + Activity::PK => $aid, + self::PK => $this->id + ]; + unset($this->activities[$aid]); + } + } + } + /** * Store the booking */ diff --git a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php index 989cdc3..e297694 100644 --- a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php +++ b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php @@ -14,6 +14,7 @@ use Galette\Controllers\Crud\AbstractPluginController; use GaletteEvents\Filters\ActivitiesList; use GaletteEvents\Activity; +use GaletteEvents\NotFoundException; use GaletteEvents\Repository\Activities; use Slim\Psr7\Request; use Slim\Psr7\Response; @@ -150,7 +151,11 @@ public function edit(Request $request, Response $response, ?int $id = null, stri } if ($id !== null && $activity->getId() != $id) { - $activity->load($id); + try { + $activity->load($id); + } catch (NotFoundException) { + return $this->redirectNotFound($response, $id); + } } // template variable declaration @@ -187,7 +192,11 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st $post = $request->getParsedBody(); $activity = new Activity($this->zdb, $this->login, $this->history); if (isset($post['id']) && !empty($post['id'])) { - $activity->load((int)$post['id']); + try { + $activity->load((int)$post['id']); + } catch (NotFoundException) { + return $this->redirectNotFound($response, (int)$post['id']); + } } $success_detected = []; @@ -256,6 +265,34 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st ->withHeader('Location', $redirect_url); } + /** + * Get the message for an activity that does not exist + * + * @param int $id Requested activity identifier + */ + private function getNotFoundMessage(int $id): string + { + return sprintf( + //TRANS: %1$s is the activity identifier + _T('No activity #%1$s.', 'events'), + $id + ); + } + + /** + * Redirect when requested activity does not exist + * + * @param int $id Requested activity identifier + */ + private function redirectNotFound(Response $response, int $id): Response + { + return $this->redirectWithErrors( + response: $response, + errors: [$this->getNotFoundMessage($id)], + redirect_url: $this->routeparser->urlFor('events_activities') + ); + } + // /CRUD - Update // CRUD - Delete @@ -289,7 +326,11 @@ public function formUri(array $args): string */ public function confirmRemoveTitle(array $args): string { - $activity = new Activity($this->zdb, $this->login, $this->history, (int)$args['id']); + try { + $activity = new Activity($this->zdb, $this->login, $this->history, (int)$args['id']); + } catch (NotFoundException) { + return $this->getNotFoundMessage((int)$args['id']); + } return sprintf( //TRANS %1$s is activity name _T('Remove activity %1$s', 'events'), diff --git a/lib/GaletteEvents/Controllers/Crud/BookingsController.php b/lib/GaletteEvents/Controllers/Crud/BookingsController.php index 2c89068..6ce2ad7 100644 --- a/lib/GaletteEvents/Controllers/Crud/BookingsController.php +++ b/lib/GaletteEvents/Controllers/Crud/BookingsController.php @@ -19,6 +19,7 @@ use GaletteEvents\Filters\BookingsList; use GaletteEvents\Booking; use GaletteEvents\Event; +use GaletteEvents\NotFoundException; use GaletteEvents\Repository\Bookings; use GaletteEvents\Repository\Events; use Slim\Psr7\Request; @@ -86,7 +87,7 @@ public function listBookings(Response $response, string|int $event, ?string $opt $filters = $this->session->{$this->getFilterName('bookings')} ?? new BookingsList(); if ($event == 'guess') { - $linked_event = $filters->event_filter; + $linked_event = $filters->event_filter ?? 'all'; } else { $linked_event = $event; } @@ -107,8 +108,23 @@ public function listBookings(Response $response, string|int $event, ?string $opt $event = null; if ($linked_event !== 'all') { + try { + $event = new Event($this->zdb, $this->login, $this->history, (int)$linked_event); + } catch (NotFoundException) { + //event may have been removed since it has been filtered + $filters->event_filter = null; + $this->session->{$this->getFilterName('bookings')} = $filters; + return $this->redirectWithErrors( + response: $response, + errors: [sprintf( + //TRANS: %1$s is the event identifier + _T('No event #%1$s.', 'events'), + (int)$linked_event + )], + redirect_url: $this->routeparser->urlFor('events_bookings', ['event' => 'all']) + ); + } $filters->event_filter = (int)$linked_event; - $event = new Event($this->zdb, $this->login, $this->history, (int)$linked_event); } //Groups @@ -370,7 +386,11 @@ public function edit(Request $request, Response $response, ?int $id = null, stri } if ($id !== null && $booking->getId() != $id) { - $booking->load($id); + try { + $booking->load($id); + } catch (NotFoundException) { + return $this->redirectNotFound($response, $id); + } } if ($booking->getId() !== null && !$booking->canEdit($this->login)) { @@ -468,7 +488,11 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st $post = $request->getParsedBody(); $booking = new Booking($this->zdb, $this->login, $this->history); if (isset($post['id']) && !empty($post['id'])) { - $booking->load((int)$post['id']); + try { + $booking->load((int)$post['id']); + } catch (NotFoundException) { + return $this->redirectNotFound($response, (int)$post['id']); + } } if ($booking->getId() !== null && !$booking->canEdit($this->login)) { @@ -577,6 +601,34 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st ->withHeader('Location', $redirect_url); } + /** + * Get the message for a booking that does not exist + * + * @param int $id Requested booking identifier + */ + private function getNotFoundMessage(int $id): string + { + return sprintf( + //TRANS: %1$s is the booking identifier + _T('No booking #%1$s.', 'events'), + $id + ); + } + + /** + * Redirect when requested booking does not exist + * + * @param int $id Requested booking identifier + */ + private function redirectNotFound(Response $response, int $id): Response + { + return $this->redirectWithErrors( + response: $response, + errors: [$this->getNotFoundMessage($id)], + redirect_url: $this->routeparser->urlFor('events_bookings', ['event' => 'all']) + ); + } + /** * Redirect when current logged-in user cannot edit a booking * @@ -630,7 +682,11 @@ public function formUri(array $args): string */ public function confirmRemoveTitle(array $args): string { - $booking = new Booking($this->zdb, $this->login, $this->history, (int)$args['id']); + try { + $booking = new Booking($this->zdb, $this->login, $this->history, (int)$args['id']); + } catch (NotFoundException) { + return $this->getNotFoundMessage((int)$args['id']); + } $member = $booking->getMember(); $event = $booking->getEvent(); return sprintf( diff --git a/lib/GaletteEvents/Controllers/Crud/EventsController.php b/lib/GaletteEvents/Controllers/Crud/EventsController.php index c699b6c..55c0600 100644 --- a/lib/GaletteEvents/Controllers/Crud/EventsController.php +++ b/lib/GaletteEvents/Controllers/Crud/EventsController.php @@ -15,6 +15,7 @@ use Galette\Controllers\Crud\AbstractPluginController; use GaletteEvents\Filters\EventsList; use GaletteEvents\Event; +use GaletteEvents\NotFoundException; use GaletteEvents\Repository\Events; use Slim\Psr7\Request; use Slim\Psr7\Response; @@ -213,7 +214,11 @@ public function edit(Request $request, Response $response, ?int $id = null, stri $can = $event->canCreate($this->login); if ($id !== null && $event->getId() != $id) { - $event->load($id); + try { + $event->load($id); + } catch (NotFoundException) { + return $this->redirectNotFound($response, $id); + } $can = $event->canEdit($this->login); } @@ -263,7 +268,11 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st $event = new Event($this->zdb, $this->login, $this->history); $can = $event->canCreate($this->login); if (isset($post['id']) && !empty($post['id'])) { - $event->load((int)$post['id']); + try { + $event->load((int)$post['id']); + } catch (NotFoundException) { + return $this->redirectNotFound($response, (int)$post['id']); + } $can = $event->canEdit($this->login); } @@ -363,6 +372,34 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st ->withHeader('Location', $redirect_url); } + /** + * Get the message for an event that does not exist + * + * @param int $id Requested event identifier + */ + private function getNotFoundMessage(int $id): string + { + return sprintf( + //TRANS: %1$s is the event identifier + _T('No event #%1$s.', 'events'), + $id + ); + } + + /** + * Redirect when requested event does not exist + * + * @param int $id Requested event identifier + */ + private function redirectNotFound(Response $response, int $id): Response + { + return $this->redirectWithErrors( + response: $response, + errors: [$this->getNotFoundMessage($id)], + redirect_url: $this->routeparser->urlFor('events_events') + ); + } + /** * Redirect when current logged-in user cannot edit an event * @@ -416,7 +453,11 @@ public function formUri(array $args): string */ public function confirmRemoveTitle(array $args): string { - $event = new Event($this->zdb, $this->login, $this->history, (int)$args['id']); + try { + $event = new Event($this->zdb, $this->login, $this->history, (int)$args['id']); + } catch (NotFoundException) { + return $this->getNotFoundMessage((int)$args['id']); + } return sprintf( //TRANS: %1$s is the event name _T('Remove event \'%1$s\'', 'events'), diff --git a/lib/GaletteEvents/Controllers/CsvController.php b/lib/GaletteEvents/Controllers/CsvController.php index 08deb3f..6930660 100644 --- a/lib/GaletteEvents/Controllers/CsvController.php +++ b/lib/GaletteEvents/Controllers/CsvController.php @@ -19,6 +19,7 @@ use Galette\IO\CsvOut; use GaletteEvents\Event; use GaletteEvents\Filters\BookingsList; +use GaletteEvents\NotFoundException; use GaletteEvents\Repository\Bookings; /** @@ -96,9 +97,22 @@ public function bookingsExport(Request $request, Response $response, ?int $id = _T('Number of persons', 'events'), ]; - //activities are onl:y available for one event - if ($filters->event_filter > 0) { - $event = new Event($this->zdb, $this->login, $this->history, (int)$filters->event_filter); + //activities are only available for one event + $event = null; + if (is_numeric($filters->event_filter) && (int)$filters->event_filter > 0) { + try { + $event = new Event($this->zdb, $this->login, $this->history, (int)$filters->event_filter); + } catch (NotFoundException) { + return $this->redirectWithErrors( + response: $response, + errors: [sprintf( + //TRANS: %1$s is the event identifier + _T('No event #%1$s.', 'events'), + (int)$filters->event_filter + )], + redirect_url: $this->routeparser->urlFor('events_bookings', ['event' => 'all']) + ); + } $activities = $event->getActivities(); foreach ($activities as $activity) { $labels[] = $activity['activity']->getName(); @@ -146,7 +160,7 @@ public function bookingsExport(Request $request, Response $response, ?int $id = $booking->getNumberPeople() ]; - if ($filters->event_filter > 0) { + if ($event !== null) { $bactivities = $booking->getActivities(); foreach (array_keys($activities) as $aid) { $entry[] = isset($bactivities[$aid]) && $bactivities[$aid]['checked'] ? _T('Yes') : _T('No'); diff --git a/lib/GaletteEvents/Event.php b/lib/GaletteEvents/Event.php index 0cd7857..92be9f0 100644 --- a/lib/GaletteEvents/Event.php +++ b/lib/GaletteEvents/Event.php @@ -67,49 +67,36 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb $this->zdb = $zdb; $this->login = $login; $this->history = $history; - if ($args == null || is_int($args)) { - if (is_int($args) && $args > 0) { - $this->load($args); - } else { - $now = date('Y-m-d'); - $this->begin_date = $now; - $this->end_date = $now; - } - } elseif (is_object($args)) { + if (is_int($args)) { + $this->load($args); + } elseif ($args !== null) { $this->loadFromRS($args); $this->loadActivities(); + } else { + $now = date('Y-m-d'); + $this->begin_date = $now; + $this->end_date = $now; } } /** - * Loads an event from its id + * Load an event from its id * - * @param int $id the identifiant for the event to load + * @param int $id Event identifier * - * @return bool true if query succeed, false otherwise + * @throws NotFoundException */ - public function load(int $id): bool + public function load(int $id): void { - try { - $select = $this->zdb->select($this->getTableName()); - $select->where([self::PK => $id]); - - $results = $this->zdb->execute($select); + $select = $this->zdb->select($this->getTableName()); + $select->where([self::PK => $id]); + $results = $this->zdb->execute($select); - if ($results->count() > 0) { - $this->loadFromRS($results->current()); - $this->loadActivities(); - return true; - } else { - return false; - } - } catch (\Exception $e) { - Analog::log( - 'Cannot load event form id `' . $id . '` | ' . $e->getMessage(), - Analog::WARNING - ); - throw $e; + if ($results->count() === 0) { + throw new NotFoundException('No event #' . $id); } + $this->loadFromRS($results->current()); + $this->loadActivities(); } /** @@ -409,8 +396,12 @@ public function store(): void */ private function getActiveActivity(int $id): ?Activity { - $activity = new Activity($this->zdb, $this->login, $this->history, $id); - return $activity->getId() !== null && $activity->isActive() ? $activity : null; + try { + $activity = new Activity($this->zdb, $this->login, $this->history, $id); + } catch (NotFoundException) { + return null; + } + return $activity->isActive() ? $activity : null; } /** diff --git a/lib/GaletteEvents/NotFoundException.php b/lib/GaletteEvents/NotFoundException.php new file mode 100644 index 0000000..5587fc8 --- /dev/null +++ b/lib/GaletteEvents/NotFoundException.php @@ -0,0 +1,20 @@ + + */ +class NotFoundException extends \RuntimeException +{ +} diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/ActivitiesController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/ActivitiesController.php index 0fd4a56..f98ac8e 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/ActivitiesController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/ActivitiesController.php @@ -141,4 +141,19 @@ public function testStaffManagesActivities(): void $this->assertSame(1, $this->countActivities('Lodging')); $this->expectNoLogEntry(); } + + /** + * Unknown activities are reported, instead of showing an empty form + */ + public function testUnknownActivity(): void + { + $this->logSuperAdmin(); + $id = $this->insertActivity('Dinner'); + $this->cleanEvents(); + + $test_response = $this->app->handle($this->createRequest('events_activity_edit', ['id' => (string)$id])); + $this->assertSame(['Location' => [$this->routeparser->urlFor('events_activities')]], $test_response->getHeaders()); + $this->expectFlashData(['error_detected' => ['No activity #' . $id . '.']]); + $this->expectNoLogEntry(); + } } diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php index d30bb02..eec6ab8 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php @@ -520,6 +520,35 @@ public function testList(): void $this->assertStringContainsString($member_two->sfullname, $body); } + /** + * Unknown bookings are reported, as well as events removed since they have been filtered + */ + public function testUnknownBooking(): void + { + $member_one = $this->getMemberOne(); + $event = $this->insertEvent('Event'); + $id = $this->insertBooking($event, $member_one->id); + $this->logSuperAdmin(); + + //event is remembered in list filters + $test_response = $this->app->handle($this->createRequest('events_bookings', ['event' => (string)$event])); + $this->assertSame(200, $test_response->getStatusCode()); + $this->cleanEvents(); + + $expected = ['Location' => [$this->routeparser->urlFor('events_bookings', ['event' => 'all'])]]; + $this->assertSame($expected, $this->getBookingForm($id)->getHeaders()); + $this->expectFlashData(['error_detected' => ['No booking #' . $id . '.']]); + + $test_response = $this->app->handle($this->createRequest('events_bookings', ['event' => 'guess'])); + $this->assertSame($expected, $test_response->getHeaders()); + $this->expectFlashData(['error_detected' => ['No event #' . $event . '.']]); + $this->assertNull($this->session->plugin_events_bookings_filter->event_filter); + + $test_response = $this->app->handle($this->createRequest('events_bookings', ['event' => 'guess'])); + $this->assertSame(200, $test_response->getStatusCode()); + $this->expectNoLogEntry(); + } + /** * Bookings list filters are stored in session */ diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php index 335b09d..25086de 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php @@ -308,6 +308,30 @@ public function testRemovalTitle(): void $this->assertStringContainsString("Remove event 'Party' - ", $body); } + /** + * Unknown events are reported, instead of showing an empty form + */ + public function testUnknownEvent(): void + { + $this->logSuperAdmin(); + $id = $this->insertEvent('Removed event'); + $this->cleanEvents(); + $expected = ['Location' => [$this->routeparser->urlFor('events_events')]]; + + $test_response = $this->app->handle($this->createRequest('events_event_edit', ['id' => (string)$id])); + $this->assertSame($expected, $test_response->getHeaders()); + $this->expectFlashData(['error_detected' => ['No event #' . $id . '.']]); + + $test_response = $this->postEvent($id, $this->getFormValues(['save' => '1'])); + $this->assertSame($expected, $test_response->getHeaders()); + $this->expectFlashData(['error_detected' => ['No event #' . $id . '.']]); + + $test_response = $this->app->handle($this->createRequest('events_remove_event', ['id' => (string)$id])); + $this->assertSame(200, $test_response->getStatusCode()); + $this->assertStringContainsString('<title>No event #' . $id . '. - ', (string)$test_response->getBody()); + $this->expectNoLogEntry(); + } + /** * Events list shows events current user can see */ diff --git a/tests/GaletteEvents/Controllers/tests/units/CsvController.php b/tests/GaletteEvents/Controllers/tests/units/CsvController.php index cc4b1a1..816023d 100644 --- a/tests/GaletteEvents/Controllers/tests/units/CsvController.php +++ b/tests/GaletteEvents/Controllers/tests/units/CsvController.php @@ -124,6 +124,24 @@ public function testExportReadsBookingsFiltersOnly(): void $this->assertSame(['text/csv'], $test_response->getHeader('Content-Type')); } + /** + * Export of an unknown event is reported + */ + public function testExportUnknownEvent(): void + { + $this->logSuperAdmin(); + $event = $this->insertEvent('Removed event'); + $this->cleanEvents(); + + $test_response = $this->app->handle($this->createRequest('event_bookings_export', ['id' => (string)$event])); + $this->assertSame( + ['Location' => [$this->routeparser->urlFor('events_bookings', ['event' => 'all'])]], + $test_response->getHeaders() + ); + $this->expectFlashData(['error_detected' => ['No event #' . $event . '.']]); + $this->expectNoLogEntry(); + } + /** * Bookings without a known payment method are exported */ diff --git a/tests/GaletteEvents/tests/units/Activity.php b/tests/GaletteEvents/tests/units/Activity.php index 2db8f19..f64e9b9 100644 --- a/tests/GaletteEvents/tests/units/Activity.php +++ b/tests/GaletteEvents/tests/units/Activity.php @@ -82,7 +82,7 @@ public function testCrud(): void //creation date column holds no time $this->assertSame(date('Y-m-d'), $activity->getCreationDate(false)); - $this->assertTrue($activity->load($first_id)); + $activity->load($first_id); $this->assertSame('Test activity', $activity->getName()); $this->assertSame('Test comment', $activity->getComment()); $this->assertFalse($activity->isActive()); @@ -102,7 +102,7 @@ public function testCrud(): void $data['name'] = 'Test activity edited'; $this->assertTrue($activity->check($data)); $activity->store(); - $this->assertTrue($activity->load($first_id)); + $activity->load($first_id); $this->assertSame('Test activity edited', $activity->getName()); $this->assertTrue($activity->isActive()); @@ -114,7 +114,8 @@ public function testCrud(): void public function testLoadError(): void { $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); - $this->assertFalse($activity->load(999)); + $this->expectException(\GaletteEvents\NotFoundException::class); + $activity->load(999); } /** @@ -147,7 +148,8 @@ public function testCountAndRemove(): void $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history, $id); $this->assertSame(2, $activity->countEvents()); $activity->remove(); - $this->assertFalse((new \GaletteEvents\Activity($this->zdb, $this->login, $this->history))->load($id)); + $this->expectException(\GaletteEvents\NotFoundException::class); + (new \GaletteEvents\Activity($this->zdb, $this->login, $this->history))->load($id); $select = $this->zdb->select(EVENTS_PREFIX . 'activitiesevents'); $select->where([\GaletteEvents\Activity::PK => $id]); diff --git a/tests/GaletteEvents/tests/units/Event.php b/tests/GaletteEvents/tests/units/Event.php index e1f1f85..12eb899 100644 --- a/tests/GaletteEvents/tests/units/Event.php +++ b/tests/GaletteEvents/tests/units/Event.php @@ -163,7 +163,7 @@ public function testActivitiesSync(): void //reloading does not keep activities of the previous event $other = (int)$this->insertEvent('Other event'); - $this->assertTrue($event->load($other)); + $event->load($other); $this->assertSame([], $event->getActivities()); } @@ -266,7 +266,8 @@ public function testRemove(): void $event->remove(); $this->assertSame(0, $this->countBookings($id)); $this->assertSame([], $this->getEventActivities($id)); - $this->assertFalse((new \GaletteEvents\Event($this->zdb, $this->login, $this->history))->load($id)); + $this->expectException(\GaletteEvents\NotFoundException::class); + (new \GaletteEvents\Event($this->zdb, $this->login, $this->history))->load($id); } /** From 88c4bebf38318b0d884a22afe3adb65b8d76d817 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski <johan@x-tnd.be> Date: Sun, 27 Sep 2026 12:01:30 +0200 Subject: [PATCH 4/9] Initialize entities fully --- lib/GaletteEvents/Activity.php | 16 +++++------ lib/GaletteEvents/Booking.php | 32 +++++++++++++-------- lib/GaletteEvents/Event.php | 24 +++++++++------- tests/GaletteEvents/tests/units/Booking.php | 17 +++++++++++ tests/GaletteEvents/tests/units/Event.php | 16 +++++++++++ 5 files changed, 75 insertions(+), 30 deletions(-) diff --git a/lib/GaletteEvents/Activity.php b/lib/GaletteEvents/Activity.php index ffc5dd3..496bc60 100644 --- a/lib/GaletteEvents/Activity.php +++ b/lib/GaletteEvents/Activity.php @@ -37,10 +37,10 @@ class Activity /** @var array<string> */ private array $errors = []; - private int $id; - private string $name; + private ?int $id = null; + private string $name = ''; private bool $active = false; - private string $creation_date; + private ?string $creation_date = null; private string $comment = ''; /** @@ -163,7 +163,7 @@ public function store(): void 'comment' => $this->comment ]; - if (empty($this->id)) { + if ($this->id === null) { //we're inserting a new activity $this->creation_date = date("Y-m-d"); $values['creation_date'] = $this->creation_date; @@ -218,7 +218,7 @@ public function store(): void */ public function getId(): ?int { - return $this->id ?? null; + return $this->id; } /** @@ -226,7 +226,7 @@ public function getId(): ?int */ public function getName(): string { - return $this->name ?? ''; + return $this->name; } /** @@ -237,7 +237,7 @@ public function getName(): string */ private function getDate(string $prop, bool $formatted = true): string { - if (!isset($this->$prop)) { + if ($this->$prop === null) { return ''; } @@ -288,7 +288,7 @@ public function getComment(): string */ public function countEvents(): int { - if (empty($this->id)) { + if ($this->id === null) { return 0; } diff --git a/lib/GaletteEvents/Booking.php b/lib/GaletteEvents/Booking.php index 126e1ec..105b10e 100644 --- a/lib/GaletteEvents/Booking.php +++ b/lib/GaletteEvents/Booking.php @@ -35,9 +35,9 @@ class Booking /** @var array<string> */ private array $errors = []; - private int $id; - private int $event; - private int $member; + private ?int $id = null; + private ?int $event = null; + private ?int $member = null; private string $date = ''; private bool $paid = false; private ?float $amount = null; @@ -51,7 +51,7 @@ class Booking private array $activities = []; /** @var array<int, array<string,mixed>> */ private array $activities_removed = []; - private string $creation_date; + private ?string $creation_date = null; /** * Default constructor @@ -267,7 +267,7 @@ public function check(array $values): bool Event::PK => $this->event, Adherent::PK => $this->member ]); - if (isset($this->id)) { + if ($this->id !== null) { $select->where->notEqualTo( self::PK, $this->id @@ -343,7 +343,7 @@ private function checkActivities(Event $event, array $checked): void */ public function store(): void { - $new = empty($this->id); + $new = $this->id === null; $transaction = !$this->zdb->connection->inTransaction(); if ($transaction) { $this->zdb->connection->beginTransaction(); @@ -532,7 +532,7 @@ public function store(): void } if ($new) { //nothing has been stored - unset($this->id); + $this->id = null; } throw $e; } @@ -543,7 +543,7 @@ public function store(): void */ public function getId(): ?int { - return $this->id ?? null; + return $this->id; } /** @@ -551,7 +551,7 @@ public function getId(): ?int */ public function getEventId(): ?int { - return $this->event ?? null; + return $this->event; } /** @@ -559,7 +559,7 @@ public function getEventId(): ?int */ public function getEvent(): ?Event { - if (isset($this->event)) { + if ($this->event !== null) { return new Event($this->zdb, $this->login, $this->history, $this->event); } return null; @@ -570,11 +570,11 @@ public function getEvent(): ?Event */ public function getMemberId(): ?int { - return $this->member ?? null; + return $this->member; } /** - * Get member + * Get member, empty if booking has no member yet */ public function getMember(): Adherent { @@ -588,6 +588,10 @@ public function getMember(): Adherent */ public function getDate(bool $formatted = true): string { + if ($this->date === '') { + return ''; + } + if ($formatted === true) { $date = new \DateTime($this->date); return $date->format(__("Y-m-d")); @@ -666,6 +670,10 @@ public function getNumberPeople(): int */ public function getCreationDate(bool $formatted = true): string { + if ($this->creation_date === null) { + return ''; + } + if ($formatted === true) { $date = new \DateTime($this->creation_date); return $date->format(__("Y-m-d")); diff --git a/lib/GaletteEvents/Event.php b/lib/GaletteEvents/Event.php index 92be9f0..a60a22c 100644 --- a/lib/GaletteEvents/Event.php +++ b/lib/GaletteEvents/Event.php @@ -35,15 +35,15 @@ class Event /** @var array<string> */ private array $errors = []; - private int $id; - private string $name; + private ?int $id = null; + private string $name = ''; private string $address = ''; private string $zip = ''; private string $town = ''; private ?string $country = null; private string $begin_date; private string $end_date; - private string $creation_date; + private ?string $creation_date = null; private bool $open = true; private ?int $group = null; private string $comment = ''; @@ -301,7 +301,7 @@ public function check(array $values): bool */ public function store(): void { - $new = empty($this->id); + $new = $this->id === null; $transaction = !$this->zdb->connection->inTransaction(); if ($transaction) { $this->zdb->connection->beginTransaction(); @@ -383,7 +383,7 @@ public function store(): void } if ($new) { //nothing has been stored - unset($this->id); + $this->id = null; } throw $e; } @@ -468,15 +468,15 @@ private function storeActivities(): void */ public function getId(): ?int { - return $this->id ?? null; + return $this->id; } /** * Get event name */ - public function getName(): ?string + public function getName(): string { - return $this->name ?? null; + return $this->name; } /** @@ -508,7 +508,7 @@ public function getTown(): string */ public function getCountry(): ?string { - return $this->country ?? null; + return $this->country; } /** @@ -516,7 +516,7 @@ public function getCountry(): ?string */ public function getGroup(): ?int { - return $this->group ?? null; + return $this->group; } /** @@ -540,6 +540,10 @@ public function getGroupName(): string */ private function getDate(string $prop, bool $formatted = true): string { + if ($this->$prop === null) { + return ''; + } + if ($formatted === true) { $date = new \DateTime($this->$prop); return $date->format(__("Y-m-d")); diff --git a/tests/GaletteEvents/tests/units/Booking.php b/tests/GaletteEvents/tests/units/Booking.php index b7819a4..06e11b6 100644 --- a/tests/GaletteEvents/tests/units/Booking.php +++ b/tests/GaletteEvents/tests/units/Booking.php @@ -34,6 +34,23 @@ public function tearDown(): void parent::tearDown(); } + /** + * New bookings are fully initialized + */ + public function testEmpty(): void + { + $booking = new \GaletteEvents\Booking($this->zdb, $this->login, $this->history); + $this->assertNull($booking->getId()); + $this->assertNull($booking->getEventId()); + $this->assertNull($booking->getEvent()); + $this->assertNull($booking->getMemberId()); + $this->assertNull($booking->getMember()->id); + $this->assertSame('', $booking->getDate()); + $this->assertSame('', $booking->getCreationDate()); + $this->assertSame([], $booking->getActivities()); + $this->assertSame([], $booking->getErrors()); + } + /** * Optional values may be NULL in database */ diff --git a/tests/GaletteEvents/tests/units/Event.php b/tests/GaletteEvents/tests/units/Event.php index 12eb899..93491aa 100644 --- a/tests/GaletteEvents/tests/units/Event.php +++ b/tests/GaletteEvents/tests/units/Event.php @@ -58,6 +58,22 @@ private function getFormValues(array $values = []): array ]; } + /** + * New events are fully initialized + */ + public function testEmpty(): void + { + $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history); + $this->assertNull($event->getId()); + $this->assertSame('', $event->getName()); + $this->assertSame('', $event->getCreationDate()); + $this->assertSame(date('Y-m-d'), $event->getBeginDate(false)); + $this->assertSame(date('Y-m-d'), $event->getEndDate(false)); + $this->assertNull($event->getGroup()); + $this->assertSame([], $event->getActivities()); + $this->assertSame([], $event->getErrors()); + } + /** * Group managers create events of the groups they manage */ From a599bbc790f75b1732693824d3a9f2b7c051ee00 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski <johan@x-tnd.be> Date: Sun, 27 Sep 2026 12:06:07 +0200 Subject: [PATCH 5/9] Format dates in templates, rename foreground color getter --- lib/GaletteEvents/Activity.php | 34 ++----------- lib/GaletteEvents/Booking.php | 39 ++++----------- .../Controllers/Crud/ActivitiesController.php | 8 +-- lib/GaletteEvents/Event.php | 49 +++++-------------- lib/GaletteEvents/PluginGaletteEvents.php | 2 +- lib/GaletteEvents/Repository/Activities.php | 2 +- lib/GaletteEvents/Repository/Events.php | 22 ++++++--- templates/default/activities.html.twig | 2 +- templates/default/booking.html.twig | 2 +- templates/default/bookings.html.twig | 2 +- templates/default/event.html.twig | 4 +- templates/default/events.html.twig | 2 +- .../Crud/tests/units/BookingsController.php | 14 ++++++ .../Repository/tests/units/Bookings.php | 2 +- tests/GaletteEvents/tests/units/Activity.php | 18 +++---- tests/GaletteEvents/tests/units/Booking.php | 4 +- tests/GaletteEvents/tests/units/Event.php | 6 +-- 17 files changed, 81 insertions(+), 131 deletions(-) diff --git a/lib/GaletteEvents/Activity.php b/lib/GaletteEvents/Activity.php index 496bc60..5275f36 100644 --- a/lib/GaletteEvents/Activity.php +++ b/lib/GaletteEvents/Activity.php @@ -13,7 +13,6 @@ use ArrayObject; use Galette\Core\Db; use Galette\Core\History; -use Galette\Core\Login; use Analog\Analog; use Laminas\Db\Sql\Expression; @@ -32,7 +31,6 @@ class Activity public const int REQUIRED = 2; private Db $zdb; - private Login $login; private History $history; /** @var array<string> */ private array $errors = []; @@ -47,16 +45,14 @@ class Activity * Default constructor * * @param Db $zdb Database instance - * @param Login $login Login instance * @param History $history History instance * @param null|int|ArrayObject<string, mixed> $args Either a ResultSet row or its id for to load * a specific activity, or null to just * instanciate object */ - public function __construct(Db $zdb, Login $login, History $history, int|ArrayObject|null $args = null) + public function __construct(Db $zdb, History $history, int|ArrayObject|null $args = null) { $this->zdb = $zdb; - $this->login = $login; $this->history = $history; if (is_int($args)) { @@ -230,33 +226,11 @@ public function getName(): string } /** - * Get date - * - * @param string $prop Property to use - * @param bool $formatted Return date formatted, raw if false - */ - private function getDate(string $prop, bool $formatted = true): string - { - if ($this->$prop === null) { - return ''; - } - - if ($formatted === true) { - $date = new \DateTime($this->$prop); - return $date->format(__("Y-m-d")); - } else { - return $this->$prop; - } - } - - /** - * Get creation date - * - * @param bool $formatted Return date formatted, raw if false + * Get creation date, as Y-m-d */ - public function getCreationDate(bool $formatted = true): string + public function getCreationDate(): string { - return $this->getDate('creation_date', $formatted); + return $this->creation_date ?? ''; } /** diff --git a/lib/GaletteEvents/Booking.php b/lib/GaletteEvents/Booking.php index 105b10e..3848325 100644 --- a/lib/GaletteEvents/Booking.php +++ b/lib/GaletteEvents/Booking.php @@ -70,9 +70,11 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb $this->history = $history; if (is_int($args)) { $this->load($args); - } elseif (is_object($args)) { + } elseif ($args !== null) { $this->loadFromRS($args); $this->loadActivities(); + } else { + $this->date = date('Y-m-d'); } } @@ -582,22 +584,11 @@ public function getMember(): Adherent } /** - * Get date - * - * @param bool $formatted Return date formatted, raw if false + * Get booking date, as Y-m-d */ - public function getDate(bool $formatted = true): string + public function getDate(): string { - if ($this->date === '') { - return ''; - } - - if ($formatted === true) { - $date = new \DateTime($this->date); - return $date->format(__("Y-m-d")); - } else { - return $this->date; - } + return $this->date; } /** @@ -664,22 +655,11 @@ public function getNumberPeople(): int } /** - * Get creation date - * - * @param bool $formatted Return date formatted, raw if false + * Get creation date, as Y-m-d */ - public function getCreationDate(bool $formatted = true): string + public function getCreationDate(): string { - if ($this->creation_date === null) { - return ''; - } - - if ($formatted === true) { - $date = new \DateTime($this->creation_date); - return $date->format(__("Y-m-d")); - } else { - return $this->creation_date; - } + return $this->creation_date ?? ''; } /** @@ -743,7 +723,6 @@ public function loadActivities(): void $this->activities[$result[Activity::PK]] = [ 'activity' => new Activity( $this->zdb, - $this->login, $this->history, (int)$result[Activity::PK] ), diff --git a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php index e297694..29fcd8f 100644 --- a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php +++ b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php @@ -147,7 +147,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri $activity = $this->session->plugin_events_activity; $this->session->plugin_events_activity = null; } else { - $activity = new Activity($this->zdb, $this->login, $this->history); + $activity = new Activity($this->zdb, $this->history); } if ($id !== null && $activity->getId() != $id) { @@ -190,7 +190,7 @@ public function edit(Request $request, Response $response, ?int $id = null, stri public function doEdit(Request $request, Response $response, ?int $id = null, string $action = 'edit'): Response { $post = $request->getParsedBody(); - $activity = new Activity($this->zdb, $this->login, $this->history); + $activity = new Activity($this->zdb, $this->history); if (isset($post['id']) && !empty($post['id'])) { try { $activity->load((int)$post['id']); @@ -327,7 +327,7 @@ public function formUri(array $args): string public function confirmRemoveTitle(array $args): string { try { - $activity = new Activity($this->zdb, $this->login, $this->history, (int)$args['id']); + $activity = new Activity($this->zdb, $this->history, (int)$args['id']); } catch (NotFoundException) { return $this->getNotFoundMessage((int)$args['id']); } @@ -346,7 +346,7 @@ public function confirmRemoveTitle(array $args): string */ protected function doDelete(array $args, array $post): bool { - $activity = new Activity($this->zdb, $this->login, $this->history, (int)$args['id']); + $activity = new Activity($this->zdb, $this->history, (int)$args['id']); $activity->remove(); return true; } diff --git a/lib/GaletteEvents/Event.php b/lib/GaletteEvents/Event.php index a60a22c..0895999 100644 --- a/lib/GaletteEvents/Event.php +++ b/lib/GaletteEvents/Event.php @@ -397,7 +397,7 @@ public function store(): void private function getActiveActivity(int $id): ?Activity { try { - $activity = new Activity($this->zdb, $this->login, $this->history, $id); + $activity = new Activity($this->zdb, $this->history, $id); } catch (NotFoundException) { return null; } @@ -533,53 +533,27 @@ public function getGroupName(): string } /** - * Get date - * - * @param string $prop Property to use - * @param bool $formatted Return date formatted, raw if false + * Get creation date, as Y-m-d */ - private function getDate(string $prop, bool $formatted = true): string + public function getCreationDate(): string { - if ($this->$prop === null) { - return ''; - } - - if ($formatted === true) { - $date = new \DateTime($this->$prop); - return $date->format(__("Y-m-d")); - } else { - return $this->$prop; - } + return $this->creation_date ?? ''; } /** - * Get creation date - * - * @param bool $formatted Return date formatted, raw if false + * Get begin date, as Y-m-d */ - public function getCreationDate(bool $formatted = true): string + public function getBeginDate(): string { - return $this->getDate('creation_date', $formatted); + return $this->begin_date; } /** - * Get begin date - * - * @param bool $formatted Return date formatted, raw if false - */ - public function getBeginDate(bool $formatted = true): string - { - return $this->getDate('begin_date', $formatted); - } - - /** - * Get end date - * - * @param bool $formatted Return date formatted, raw if false + * Get end date, as Y-m-d */ - public function getEndDate(bool $formatted = true): string + public function getEndDate(): string { - return $this->getDate('end_date', $formatted); + return $this->end_date; } /** @@ -673,7 +647,6 @@ public function loadActivities(): void $this->activities[$result[Activity::PK]] = [ 'activity' => new Activity( $this->zdb, - $this->login, $this->history, (int)$result[Activity::PK] ), @@ -773,7 +746,7 @@ public function getErrors(): array /** * Get foreground contrasted color for current background color */ - public function getForegoundColor(): string + public function getForegroundColor(): string { $bgcolor = trim($this->color ?? '#ffffff', '#'); $r = hexdec(substr($bgcolor, 0, 2)); diff --git a/lib/GaletteEvents/PluginGaletteEvents.php b/lib/GaletteEvents/PluginGaletteEvents.php index 7704311..883c417 100644 --- a/lib/GaletteEvents/PluginGaletteEvents.php +++ b/lib/GaletteEvents/PluginGaletteEvents.php @@ -206,7 +206,7 @@ public function getNews(): ?Entry foreach ($list as $event) { $posts[] = new Post( title: $event->getName(), - date: $event->getBeginDate() + date: (new \DateTime($event->getBeginDate()))->format(__('Y-m-d')) ); } diff --git a/lib/GaletteEvents/Repository/Activities.php b/lib/GaletteEvents/Repository/Activities.php index 5463e9d..6114d8c 100644 --- a/lib/GaletteEvents/Repository/Activities.php +++ b/lib/GaletteEvents/Repository/Activities.php @@ -79,7 +79,7 @@ public function getList(): array|ResultSet $activities = []; foreach ($results as $row) { - $activity = new Activity($this->zdb, $this->login, $this->history, $row); + $activity = new Activity($this->zdb, $this->history, $row); $activities[] = $activity; } diff --git a/lib/GaletteEvents/Repository/Events.php b/lib/GaletteEvents/Repository/Events.php index 57f25e0..48eb091 100644 --- a/lib/GaletteEvents/Repository/Events.php +++ b/lib/GaletteEvents/Repository/Events.php @@ -160,23 +160,23 @@ public function getList(bool $bookable = false, bool $fullcalendar = false, bool $row['title'] = $row['name']; $row['can_edit'] = $event->canEdit($this->login); $row['start'] = $row['begin_date']; - $end_date = new \DateTime($event->getEndDate(false)); + $end_date = new \DateTime($event->getEndDate()); if ($fullcalendar === true) { $end_date = $end_date->modify('+1 day'); - $row['textColor'] = $event->getForegoundColor(); + $row['textColor'] = $event->getForegroundColor(); } $row['end'] = $end_date->format('Y-m-d'); //extended description - $row['begin_date_fmt'] = $event->getBeginDate(); - $row['end_date_fmt'] = $event->getEndDate(); + $row['begin_date_fmt'] = $this->formatDate($event->getBeginDate()); + $row['end_date_fmt'] = $this->formatDate($event->getEndDate()); $description = '<h4>'; $description .= _T('Event information', 'events'); $description .= '</h4>'; $description .= '<ul class="ui bulleted list">'; $pattern = '<li><strong>%1$s</strong> %2$s</li>'; - $description .= sprintf($pattern, _T("Start date:", "events"), $event->getBeginDate()); - $description .= sprintf($pattern, _T("End date:", "events"), $event->getEndDate()); + $description .= sprintf($pattern, _T("Start date:", "events"), $row['begin_date_fmt']); + $description .= sprintf($pattern, _T("End date:", "events"), $row['end_date_fmt']); $description .= sprintf($pattern, _T("Location:", "events"), $this->escape($event->getTown())); if ($comment = $event->getComment()) { $description .= sprintf($pattern, _T("Comment:", "events"), $this->escape($comment)); @@ -233,6 +233,16 @@ public function getList(bool $bookable = false, bool $fullcalendar = false, bool } } + /** + * Format a date for the calendar + * + * @param string $date Date, as Y-m-d + */ + private function formatDate(string $date): string + { + return (new \DateTime($date))->format(__('Y-m-d')); + } + /** * Escape a value typed by users for the calendar HTML description * diff --git a/templates/default/activities.html.twig b/templates/default/activities.html.twig index 228c900..b8723c4 100644 --- a/templates/default/activities.html.twig +++ b/templates/default/activities.html.twig @@ -70,7 +70,7 @@ {% set aid = activity.getId() %} <a href="{{ url_for("events_activity_edit", {"id": aid}) }}">{{ activity.getName() }}</a> </td> - <td class="collapsing" data-col-label="{{ _T("Creation date", "events") }}">{{ activity.getCreationDate() }}</td> + <td class="collapsing" data-col-label="{{ _T("Creation date", "events") }}">{{ activity.getCreationDate()|date(_T("Y-m-d")) }}</td> <td class="collapsing" data-col-label="{{ _T("Events", "events") }}">{{ activity.countEvents() }}</td> <td class="center id_row collapsing" data-col-label="{{ _T("Is active", "events") }}"> <i class="toggle {% if activity.isActive() %}on{% else %}off{% endif %} icon tooltip" data-html="{% if activity.isActive() %}{{ _T("Active", "events") }}{% else %}{{ _T("Inactive", "events") }}{% endif %}"></i> diff --git a/templates/default/booking.html.twig b/templates/default/booking.html.twig index fb19710..b249b93 100644 --- a/templates/default/booking.html.twig +++ b/templates/default/booking.html.twig @@ -16,7 +16,7 @@ <div class="active content"> {% include "components/forms/date.html.twig" with { id: 'booking_date', - value: booking.getDate(), + value: booking.getDate()|date(_T("Y-m-d")), label: _T("Booking date", "events"), required: true } %} diff --git a/templates/default/bookings.html.twig b/templates/default/bookings.html.twig index 7dc544d..d373d74 100644 --- a/templates/default/bookings.html.twig +++ b/templates/default/bookings.html.twig @@ -173,7 +173,7 @@ {{ booking.getMember().sfullname }} </a> </td> - <td class="{{ rclass }} collapsing" data-col-label="{{ _T("Booking date", "events") }}">{{ booking.getDate() }}</td> + <td class="{{ rclass }} collapsing" data-col-label="{{ _T("Booking date", "events") }}">{{ booking.getDate()|date(_T("Y-m-d")) }}</td> <td class="{{ rclass }} center collapsing" data-col-label="{% if booking.isPaid() %}{{ _T("Paid", "events") }}{% else %}{{ _T("Not paid", "events") }}{% endif %}"> <i class="money bill wave{% if not booking.isPaid() %} red{% endif %} icon tooltip" data-html="{% if booking.isPaid() %}{{ _T("Paid", "events") }}{% else %}{{ _T("Not paid", "events") }}{% endif %}"></i> <span class="visually-hidden"> diff --git a/templates/default/event.html.twig b/templates/default/event.html.twig index f790e8a..fd08bca 100644 --- a/templates/default/event.html.twig +++ b/templates/default/event.html.twig @@ -37,13 +37,13 @@ {% include "components/forms/date.html.twig" with { id: 'begin_date', - value: event.getBeginDate(), + value: event.getBeginDate()|date(_T("Y-m-d")), label: _T("Begin date", "events"), } %} {% include "components/forms/date.html.twig" with { id: 'end_date', - value: event.getEndDate(), + value: event.getEndDate()|date(_T("Y-m-d")), label: _T("End date", "events"), } %} diff --git a/templates/default/events.html.twig b/templates/default/events.html.twig index e0262e4..60f6dd1 100644 --- a/templates/default/events.html.twig +++ b/templates/default/events.html.twig @@ -74,7 +74,7 @@ {{ event.getName() }} {% endif %} </td> - <td class="collapsing" data-col-label="{{ _T("Date", "events") }}">{{ event.getBeginDate() }}</td> + <td class="collapsing" data-col-label="{{ _T("Date", "events") }}">{{ event.getBeginDate()|date(_T("Y-m-d")) }}</td> <td data-col-label="{{ _T("Town", "events") }}">{{ event.getTown() }}</td> <td data-col-label="{{ _T("Group", "events") }}">{{ event.getGroupName() }}</td> <td class="center id_row collapsing" data-col-label="{{ _T("Open", "events") }}"> diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php index eec6ab8..53016e4 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php @@ -520,6 +520,20 @@ public function testList(): void $this->assertStringContainsString($member_two->sfullname, $body); } + /** + * New bookings are dated from today + */ + public function testNewBookingIsDatedToday(): void + { + $this->logSuperAdmin(); + $test_response = $this->app->handle($this->createRequest('events_booking_add')); + $this->assertSame(200, $test_response->getStatusCode()); + $this->assertMatchesRegularExpression( + '/id="booking_date"[^>]* value="' . date('Y-m-d') . '"/', + (string)$test_response->getBody() + ); + } + /** * Unknown bookings are reported, as well as events removed since they have been filtered */ diff --git a/tests/GaletteEvents/Repository/tests/units/Bookings.php b/tests/GaletteEvents/Repository/tests/units/Bookings.php index 9925e30..94fa298 100644 --- a/tests/GaletteEvents/Repository/tests/units/Bookings.php +++ b/tests/GaletteEvents/Repository/tests/units/Bookings.php @@ -103,7 +103,7 @@ public function testListKeepsBookingValues(): void $list = (new \GaletteEvents\Repository\Bookings($this->zdb, $this->login, $this->history))->getList(); $this->assertCount(1, $list); $this->assertSame('Booking comment', $list[0]->getComment()); - $this->assertSame('2026-02-01', $list[0]->getCreationDate(false)); + $this->assertSame('2026-02-01', $list[0]->getCreationDate()); } /** diff --git a/tests/GaletteEvents/tests/units/Activity.php b/tests/GaletteEvents/tests/units/Activity.php index f64e9b9..d9f4ea5 100644 --- a/tests/GaletteEvents/tests/units/Activity.php +++ b/tests/GaletteEvents/tests/units/Activity.php @@ -38,7 +38,7 @@ public function tearDown(): void */ public function testEmpty(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); + $activity = new \GaletteEvents\Activity($this->zdb, $this->history); $this->assertNull($activity->getId()); $this->assertSame('', $activity->getName()); @@ -53,7 +53,7 @@ public function testEmpty(): void */ public function testCrud(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); + $activity = new \GaletteEvents\Activity($this->zdb, $this->history); $activities = new \GaletteEvents\Repository\Activities($this->zdb, $this->login, $this->history, $this->preferences); //ensure the table is empty @@ -80,7 +80,7 @@ public function testCrud(): void $first_id = $activity->getId(); $this->assertGreaterThan(0, $first_id); //creation date column holds no time - $this->assertSame(date('Y-m-d'), $activity->getCreationDate(false)); + $this->assertSame(date('Y-m-d'), $activity->getCreationDate()); $activity->load($first_id); $this->assertSame('Test activity', $activity->getName()); @@ -88,7 +88,7 @@ public function testCrud(): void $this->assertFalse($activity->isActive()); $this->assertSame(0, $activity->countEvents()); $this->assertSame(date('Y-m-d'), $activity->getCreationDate()); - $this->assertSame(date('Y-m-d'), $activity->getCreationDate(false)); + $this->assertSame(date('Y-m-d'), $activity->getCreationDate()); $activities_list = $activities->getList(); $this->assertCount(1, $activities_list); @@ -113,7 +113,7 @@ public function testCrud(): void */ public function testLoadError(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); + $activity = new \GaletteEvents\Activity($this->zdb, $this->history); $this->expectException(\GaletteEvents\NotFoundException::class); $activity->load(999); } @@ -123,7 +123,7 @@ public function testLoadError(): void */ public function testNoComment(): void { - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history); + $activity = new \GaletteEvents\Activity($this->zdb, $this->history); $this->assertTrue($activity->check(['name' => 'Dinner', 'active' => '1'])); $activity->store(); @@ -131,7 +131,7 @@ public function testNoComment(): void $update->set(['comment' => null])->where([\GaletteEvents\Activity::PK => $activity->getId()]); $this->zdb->execute($update); - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history, (int)$activity->getId()); + $activity = new \GaletteEvents\Activity($this->zdb, $this->history, (int)$activity->getId()); $this->assertSame('Dinner', $activity->getName()); $this->assertSame('', $activity->getComment()); } @@ -145,11 +145,11 @@ public function testCountAndRemove(): void $this->linkActivity($this->insertEvent('First event'), $id); $this->linkActivity($this->insertEvent('Second event'), $id); - $activity = new \GaletteEvents\Activity($this->zdb, $this->login, $this->history, $id); + $activity = new \GaletteEvents\Activity($this->zdb, $this->history, $id); $this->assertSame(2, $activity->countEvents()); $activity->remove(); $this->expectException(\GaletteEvents\NotFoundException::class); - (new \GaletteEvents\Activity($this->zdb, $this->login, $this->history))->load($id); + (new \GaletteEvents\Activity($this->zdb, $this->history))->load($id); $select = $this->zdb->select(EVENTS_PREFIX . 'activitiesevents'); $select->where([\GaletteEvents\Activity::PK => $id]); diff --git a/tests/GaletteEvents/tests/units/Booking.php b/tests/GaletteEvents/tests/units/Booking.php index 06e11b6..d0f5d2a 100644 --- a/tests/GaletteEvents/tests/units/Booking.php +++ b/tests/GaletteEvents/tests/units/Booking.php @@ -45,7 +45,7 @@ public function testEmpty(): void $this->assertNull($booking->getEvent()); $this->assertNull($booking->getMemberId()); $this->assertNull($booking->getMember()->id); - $this->assertSame('', $booking->getDate()); + $this->assertSame(date('Y-m-d'), $booking->getDate()); $this->assertSame('', $booking->getCreationDate()); $this->assertSame([], $booking->getActivities()); $this->assertSame([], $booking->getErrors()); @@ -67,7 +67,7 @@ public function testLoadNullValues(): void $this->assertSame('', $booking->getComment()); $this->assertNull($booking->getAmount()); $this->assertSame(1, $booking->getNumberPeople()); - $this->assertSame('2026-09-01', $booking->getCreationDate(false)); + $this->assertSame('2026-09-01', $booking->getCreationDate()); } /** diff --git a/tests/GaletteEvents/tests/units/Event.php b/tests/GaletteEvents/tests/units/Event.php index 93491aa..cd2e92c 100644 --- a/tests/GaletteEvents/tests/units/Event.php +++ b/tests/GaletteEvents/tests/units/Event.php @@ -67,8 +67,8 @@ public function testEmpty(): void $this->assertNull($event->getId()); $this->assertSame('', $event->getName()); $this->assertSame('', $event->getCreationDate()); - $this->assertSame(date('Y-m-d'), $event->getBeginDate(false)); - $this->assertSame(date('Y-m-d'), $event->getEndDate(false)); + $this->assertSame(date('Y-m-d'), $event->getBeginDate()); + $this->assertSame(date('Y-m-d'), $event->getEndDate()); $this->assertNull($event->getGroup()); $this->assertSame([], $event->getActivities()); $this->assertSame([], $event->getErrors()); @@ -251,7 +251,7 @@ public function testCheck(): void $values = $this->getFormValues(['begin_date' => '2026-10-10']); unset($values['end_date']); $this->assertTrue($event->check($values)); - $this->assertSame('2026-10-10', $event->getEndDate(false)); + $this->assertSame('2026-10-10', $event->getEndDate()); $this->assertTrue($event->isOpenFlag()); $this->assertTrue($event->check($this->getFormValues(['open' => null]))); From 4bb35d8921035d85de5ffe28b1bc735de7ae61e9 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski <johan@x-tnd.be> Date: Sun, 27 Sep 2026 12:25:59 +0200 Subject: [PATCH 6/9] Share loading, storage and date parsing of entities --- lib/GaletteEvents/Activity.php | 138 +++++++++++------------------ lib/GaletteEvents/Booking.php | 119 ++++--------------------- lib/GaletteEvents/EntityTrait.php | 139 ++++++++++++++++++++++++++++++ lib/GaletteEvents/Event.php | 131 +++++----------------------- 4 files changed, 226 insertions(+), 301 deletions(-) create mode 100644 lib/GaletteEvents/EntityTrait.php diff --git a/lib/GaletteEvents/Activity.php b/lib/GaletteEvents/Activity.php index 5275f36..0d6369c 100644 --- a/lib/GaletteEvents/Activity.php +++ b/lib/GaletteEvents/Activity.php @@ -23,6 +23,8 @@ */ class Activity { + use EntityTrait; + public const string TABLE = 'activities'; public const string PK = 'id_activity'; @@ -62,25 +64,6 @@ public function __construct(Db $zdb, History $history, int|ArrayObject|null $arg } } - /** - * Load an activity from its id - * - * @param int $id Activity identifier - * - * @throws NotFoundException - */ - public function load(int $id): void - { - $select = $this->zdb->select($this->getTableName()); - $select->where([self::PK => $id]); - $results = $this->zdb->execute($select); - - if ($results->count() === 0) { - throw new NotFoundException('No activity #' . $id); - } - $this->loadFromRS($results->current()); - } - /** * Populate object from a resultset row * @@ -95,16 +78,6 @@ private function loadFromRS(ArrayObject $r): void $this->comment = $r['comment'] ?? ''; } - /** - * Remove activity, with its links to events and bookings - */ - public function remove(): void - { - $delete = $this->zdb->delete($this->getTableName()); - $delete->where([self::PK => $this->id]); - $this->zdb->execute($delete); - } - /** * Check posted values validity * @@ -152,65 +125,58 @@ public function check(array $values): bool */ public function store(): void { - $values = [ - 'name' => $this->name, - 'is_active' => ($this->active ? $this->active - : ($this->zdb->isPostgres() ? 'false' : 0)), - 'comment' => $this->comment - ]; - - if ($this->id === null) { - //we're inserting a new activity - $this->creation_date = date("Y-m-d"); - $values['creation_date'] = $this->creation_date; - - $insert = $this->zdb->insert($this->getTableName()); - $insert->values($values); - $add = $this->zdb->execute($insert); - if ($add->count() === 0) { - $this->history->add(_T("Fail to add new activity.", "events")); - throw new \RuntimeException( - 'An error occurred inserting new activity!' - ); - } - - if ($this->zdb->isPostgres()) { - /** @phpstan-ignore-next-line */ - $this->id = (int)$this->zdb->driver->getLastGeneratedValue( - PREFIX_DB . $this->getTableName() . '_id_seq' - ); - } else { - $this->id = (int)$this->zdb->driver->getLastGeneratedValue(); - } - - // logging - $this->history->add( - _T("Activity added", "events"), - $this->name - ); - } else { - //we're editing an existing activity - $values[self::PK] = $this->id; - $update = $this->zdb->update($this->getTableName()); - $update - ->set($values) - ->where([self::PK => $this->id]); - - $edit = $this->zdb->execute($update); - - //edit == 0 does not mean there were an error, but that there - //were nothing to change - if ($edit->count() > 0) { + $this->transactional(function (): void { + $values = [ + 'name' => $this->name, + 'is_active' => ($this->active ? $this->active + : ($this->zdb->isPostgres() ? 'false' : 0)), + 'comment' => $this->comment + ]; + + if ($this->id === null) { + //we're inserting a new activity + $this->creation_date = date("Y-m-d"); + $values['creation_date'] = $this->creation_date; + + $insert = $this->zdb->insert($this->getTableName()); + $insert->values($values); + $add = $this->zdb->execute($insert); + if ($add->count() === 0) { + $this->history->add(_T("Fail to add new activity.", "events")); + throw new \RuntimeException( + 'An error occurred inserting new activity!' + ); + } + $this->id = $this->getLastInsertId(); + + // logging $this->history->add( - _T("Activity updated", "events"), + _T("Activity added", "events"), $this->name ); + } else { + //we're editing an existing activity + $update = $this->zdb->update($this->getTableName()); + $update + ->set($values) + ->where([self::PK => $this->id]); + + $edit = $this->zdb->execute($update); + + //edit == 0 does not mean there were an error, but that there + //were nothing to change + if ($edit->count() > 0) { + $this->history->add( + _T("Activity updated", "events"), + $this->name + ); + } } - } + }); } /** - * Get event id + * Get activity id */ public function getId(): ?int { @@ -218,7 +184,7 @@ public function getId(): ?int } /** - * Get event name + * Get activity name */ public function getName(): string { @@ -241,14 +207,6 @@ public function isActive(): bool return $this->active; } - /** - * Get table's name - */ - protected function getTableName(): string - { - return EVENTS_PREFIX . self::TABLE; - } - /** * Get comment */ diff --git a/lib/GaletteEvents/Booking.php b/lib/GaletteEvents/Booking.php index 3848325..3c8cc6d 100644 --- a/lib/GaletteEvents/Booking.php +++ b/lib/GaletteEvents/Booking.php @@ -26,6 +26,8 @@ */ class Booking { + use EntityTrait; + public const string TABLE = 'bookings'; public const string PK = 'id_booking'; @@ -72,32 +74,11 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb $this->load($args); } elseif ($args !== null) { $this->loadFromRS($args); - $this->loadActivities(); } else { $this->date = date('Y-m-d'); } } - /** - * Load a booking from its id - * - * @param int $id Booking identifier - * - * @throws NotFoundException - */ - public function load(int $id): void - { - $select = $this->zdb->select($this->getTableName()); - $select->where([self::PK => $id]); - $results = $this->zdb->execute($select); - - if ($results->count() === 0) { - throw new NotFoundException('No booking #' . $id); - } - $this->loadFromRS($results->current()); - $this->loadActivities(); - } - /** * Populate object from a resultset row * @@ -117,16 +98,7 @@ private function loadFromRS(ArrayObject $r): void $this->number_people = (int)($r['number_people'] ?? 1); $this->comment = $r['comment'] ?? ''; $this->creation_date = $r['creation_date']; - } - - /** - * Remove booking, with its activities - */ - public function remove(): void - { - $delete = $this->zdb->delete($this->getTableName()); - $delete->where([self::PK => $this->id]); - $this->zdb->execute($delete); + $this->loadActivities(); } /** @@ -235,30 +207,9 @@ public function check(array $values): bool if (!isset($values['booking_date']) || empty($values['booking_date'])) { $this->errors[] = _T('Booking date is mandatory!', 'events'); } else { - $value = $values['booking_date']; - try { - $d = \DateTime::createFromFormat(__("Y-m-d"), $value); - if ($d === false) { - //try with non localized date - $d = \DateTime::createFromFormat("Y-m-d", $value); - if ($d === false) { - throw new \Exception('Incorrect format'); - } - } - $this->date = $d->format('Y-m-d'); - } catch (\Exception $e) { - Analog::log( - 'Wrong date format. field: booking_date' - . ', value: ' . $value . ', expected fmt: ' - . __("Y-m-d") . ' | ' . $e->getMessage(), - Analog::INFO - ); - $this->errors[] = sprintf( - //TRANS %1$s is the expected date format, %2$s is the field label - _T('- Wrong date format (%1$s) for %2$s!'), - __("Y-m-d"), - __('booking date', 'events') - ); + $date = $this->parseDate((string)$values['booking_date'], __('booking date', 'events')); + if ($date !== null) { + $this->date = $date; } } @@ -345,13 +296,7 @@ private function checkActivities(Event $event, array $checked): void */ public function store(): void { - $new = $this->id === null; - $transaction = !$this->zdb->connection->inTransaction(); - if ($transaction) { - $this->zdb->connection->beginTransaction(); - } - - try { + $this->transactional(function (): void { $values = [ Event::PK => $this->event, Adherent::PK => $this->member, @@ -366,7 +311,7 @@ public function store(): void 'comment' => $this->comment ]; - if ($new) { + if ($this->id === null) { //we're inserting a new booking $this->creation_date = date("Y-m-d"); $values['creation_date'] = $this->creation_date; @@ -374,30 +319,21 @@ public function store(): void $insert = $this->zdb->insert($this->getTableName()); $insert->values($values); $add = $this->zdb->execute($insert); - if ($add->count() > 0) { - if ($this->zdb->isPostgres()) { - /** @phpstan-ignore-next-line */ - $this->id = (int)$this->zdb->driver->getLastGeneratedValue( - PREFIX_DB . EVENTS_PREFIX . Booking::TABLE . '_id_seq' - ); - } else { - $this->id = (int)$this->zdb->driver->getLastGeneratedValue(); - } - - // logging - $this->history->add( - _T("Booking added", "events"), - $this->getEvent()->getName() - ); - } else { + if ($add->count() === 0) { $this->history->add(_T("Fail to add new booking.", "events")); throw new \RuntimeException( 'An error occurred inserting new booking!' ); } + $this->id = $this->getLastInsertId(); + + // logging + $this->history->add( + _T("Booking added", "events"), + $this->getEvent()->getName() + ); } else { //we're editing an existing booking - $values[self::PK] = $this->id; $update = $this->zdb->update($this->getTableName()); $update ->set($values) @@ -524,20 +460,7 @@ public function store(): void Analog::INFO ); } - - if ($transaction) { - $this->zdb->connection->commit(); - } - } catch (\Throwable $e) { - if ($transaction) { - $this->zdb->connection->rollBack(); - } - if ($new) { - //nothing has been stored - $this->id = null; - } - throw $e; - } + }); } /** @@ -684,14 +607,6 @@ public function setMember(int $member): self return $this; } - /** - * Get table's name - */ - protected function getTableName(): string - { - return EVENTS_PREFIX . self::TABLE; - } - /** * Get comment */ diff --git a/lib/GaletteEvents/EntityTrait.php b/lib/GaletteEvents/EntityTrait.php new file mode 100644 index 0000000..101334f --- /dev/null +++ b/lib/GaletteEvents/EntityTrait.php @@ -0,0 +1,139 @@ +<?php + +/** + * This file is part of Galette Events plugin (https://galette.eu). + * SPDX-FileCopyrightText: Copyright © 2018-2026 The Galette Team + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +declare(strict_types=1); + +namespace GaletteEvents; + +use Analog\Analog; +use ArrayObject; + +/** + * Loading, storage and removal shared by events, bookings and activities + * + * @author Johan Cwiklinski <johan@x-tnd.be> + */ +trait EntityTrait +{ + /** + * Populate object from a resultset row + * + * @param ArrayObject<string, mixed> $r the resultset row + */ + abstract private function loadFromRS(ArrayObject $r): void; + + /** + * Load entity from its id + * + * @param int $id Identifier + * + * @throws NotFoundException + */ + public function load(int $id): void + { + $select = $this->zdb->select($this->getTableName()); + $select->where([self::PK => $id]); + $results = $this->zdb->execute($select); + + if ($results->count() === 0) { + throw new NotFoundException(sprintf('%1$s #%2$s does not exist', self::class, $id)); + } + $this->loadFromRS($results->current()); + } + + /** + * Remove entity; database removes its links + */ + public function remove(): void + { + $delete = $this->zdb->delete($this->getTableName()); + $delete->where([self::PK => $this->id]); + $this->zdb->execute($delete); + } + + /** + * Run storage in a transaction, unless one is already running + * + * @param callable $store Storage + */ + private function transactional(callable $store): void + { + $new = $this->id === null; + $transaction = !$this->zdb->connection->inTransaction(); + if ($transaction) { + $this->zdb->connection->beginTransaction(); + } + + try { + $store(); + if ($transaction) { + $this->zdb->connection->commit(); + } + } catch (\Throwable $e) { + if ($transaction) { + $this->zdb->connection->rollBack(); + } + if ($new) { + //nothing has been stored + $this->id = null; + } + throw $e; + } + } + + /** + * Get identifier of the row that has just been inserted + */ + private function getLastInsertId(): int + { + if ($this->zdb->isPostgres()) { + /** @phpstan-ignore-next-line */ + return (int)$this->zdb->driver->getLastGeneratedValue( + PREFIX_DB . $this->getTableName() . '_id_seq' + ); + } + return (int)$this->zdb->driver->getLastGeneratedValue(); + } + + /** + * Parse a date typed in the localized format, or as Y-m-d + * + * @param string $value Typed date + * @param string $label Field label, for the error message + * + * @return ?string Date as Y-m-d, null when it cannot be parsed + */ + private function parseDate(string $value, string $label): ?string + { + $date = \DateTime::createFromFormat(__('Y-m-d'), $value) + ?: \DateTime::createFromFormat('Y-m-d', $value); + if ($date === false) { + Analog::log( + 'Wrong date format. field: ' . $label . ', value: ' . $value + . ', expected fmt: ' . __('Y-m-d'), + Analog::INFO + ); + $this->errors[] = sprintf( + //TRANS: %1$s is the expected date format, %2$s is the field label + _T('- Wrong date format (%1$s) for %2$s!'), + __('Y-m-d'), + $label + ); + return null; + } + return $date->format('Y-m-d'); + } + + /** + * Get table's name + */ + protected function getTableName(): string + { + return EVENTS_PREFIX . self::TABLE; + } +} diff --git a/lib/GaletteEvents/Event.php b/lib/GaletteEvents/Event.php index 0895999..70b0f5b 100644 --- a/lib/GaletteEvents/Event.php +++ b/lib/GaletteEvents/Event.php @@ -26,6 +26,8 @@ */ class Event { + use EntityTrait; + public const string TABLE = 'events'; public const string PK = 'id_event'; @@ -71,7 +73,6 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb $this->load($args); } elseif ($args !== null) { $this->loadFromRS($args); - $this->loadActivities(); } else { $now = date('Y-m-d'); $this->begin_date = $now; @@ -79,26 +80,6 @@ public function __construct(Db $zdb, Login $login, History $history, int|ArrayOb } } - /** - * Load an event from its id - * - * @param int $id Event identifier - * - * @throws NotFoundException - */ - public function load(int $id): void - { - $select = $this->zdb->select($this->getTableName()); - $select->where([self::PK => $id]); - $results = $this->zdb->execute($select); - - if ($results->count() === 0) { - throw new NotFoundException('No event #' . $id); - } - $this->loadFromRS($results->current()); - $this->loadActivities(); - } - /** * Populate object from a resultset row * @@ -119,16 +100,7 @@ private function loadFromRS(ArrayObject $r): void $this->group = $r['id_group'] === null ? null : (int)$r['id_group']; $this->comment = $r['comment'] ?? ''; $this->color = $r['color']; - } - - /** - * Remove event, with its bookings and activities links - */ - public function remove(): void - { - $delete = $this->zdb->delete($this->getTableName()); - $delete->where([self::PK => $this->id]); - $this->zdb->execute($delete); + $this->loadActivities(); } /** @@ -144,38 +116,15 @@ public function check(array $values): bool if (empty($values['begin_date'])) { $this->errors[] = _T('Begin date is mandatory', 'events'); } else { - //handle dates - foreach (['begin_date', 'end_date'] as $datefield) { + $labels = [ + 'begin_date' => _T('Begin date', 'events'), + 'end_date' => _T('End date', 'events'), + ]; + foreach ($labels as $datefield => $label) { if (isset($values[$datefield])) { - $value = $values[$datefield]; - try { - $d = \DateTime::createFromFormat(__("Y-m-d"), $value); - if ($d === false) { - //try with non localized date - $d = \DateTime::createFromFormat("Y-m-d", $value); - if ($d === false) { - throw new \Exception('Incorrect format'); - } - } - $this->$datefield = $d->format('Y-m-d'); - } catch (\Exception $e) { - Analog::log( - 'Wrong date format. field: ' . $datefield - . ', value: ' . $value . ', expected fmt: ' - . __("Y-m-d") . ' | ' . $e->getMessage(), - Analog::INFO - ); - if ($datefield == 'begin_date') { - $label = _T('Begin date', 'events'); - } else { - $label = _T('End date', 'events'); - } - $this->errors[] = sprintf( - //TRANS %1$s is the expected date format, %2$s is the field label - _T('- Wrong date format (%1$s) for %2$s!'), - __("Y-m-d"), - $label - ); + $date = $this->parseDate((string)$values[$datefield], $label); + if ($date !== null) { + $this->$datefield = $date; } } } @@ -301,13 +250,7 @@ public function check(array $values): bool */ public function store(): void { - $new = $this->id === null; - $transaction = !$this->zdb->connection->inTransaction(); - if ($transaction) { - $this->zdb->connection->beginTransaction(); - } - - try { + $this->transactional(function (): void { $values = [ 'name' => $this->name, 'address' => $this->address, @@ -323,7 +266,7 @@ public function store(): void 'color' => $this->color ]; - if ($new) { + if ($this->id === null) { //we're inserting a new event $this->creation_date = date("Y-m-d"); $values['creation_date'] = $this->creation_date; @@ -331,29 +274,20 @@ public function store(): void $insert = $this->zdb->insert($this->getTableName()); $insert->values($values); $add = $this->zdb->execute($insert); - if ($add->count() > 0) { - if ($this->zdb->isPostgres()) { - /** @phpstan-ignore-next-line */ - $this->id = (int)$this->zdb->driver->getLastGeneratedValue( - PREFIX_DB . EVENTS_PREFIX . Event::TABLE . '_id_seq' - ); - } else { - $this->id = (int)$this->zdb->driver->getLastGeneratedValue(); - } - - // logging - $this->history->add( - _T("Event added", "events"), - $this->name - ); - } else { + if ($add->count() === 0) { $this->history->add(_T("Fail to add new event.", "events")); throw new \RuntimeException( 'An error occurred inserting new event!' ); } + $this->id = $this->getLastInsertId(); + + // logging + $this->history->add( + _T("Event added", "events"), + $this->name + ); } else { - $values['id_event'] = $this->id; //we're editing an existing event $update = $this->zdb->update($this->getTableName()); $update @@ -373,20 +307,7 @@ public function store(): void } $this->storeActivities(); - - if ($transaction) { - $this->zdb->connection->commit(); - } - } catch (\Throwable $e) { - if ($transaction) { - $this->zdb->connection->rollBack(); - } - if ($new) { - //nothing has been stored - $this->id = null; - } - throw $e; - } + }); } /** @@ -605,14 +526,6 @@ public function isOpen(): bool return false; } - /** - * Get table's name - */ - protected function getTableName(): string - { - return EVENTS_PREFIX . self::TABLE; - } - /** * Get activities list * From bbe9e5c78a55b509c7e0e0b313162ba4ba255d25 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski <johan@x-tnd.be> Date: Sun, 27 Sep 2026 12:30:04 +0200 Subject: [PATCH 7/9] Keep posted values in session instead of serialized entities --- .../Controllers/Crud/ActivitiesController.php | 23 +++++++++------ .../Controllers/Crud/BookingsController.php | 24 +++++++++------- .../Controllers/Crud/EventsController.php | 23 +++++++++------ .../Crud/tests/units/BookingsController.php | 28 +++++++++++++++++++ .../Crud/tests/units/EventsController.php | 28 ++++++++++++++++++- 5 files changed, 97 insertions(+), 29 deletions(-) diff --git a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php index 29fcd8f..fbbeec7 100644 --- a/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php +++ b/lib/GaletteEvents/Controllers/Crud/ActivitiesController.php @@ -143,14 +143,9 @@ public function filter(Request $request, Response $response): Response */ public function edit(Request $request, Response $response, ?int $id = null, string $action = 'edit'): Response { - if ($this->session->plugin_events_activity !== null) { - $activity = $this->session->plugin_events_activity; - $this->session->plugin_events_activity = null; - } else { - $activity = new Activity($this->zdb, $this->history); - } + $activity = new Activity($this->zdb, $this->history); - if ($id !== null && $activity->getId() != $id) { + if ($id !== null) { try { $activity->load($id); } catch (NotFoundException) { @@ -158,6 +153,13 @@ public function edit(Request $request, Response $response, ?int $id = null, stri } } + //values posted before an error + $data = $this->session->plugin_events_activity_data ?? null; + unset($this->session->plugin_events_activity_data); + if (is_array($data) && $data['id'] === $activity->getId()) { + $activity->check($data['values']); + } + // template variable declaration $title = _T("Activity", "events"); if ($activity->getId() != '') { @@ -247,8 +249,11 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st if (count($error_detected) == 0) { $redirect_url = $this->routeparser->urlFor('events_activities'); } else { - //store entity in session - $this->session->plugin_events_activity = $activity; + //keep posted values for the form + $this->session->plugin_events_activity_data = [ + 'id' => $activity->getId(), + 'values' => $post + ]; if ($activity->getId()) { $redirect_url = $this->routeparser->urlFor( diff --git a/lib/GaletteEvents/Controllers/Crud/BookingsController.php b/lib/GaletteEvents/Controllers/Crud/BookingsController.php index 6ce2ad7..9ac9abf 100644 --- a/lib/GaletteEvents/Controllers/Crud/BookingsController.php +++ b/lib/GaletteEvents/Controllers/Crud/BookingsController.php @@ -378,14 +378,9 @@ public function edit(Request $request, Response $response, ?int $id = null, stri $get = $request->getQueryParams(); $route_params = []; - if ($this->session->plugin_events_booking !== null) { - $booking = $this->session->plugin_events_booking; - $this->session->plugin_events_booking = null; - } else { - $booking = new Booking($this->zdb, $this->login, $this->history); - } + $booking = new Booking($this->zdb, $this->login, $this->history); - if ($id !== null && $booking->getId() != $id) { + if ($id !== null) { try { $booking->load($id); } catch (NotFoundException) { @@ -397,6 +392,13 @@ public function edit(Request $request, Response $response, ?int $id = null, stri return $this->redirectForbidden($response, $booking); } + //values posted before an error, or before the event has been changed + $data = $this->session->plugin_events_booking_data ?? null; + unset($this->session->plugin_events_booking_data); + if (is_array($data) && $data['id'] === $booking->getId()) { + $booking->check($data['values']); + } + // template variable declaration $title = _T("Booking", "events"); if ($booking->getId() != '') { @@ -539,7 +541,6 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st } if (!isset($post['save'])) { - $this->session->plugin_events_booking = $booking; $error_detected = []; $goto_list = false; $warning_detected[] = _T('Do not forget to store the booking', 'events'); @@ -577,8 +578,11 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st ['event' => (string)$booking->getEventId()] ); } else { - //store entity in session - $this->session->plugin_events_booking = $booking; + //keep posted values for the form + $this->session->plugin_events_booking_data = [ + 'id' => $booking->getId(), + 'values' => $post + ]; if ($booking->getId()) { $route = 'events_booking_edit'; diff --git a/lib/GaletteEvents/Controllers/Crud/EventsController.php b/lib/GaletteEvents/Controllers/Crud/EventsController.php index 55c0600..4a8eeca 100644 --- a/lib/GaletteEvents/Controllers/Crud/EventsController.php +++ b/lib/GaletteEvents/Controllers/Crud/EventsController.php @@ -205,15 +205,10 @@ public function filter(Request $request, Response $response): Response */ public function edit(Request $request, Response $response, ?int $id = null, string $action = 'edit'): Response { - if ($this->session->plugin_events_event !== null) { - $event = $this->session->plugin_events_event; - $this->session->plugin_events_event = null; - } else { - $event = new Event($this->zdb, $this->login, $this->history); - } + $event = new Event($this->zdb, $this->login, $this->history); $can = $event->canCreate($this->login); - if ($id !== null && $event->getId() != $id) { + if ($id !== null) { try { $event->load($id); } catch (NotFoundException) { @@ -227,6 +222,13 @@ public function edit(Request $request, Response $response, ?int $id = null, stri return $this->redirectForbidden($response, $event); } + //values posted before an error, or before activities have been changed + $data = $this->session->plugin_events_event_data ?? null; + unset($this->session->plugin_events_event_data); + if (is_array($data) && $data['id'] === $event->getId()) { + $event->check($data['values']); + } + // template variable declaration $title = _T("Event", "events"); if ($event->getId() != '') { @@ -354,8 +356,11 @@ public function doEdit(Request $request, Response $response, ?int $id = null, st if (count($error_detected) == 0 && $goto_list) { $redirect_url = $this->routeparser->urlFor('events_events'); } else { - //store entity in session - $this->session->plugin_events_event = $event; + //keep posted values for the form + $this->session->plugin_events_event_data = [ + 'id' => $event->getId(), + 'values' => $post + ]; if ($event->getId()) { $redirect_url = $this->routeparser->urlFor( diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php index 53016e4..8a3888a 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/BookingsController.php @@ -520,6 +520,34 @@ public function testList(): void $this->assertStringContainsString($member_two->sfullname, $body); } + /** + * Changing the event of a booking shows its activities, before the booking is stored + */ + public function testChangeEventShowsItsActivities(): void + { + $this->getMemberOne(); + $event = $this->insertEvent('Event'); + $dinner = $this->insertActivity('Dinner'); + $this->linkActivity($event, $dinner); + $this->logMember($this->dataAdherentOne()); + + $data = ['event' => (string)$event, 'booking_date' => date('Y-m-d'), 'number_people' => '1', 'comment' => '']; + $test_response = $this->app->handle( + $this->createRequest('events_storebooking_add', [], 'POST')->withParsedBody($data) + ); + $this->assertSame( + ['Location' => [$this->routeparser->urlFor('events_booking_add', ['action' => 'add'])]], + $test_response->getHeaders() + ); + $this->expectFlashData(['warning_detected' => ['Do not forget to store the booking']]); + $this->assertSame(0, $this->countBookings($event)); + + $test_response = $this->app->handle($this->createRequest('events_booking_add')); + $this->assertSame(200, $test_response->getStatusCode()); + $this->assertStringContainsString('id="activity_' . $dinner . '"', (string)$test_response->getBody()); + $this->expectNoLogEntry(); + } + /** * New bookings are dated from today */ diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php index 25086de..b82b006 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php @@ -177,7 +177,14 @@ public function testAttachActivityOnIncompleteEvent(): void 'success_detected' => ['Activity has been attached to event.'], ]); $this->expectLogEntry(Analog::ERROR, 'Some errors has been threw attempting to edit/store an event'); - $this->assertSame([$dinner], array_keys($this->session->plugin_events_event->getActivities())); + //form shows posted values, with the attached activity + $test_response = $this->app->handle($this->createRequest('events_event_add')); + $this->assertSame(200, $test_response->getStatusCode()); + $body = (string)$test_response->getBody(); + $this->assertStringContainsString('name="activities_ids[]" value="' . $dinner . '"', $body); + $this->assertStringContainsString('value="Lille"', $body); + $this->expectLogEntry(Analog::ERROR, 'Some errors has been threw attempting to edit/store an event'); + $this->assertNull($this->session->plugin_events_event_data ?? null); //an unknown activity is not attached $this->postEvent(null, $this->getFormValues([ @@ -187,6 +194,25 @@ public function testAttachActivityOnIncompleteEvent(): void $this->expectFlashData(['error_detected' => ['Please choose an activity to add']]); } + /** + * Values posted on an event are not shown on another one + */ + public function testPostedValuesStayOnTheirEvent(): void + { + $this->logSuperAdmin(); + $first = $this->insertEvent('First event'); + $second = $this->insertEvent('Second event'); + + $this->postEvent($first, $this->getFormValues(['name' => 'Renamed', 'town' => '', 'save' => '1'])); + $this->expectFlashData(['error_detected' => ['Town is mandatory']]); + $this->expectLogEntry(Analog::ERROR, 'Some errors has been threw attempting to edit/store an event'); + + $body = (string)$this->app->handle($this->createRequest('events_event_edit', ['id' => (string)$second]))->getBody(); + $this->assertStringContainsString('value="Second event"', $body); + $this->assertStringNotContainsString('Renamed', $body); + $this->expectNoLogEntry(); + } + /** * Group managers cannot store events of groups they do not manage */ From a1a27b01185214d25e8fa0e1f51611fcb3974db6 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski <johan@x-tnd.be> Date: Sun, 27 Sep 2026 12:34:55 +0200 Subject: [PATCH 8/9] Load events, members and activities once --- lib/GaletteEvents/Booking.php | 44 ++++++++++---- lib/GaletteEvents/Event.php | 38 +++--------- lib/GaletteEvents/Repository/Bookings.php | 25 ++++++++ lib/GaletteEvents/Repository/Events.php | 58 +++++++++++++++---- .../Crud/tests/units/EventsController.php | 28 +++++++++ .../Repository/tests/units/Bookings.php | 25 ++++++++ tests/GaletteEvents/tests/units/Event.php | 6 -- 7 files changed, 165 insertions(+), 59 deletions(-) diff --git a/lib/GaletteEvents/Booking.php b/lib/GaletteEvents/Booking.php index 3c8cc6d..0f26242 100644 --- a/lib/GaletteEvents/Booking.php +++ b/lib/GaletteEvents/Booking.php @@ -40,6 +40,8 @@ class Booking private ?int $id = null; private ?int $event = null; private ?int $member = null; + private ?Event $event_entity = null; + private ?Adherent $member_entity = null; private string $date = ''; private bool $paid = false; private ?float $amount = null; @@ -125,7 +127,7 @@ public function check(array $values): bool $this->errors[] = _T('This event cannot be booked.', 'events'); } if ($event !== null) { - $this->event = (int)$values['event']; + $this->useEvent($event); $this->checkActivities($event, $values['activities'] ?? []); } } @@ -484,10 +486,13 @@ public function getEventId(): ?int */ public function getEvent(): ?Event { - if ($this->event !== null) { - return new Event($this->zdb, $this->login, $this->history, $this->event); + if ($this->event === null) { + return null; } - return null; + if ($this->event_entity?->getId() !== $this->event) { + $this->event_entity = new Event($this->zdb, $this->login, $this->history, $this->event); + } + return $this->event_entity; } /** @@ -503,7 +508,10 @@ public function getMemberId(): ?int */ public function getMember(): Adherent { - return new Adherent($this->zdb, $this->member); + if ($this->member_entity === null || $this->member_entity->id !== $this->member) { + $this->member_entity = new Adherent($this->zdb, $this->member); + } + return $this->member_entity; } /** @@ -596,6 +604,18 @@ public function setEvent(int $event): self return $this; } + /** + * Set event from an already loaded one + * + * @param Event $event Event + */ + public function useEvent(Event $event): self + { + $this->event = $event->getId(); + $this->event_entity = $event; + return $this; + } + /** * Set member * @@ -632,15 +652,17 @@ public function loadActivities(): void { $this->activities = []; $select = $this->zdb->select(EVENTS_PREFIX . 'activitiesbookings', 'acb'); - $select->where([self::PK => $this->id]); + //activities are loaded along with their links + $select->join( + ['ac' => PREFIX_DB . EVENTS_PREFIX . Activity::TABLE], + 'acb.' . Activity::PK . ' = ac.' . Activity::PK, + ['name', 'is_active', 'creation_date', 'comment'] + ); + $select->where(['acb.' . self::PK => $this->id]); $results = $this->zdb->execute($select); foreach ($results as $result) { $this->activities[$result[Activity::PK]] = [ - 'activity' => new Activity( - $this->zdb, - $this->history, - (int)$result[Activity::PK] - ), + 'activity' => new Activity($this->zdb, $this->history, $result), 'checked' => $result['checked'] ]; } diff --git a/lib/GaletteEvents/Event.php b/lib/GaletteEvents/Event.php index 70b0f5b..5c5ca75 100644 --- a/lib/GaletteEvents/Event.php +++ b/lib/GaletteEvents/Event.php @@ -16,7 +16,6 @@ use Galette\Core\Login; use Galette\Entity\Group; use Analog\Analog; -use Laminas\Db\ResultSet\ResultSet; use Laminas\Db\Sql\Expression; /** @@ -554,15 +553,17 @@ public function loadActivities(): void { $this->activities = []; $select = $this->zdb->select(EVENTS_PREFIX . 'activitiesevents', 'ace'); - $select->where([self::PK => $this->id]); + //activities are loaded along with their links + $select->join( + ['ac' => PREFIX_DB . EVENTS_PREFIX . Activity::TABLE], + 'ace.' . Activity::PK . ' = ac.' . Activity::PK, + ['name', 'is_active', 'creation_date', 'comment'] + ); + $select->where(['ace.' . self::PK => $this->id]); $results = $this->zdb->execute($select); foreach ($results as $result) { $this->activities[$result[Activity::PK]] = [ - 'activity' => new Activity( - $this->zdb, - $this->history, - (int)$result[Activity::PK] - ), + 'activity' => new Activity($this->zdb, $this->history, $result), 'status' => $result['status'] ]; } @@ -595,29 +596,6 @@ public function getColor(): string return $this->color ?? ''; } - /** - * Count attendees per event - */ - public function countAttendees(): ResultSet - { - $select = $this->zdb->select(EVENTS_PREFIX . Booking::TABLE, 'b'); - $select->columns( - [ - 'count' => new Expression('SUM(b.number_people)'), - 'is_paid' - ] - ); - $select->where([ - self::PK => $this->id, - ]); - - $select->group('is_paid'); - - $results = $this->zdb->execute($select); - - return $results; - } - /** * Can member edit event * diff --git a/lib/GaletteEvents/Repository/Bookings.php b/lib/GaletteEvents/Repository/Bookings.php index 9a6a718..eb2a6d4 100644 --- a/lib/GaletteEvents/Repository/Bookings.php +++ b/lib/GaletteEvents/Repository/Bookings.php @@ -94,6 +94,7 @@ public function getList(bool $full = false): array $booking = new Booking($this->zdb, $this->login, $this->history, $row); $bookings[] = $booking; } + $this->loadEvents($bookings); return $bookings; } catch (\Exception $e) { @@ -105,6 +106,30 @@ public function getList(bool $full = false): array } } + /** + * Load events of listed bookings, once each + * + * @param array<Booking> $bookings Bookings + */ + private function loadEvents(array $bookings): void + { + $ids = array_unique(array_map(fn(Booking $booking): int => (int)$booking->getEventId(), $bookings)); + if (count($ids) === 0) { + return; + } + + $select = $this->zdb->select(EVENTS_PREFIX . Event::TABLE); + $select->where([Event::PK => array_values($ids)]); + $events = []; + foreach ($this->zdb->execute($select) as $row) { + $events[(int)$row[Event::PK]] = new Event($this->zdb, $this->login, $this->history, $row); + } + + foreach ($bookings as $booking) { + $booking->useEvent($events[$booking->getEventId()]); + } + } + /** * Builds the SELECT statement * diff --git a/lib/GaletteEvents/Repository/Events.php b/lib/GaletteEvents/Repository/Events.php index 48eb091..4ec9c17 100644 --- a/lib/GaletteEvents/Repository/Events.php +++ b/lib/GaletteEvents/Repository/Events.php @@ -14,7 +14,6 @@ use ArrayObject; use Galette\Entity\Adherent; use GaletteEvents\Booking; -use Laminas\Db\ResultSet\ResultSet; use Laminas\Db\Sql\Expression; use Laminas\Db\Sql\Predicate; use Laminas\Db\Sql\Predicate\PredicateSet; @@ -150,8 +149,17 @@ public function getList(bool $bookable = false, bool $fullcalendar = false, bool $results = $this->zdb->execute($select); $this->filters->query = $this->zdb->query_string; - $events = []; + $rows = []; foreach ($results as $row) { + $rows[] = $row; + } + $attendees = []; + if ($this->filters->calendar_filter) { + $attendees = $this->countAttendees(array_map(fn(ArrayObject $row): int => (int)$row[Event::PK], $rows)); + } + + $events = []; + foreach ($rows as $row) { $event = new Event($this->zdb, $this->login, $this->history, $row); if (!$this->filters->calendar_filter) { $events[] = $event; @@ -182,16 +190,8 @@ public function getList(bool $bookable = false, bool $fullcalendar = false, bool $description .= sprintf($pattern, _T("Comment:", "events"), $this->escape($comment)); } - /** @var ResultSet $attendees */ - $attendees = $event->countAttendees(); - $total_attendees = 0; - $paid_attendees = 0; - foreach ($attendees as $attendee) { - $total_attendees += $attendee['count']; - if ($attendee['is_paid']) { - $paid_attendees += $attendee['count']; - } - } + $total_attendees = $attendees[$event->getId()]['total'] ?? 0; + $paid_attendees = $attendees[$event->getId()]['paid'] ?? 0; $attendees_str = $total_attendees; if ($total_attendees) { @@ -233,6 +233,40 @@ public function getList(bool $bookable = false, bool $fullcalendar = false, bool } } + /** + * Count attendees of events, and the paid ones + * + * @param array<int> $ids Events identifiers + * + * @return array<int, array{total: int, paid: int}> + */ + private function countAttendees(array $ids): array + { + if (count($ids) === 0) { + return []; + } + + $select = $this->zdb->select(EVENTS_PREFIX . Booking::TABLE, 'b'); + $select->columns([ + Event::PK, + 'is_paid', + 'count' => new Expression('SUM(b.number_people)') + ]); + $select->where([Event::PK => $ids]); + $select->group([Event::PK, 'is_paid']); + + $attendees = []; + foreach ($this->zdb->execute($select) as $row) { + $id = (int)$row[Event::PK]; + $attendees[$id] ??= ['total' => 0, 'paid' => 0]; + $attendees[$id]['total'] += (int)$row['count']; + if ($row['is_paid']) { + $attendees[$id]['paid'] += (int)$row['count']; + } + } + return $attendees; + } + /** * Format a date for the calendar * diff --git a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php index b82b006..03015cc 100644 --- a/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php +++ b/tests/GaletteEvents/Controllers/Crud/tests/units/EventsController.php @@ -334,6 +334,34 @@ public function testRemovalTitle(): void $this->assertStringContainsString("<title>Remove event 'Party' - ", $body); } + /** + * Calendar counts attendees of each event + */ + public function testCalendarAttendees(): void + { + $member_one = $this->getMemberOne(); + $member_two = $this->getMemberTwo(); + $paid = $this->zdb->isPostgres() ? 'true' : 1; + $first = $this->insertEvent('First event'); + $this->insertBooking($first, $member_one->id, ['number_people' => 3, 'is_paid' => $paid]); + $this->insertBooking($first, $member_two->id, ['number_people' => 2]); + $this->insertEvent('Second event'); + $this->logSuperAdmin(); + + $request = $this->createRequest( + 'ajax-events_calendar', + query_params: [ + 'start' => date('Y-m-d'), + 'end' => date('Y-m-d', strtotime('+1 month')), + ] + ); + $events = json_decode((string)$this->app->handle($request)->getBody(), true); + $this->assertIsArray($events); + $descriptions = array_column($events, 'description', 'name'); + $this->assertStringContainsString('<strong>Attendees:</strong> 5 (3 paid)', $descriptions['First event']); + $this->assertStringContainsString('<strong>Attendees:</strong> 0</li>', $descriptions['Second event']); + } + /** * Unknown events are reported, instead of showing an empty form */ diff --git a/tests/GaletteEvents/Repository/tests/units/Bookings.php b/tests/GaletteEvents/Repository/tests/units/Bookings.php index 94fa298..c76086c 100644 --- a/tests/GaletteEvents/Repository/tests/units/Bookings.php +++ b/tests/GaletteEvents/Repository/tests/units/Bookings.php @@ -104,6 +104,31 @@ public function testListKeepsBookingValues(): void $this->assertCount(1, $list); $this->assertSame('Booking comment', $list[0]->getComment()); $this->assertSame('2026-02-01', $list[0]->getCreationDate()); + $this->assertSame('Event comment', $list[0]->getEvent()?->getComment()); + } + + /** + * Bookings of an event share its instance, loaded once + */ + public function testListLoadsEventsOnce(): void + { + $member_one = $this->getMemberOne(); + $member_two = $this->getMemberTwo(); + $event = $this->insertEvent('Event'); + $this->insertBooking($event, $member_one->id); + $this->insertBooking($event, $member_two->id); + $this->insertBooking($this->insertEvent('Other event'), $member_one->id); + + $this->logSuperAdmin(); + $list = (new \GaletteEvents\Repository\Bookings($this->zdb, $this->login, $this->history))->getList(); + $this->assertCount(3, $list); + $events = []; + foreach ($list as $booking) { + $events[$booking->getEventId()][] = $booking->getEvent(); + } + $this->assertCount(2, $events); + $this->assertCount(2, $events[$event]); + $this->assertSame($events[$event][0], $events[$event][1]); } /** diff --git a/tests/GaletteEvents/tests/units/Event.php b/tests/GaletteEvents/tests/units/Event.php index cd2e92c..4be0cc4 100644 --- a/tests/GaletteEvents/tests/units/Event.php +++ b/tests/GaletteEvents/tests/units/Event.php @@ -273,12 +273,6 @@ public function testRemove(): void $this->insertBooking($id, $member_one->id, ['number_people' => 3, 'is_paid' => $this->zdb->isPostgres() ? 'true' : 1]); $event = new \GaletteEvents\Event($this->zdb, $this->login, $this->history, $id); - $attendees = []; - foreach ($event->countAttendees() as $row) { - $attendees[(int)(bool)$row['is_paid']] = (int)$row['count']; - } - $this->assertSame([1 => 3], $attendees); - $event->remove(); $this->assertSame(0, $this->countBookings($id)); $this->assertSame([], $this->getEventActivities($id)); From 5449f5472daa78b2bb34bea218f0a6c54785eefe Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski <johan@x-tnd.be> Date: Sun, 27 Sep 2026 12:35:05 +0200 Subject: [PATCH 9/9] Extract strings --- lang/events.pot | 202 ++++++++++++++++++++++------------------- lang/events_en_US.po | 210 +++++++++++++++++++++++-------------------- 2 files changed, 226 insertions(+), 186 deletions(-) diff --git a/lang/events.pot b/lang/events.pot index abb9c88..c434563 100644 --- a/lang/events.pot +++ b/lang/events.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-27 08:45+0200\n" +"POT-Creation-Date: 2026-09-27 12:35+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -18,43 +18,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: ../lib/GaletteEvents/Event.php:182 +#: ../lib/GaletteEvents/Event.php:116 msgid "Begin date is mandatory" msgstr "" -#: ../lib/GaletteEvents/Event.php:206 ../templates/default/event.html.twig:40 +#: ../lib/GaletteEvents/Event.php:119 ../templates/default/event.html.twig:40 msgid "Begin date" msgstr "" -#: ../lib/GaletteEvents/Event.php:208 ../templates/default/event.html.twig:46 +#: ../lib/GaletteEvents/Event.php:120 ../templates/default/event.html.twig:46 msgid "End date" msgstr "" -#: ../lib/GaletteEvents/Event.php:226 +#: ../lib/GaletteEvents/Event.php:137 msgid "End date must be later or equal to begin date" msgstr "" -#: ../lib/GaletteEvents/Event.php:232 ../lib/GaletteEvents/Activity.php:155 +#: ../lib/GaletteEvents/Event.php:143 ../lib/GaletteEvents/Activity.php:92 msgid "Name is mandatory" msgstr "" -#: ../lib/GaletteEvents/Event.php:248 +#: ../lib/GaletteEvents/Event.php:159 msgid "Please select a group you own!" msgstr "" -#: ../lib/GaletteEvents/Event.php:255 +#: ../lib/GaletteEvents/Event.php:166 msgid "Town is mandatory" msgstr "" -#: ../lib/GaletteEvents/Event.php:380 -msgid "Event added" +#: ../lib/GaletteEvents/Event.php:277 +msgid "Fail to add new event." msgstr "" -#: ../lib/GaletteEvents/Event.php:384 -msgid "Fail to add new event." +#: ../lib/GaletteEvents/Event.php:286 +msgid "Event added" msgstr "" -#: ../lib/GaletteEvents/Event.php:403 +#: ../lib/GaletteEvents/Event.php:302 msgid "Event updated" msgstr "" @@ -70,286 +70,306 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: ../lib/GaletteEvents/Controllers/CsvController.php:86 -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:226 +#: ../lib/GaletteEvents/Controllers/CsvController.php:87 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:233 #: ../templates/default/booking.html.twig:33 ../templates/default/booking.html.twig:39 #: ../templates/default/bookings.html.twig:49 ../templates/default/bookings.html.twig:77 msgid "Event" msgstr "" -#: ../lib/GaletteEvents/Controllers/CsvController.php:90 +#: ../lib/GaletteEvents/Controllers/CsvController.php:91 #: ../templates/default/event.html.twig:86 msgid "Zip code" msgstr "" -#: ../lib/GaletteEvents/Controllers/CsvController.php:96 +#: ../lib/GaletteEvents/Controllers/CsvController.php:97 #: ../templates/default/booking.html.twig:63 msgid "Number of persons" msgstr "" -#: ../lib/GaletteEvents/Controllers/CsvController.php:111 +#. TRANS: %1$s is the event identifier +#: ../lib/GaletteEvents/Controllers/CsvController.php:110 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:389 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:121 +#, php-format +msgid "No event #%1$s." +msgstr "" + +#: ../lib/GaletteEvents/Controllers/CsvController.php:125 #: ../templates/default/booking.html.twig:115 msgid "Amount" msgstr "" -#: ../lib/GaletteEvents/Controllers/CsvController.php:113 +#: ../lib/GaletteEvents/Controllers/CsvController.php:127 #: ../templates/default/booking.html.twig:127 msgid "Bank name" msgstr "" #. TRANS: Bank check number -#: ../lib/GaletteEvents/Controllers/CsvController.php:115 +#: ../lib/GaletteEvents/Controllers/CsvController.php:129 #: ../templates/default/booking.html.twig:133 msgid "Check number" msgstr "" #. TRANS: this is a filename: all lowercase, no special character, no space. -#: ../lib/GaletteEvents/Controllers/CsvController.php:170 +#: ../lib/GaletteEvents/Controllers/CsvController.php:184 msgid "bookingslist" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:102 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:103 msgid "Events management" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:128 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:129 msgid "Javascript libraries has not been built!" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:137 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:136 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:138 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:138 msgid "Events calendar" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:288 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:299 msgid "Activity has been attached to event." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:289 -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:295 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:300 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:306 msgid "Do not forget to store the event" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:291 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:302 #: ../templates/default/event.html.twig:179 msgid "Please choose an activity to add" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:294 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:305 msgid "Activity has been detached from event." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:303 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:315 msgid "New event has been successfully added." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:305 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:317 msgid "Event has been modified." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:308 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:324 msgid "An error occurred while storing the event." msgstr "" #. TRANS: %1$s is the event name -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:417 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:468 #, php-format msgid "Remove event '%1$s'" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:134 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:150 msgid "Bookings management" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:258 -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:322 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:274 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:338 msgid "No booking was selected, please check at least one." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:317 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:333 msgid "No action was matching." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:381 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:403 msgid "Booking" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:510 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:530 msgid "New booking has been successfully added." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:512 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:532 msgid "Booking has been modified." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:516 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:539 msgid "An error occurred while storing the booking." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:524 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:546 msgid "Do not forget to store the booking" msgstr "" +#. TRANS: %1$s is the booking identifier +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:617 +#, php-format +msgid "No booking #%1$s." +msgstr "" + #. TRANS: %1$s is the member name, %2$s the event name. -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:641 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:698 #, php-format msgid "Remove booking for %1$s on %2$s" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:94 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:96 msgid "Activities management" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:156 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:164 msgid "Activity" msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:212 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:218 msgid "New activity has been successfully added." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:214 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:220 msgid "Activity has been modified." msgstr "" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:218 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:227 msgid "An error occurred while storing the activity." msgstr "" +#. TRANS: %1$s is the activity identifier +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:282 +#, php-format +msgid "No activity #%1$s." +msgstr "" + #. TRANS %1$s is activity name -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:297 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:341 #, php-format msgid "Remove activity %1$s" msgstr "" -#: ../lib/GaletteEvents/Booking.php:177 +#: ../lib/GaletteEvents/Booking.php:118 msgid "Event is mandatory" msgstr "" -#: ../lib/GaletteEvents/Booking.php:183 +#: ../lib/GaletteEvents/Booking.php:127 msgid "This event cannot be booked." msgstr "" -#. TRANS: %1$s is activity name -#: ../lib/GaletteEvents/Booking.php:193 -#, php-format -msgid "%1$s is mandatory for this event!" -msgstr "" - -#: ../lib/GaletteEvents/Booking.php:231 +#: ../lib/GaletteEvents/Booking.php:151 msgid "Amount must be a number." msgstr "" -#: ../lib/GaletteEvents/Booking.php:236 +#: ../lib/GaletteEvents/Booking.php:156 msgid "Please specify amount if booking has been paid ;)" msgstr "" -#: ../lib/GaletteEvents/Booking.php:257 +#: ../lib/GaletteEvents/Booking.php:177 msgid "Member is mandatory" msgstr "" -#: ../lib/GaletteEvents/Booking.php:271 +#: ../lib/GaletteEvents/Booking.php:191 msgid "You can only book other members on events of groups you manage." msgstr "" -#: ../lib/GaletteEvents/Booking.php:281 +#: ../lib/GaletteEvents/Booking.php:201 msgid "There must be at least one person" msgstr "" -#: ../lib/GaletteEvents/Booking.php:290 +#: ../lib/GaletteEvents/Booking.php:210 msgid "Booking date is mandatory!" msgstr "" -#: ../lib/GaletteEvents/Booking.php:314 +#: ../lib/GaletteEvents/Booking.php:212 msgid "booking date" msgstr "" #. TRANS: first replacement is member name, second is event name -#: ../lib/GaletteEvents/Booking.php:336 +#: ../lib/GaletteEvents/Booking.php:235 #, php-format msgid "A booking already exists for %1$s in %2$s" msgstr "" -#: ../lib/GaletteEvents/Booking.php:402 -msgid "Booking added" +#. TRANS: %1$s is activity name +#: ../lib/GaletteEvents/Booking.php:274 +#, php-format +msgid "%1$s is mandatory for this event!" msgstr "" -#: ../lib/GaletteEvents/Booking.php:406 +#: ../lib/GaletteEvents/Booking.php:325 msgid "Fail to add new booking." msgstr "" -#: ../lib/GaletteEvents/Booking.php:425 -msgid "Booking updated" +#: ../lib/GaletteEvents/Booking.php:334 +msgid "Booking added" msgstr "" -#: ../lib/GaletteEvents/Activity.php:221 -msgid "Activity added" +#: ../lib/GaletteEvents/Booking.php:350 +msgid "Booking updated" msgstr "" -#: ../lib/GaletteEvents/Activity.php:226 +#: ../lib/GaletteEvents/Activity.php:145 msgid "Fail to add new activity." msgstr "" -#: ../lib/GaletteEvents/Activity.php:245 +#: ../lib/GaletteEvents/Activity.php:154 +msgid "Activity added" +msgstr "" + +#: ../lib/GaletteEvents/Activity.php:170 msgid "Activity updated" msgstr "" -#: ../lib/GaletteEvents/Repository/Events.php:170 +#: ../lib/GaletteEvents/Repository/Events.php:182 msgid "Event information" msgstr "" -#: ../lib/GaletteEvents/Repository/Events.php:174 +#: ../lib/GaletteEvents/Repository/Events.php:186 msgid "Start date:" msgstr "" -#: ../lib/GaletteEvents/Repository/Events.php:175 +#: ../lib/GaletteEvents/Repository/Events.php:187 msgid "End date:" msgstr "" -#: ../lib/GaletteEvents/Repository/Events.php:176 +#: ../lib/GaletteEvents/Repository/Events.php:188 msgid "Location:" msgstr "" -#: ../lib/GaletteEvents/Repository/Events.php:178 +#: ../lib/GaletteEvents/Repository/Events.php:190 msgid "Comment:" msgstr "" #. TRANS: %1$s is the number of paid attendees -#: ../lib/GaletteEvents/Repository/Events.php:195 +#: ../lib/GaletteEvents/Repository/Events.php:199 #, php-format msgid "%1$s paid" msgstr "" -#: ../lib/GaletteEvents/Repository/Events.php:200 +#: ../lib/GaletteEvents/Repository/Events.php:204 msgid "Attendees:" msgstr "" -#: ../lib/GaletteEvents/Repository/Events.php:208 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:93 +#: ../lib/GaletteEvents/Repository/Events.php:212 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:95 #: ../templates/default/booking.html.twig:78 ../templates/default/event.html.twig:108 msgid "Activities" msgstr "" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:58 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:62 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:60 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:64 #: ../templates/default/activities.html.twig:41 ../templates/default/activities.html.twig:74 msgid "Events" msgstr "" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:69 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:135 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:71 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:137 msgid "Calendar" msgstr "" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:75 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:77 msgid "Bookings" msgstr "" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:156 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:158 msgid "New event booking" msgstr "" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:212 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:214 msgid "Upcoming events" msgstr "" diff --git a/lang/events_en_US.po b/lang/events_en_US.po index 8dc055e..3da8665 100644 --- a/lang/events_en_US.po +++ b/lang/events_en_US.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-27 08:45+0200\n" -"PO-Revision-Date: 2026-09-27 08:45+0200\n" +"POT-Creation-Date: 2026-09-27 12:35+0200\n" +"PO-Revision-Date: 2026-09-27 12:35+0200\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: en_US\n" @@ -17,43 +17,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: ../lib/GaletteEvents/Event.php:182 +#: ../lib/GaletteEvents/Event.php:116 msgid "Begin date is mandatory" msgstr "Begin date is mandatory" -#: ../lib/GaletteEvents/Event.php:206 ../templates/default/event.html.twig:40 +#: ../lib/GaletteEvents/Event.php:119 ../templates/default/event.html.twig:40 msgid "Begin date" msgstr "Begin date" -#: ../lib/GaletteEvents/Event.php:208 ../templates/default/event.html.twig:46 +#: ../lib/GaletteEvents/Event.php:120 ../templates/default/event.html.twig:46 msgid "End date" msgstr "End date" -#: ../lib/GaletteEvents/Event.php:226 +#: ../lib/GaletteEvents/Event.php:137 msgid "End date must be later or equal to begin date" msgstr "End date must be later or equal to begin date" -#: ../lib/GaletteEvents/Event.php:232 ../lib/GaletteEvents/Activity.php:155 +#: ../lib/GaletteEvents/Event.php:143 ../lib/GaletteEvents/Activity.php:92 msgid "Name is mandatory" msgstr "Name is mandatory" -#: ../lib/GaletteEvents/Event.php:248 +#: ../lib/GaletteEvents/Event.php:159 msgid "Please select a group you own!" msgstr "Please select a group you own!" -#: ../lib/GaletteEvents/Event.php:255 +#: ../lib/GaletteEvents/Event.php:166 msgid "Town is mandatory" msgstr "Town is mandatory" -#: ../lib/GaletteEvents/Event.php:380 -msgid "Event added" -msgstr "Event added" - -#: ../lib/GaletteEvents/Event.php:384 +#: ../lib/GaletteEvents/Event.php:277 msgid "Fail to add new event." msgstr "Fail to add new event." -#: ../lib/GaletteEvents/Event.php:403 +#: ../lib/GaletteEvents/Event.php:286 +msgid "Event added" +msgstr "Event added" + +#: ../lib/GaletteEvents/Event.php:302 msgid "Event updated" msgstr "Event updated" @@ -73,8 +73,8 @@ msgstr[1] "" "Group \"%1$s\" is used by %2$s events, it cannot be deleted. Remove the " "events or change their group first." -#: ../lib/GaletteEvents/Controllers/CsvController.php:86 -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:226 +#: ../lib/GaletteEvents/Controllers/CsvController.php:87 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:233 #: ../templates/default/booking.html.twig:33 #: ../templates/default/booking.html.twig:39 #: ../templates/default/bookings.html.twig:49 @@ -82,281 +82,301 @@ msgstr[1] "" msgid "Event" msgstr "Event" -#: ../lib/GaletteEvents/Controllers/CsvController.php:90 +#: ../lib/GaletteEvents/Controllers/CsvController.php:91 #: ../templates/default/event.html.twig:86 msgid "Zip code" msgstr "Zip code" -#: ../lib/GaletteEvents/Controllers/CsvController.php:96 +#: ../lib/GaletteEvents/Controllers/CsvController.php:97 #: ../templates/default/booking.html.twig:63 msgid "Number of persons" msgstr "Number of persons" -#: ../lib/GaletteEvents/Controllers/CsvController.php:111 +#. TRANS: %1$s is the event identifier +#: ../lib/GaletteEvents/Controllers/CsvController.php:110 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:389 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:121 +#, php-format +msgid "No event #%1$s." +msgstr "No event #%1$s." + +#: ../lib/GaletteEvents/Controllers/CsvController.php:125 #: ../templates/default/booking.html.twig:115 msgid "Amount" msgstr "Amount" -#: ../lib/GaletteEvents/Controllers/CsvController.php:113 +#: ../lib/GaletteEvents/Controllers/CsvController.php:127 #: ../templates/default/booking.html.twig:127 msgid "Bank name" msgstr "Bank name" #. TRANS: Bank check number -#: ../lib/GaletteEvents/Controllers/CsvController.php:115 +#: ../lib/GaletteEvents/Controllers/CsvController.php:129 #: ../templates/default/booking.html.twig:133 msgid "Check number" msgstr "Check number" #. TRANS: this is a filename: all lowercase, no special character, no space. -#: ../lib/GaletteEvents/Controllers/CsvController.php:170 +#: ../lib/GaletteEvents/Controllers/CsvController.php:184 msgid "bookingslist" msgstr "bookingslist" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:102 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:103 msgid "Events management" msgstr "Events management" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:128 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:129 msgid "Javascript libraries has not been built!" msgstr "Javascript libraries has not been built!" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:137 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:136 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:138 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:138 msgid "Events calendar" msgstr "Events calendar" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:288 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:299 msgid "Activity has been attached to event." msgstr "Activity has been attached to event." -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:289 -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:295 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:300 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:306 msgid "Do not forget to store the event" msgstr "Do not forget to store the event" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:291 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:302 #: ../templates/default/event.html.twig:179 msgid "Please choose an activity to add" msgstr "Please choose an activity to add" -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:294 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:305 msgid "Activity has been detached from event." msgstr "Activity has been detached from event." -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:303 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:315 msgid "New event has been successfully added." msgstr "New event has been successfully added." -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:305 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:317 msgid "Event has been modified." msgstr "Event has been modified." -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:308 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:324 msgid "An error occurred while storing the event." msgstr "An error occurred while storing the event." #. TRANS: %1$s is the event name -#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:417 +#: ../lib/GaletteEvents/Controllers/Crud/EventsController.php:468 #, php-format msgid "Remove event '%1$s'" msgstr "Remove event '%1$s'" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:134 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:150 msgid "Bookings management" msgstr "Bookings management" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:258 -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:322 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:274 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:338 msgid "No booking was selected, please check at least one." msgstr "No booking was selected, please check at least one." -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:317 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:333 msgid "No action was matching." msgstr "No action was matching." -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:381 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:403 msgid "Booking" msgstr "Booking" -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:510 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:530 msgid "New booking has been successfully added." msgstr "New booking has been successfully added." -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:512 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:532 msgid "Booking has been modified." msgstr "Booking has been modified." -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:516 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:539 msgid "An error occurred while storing the booking." msgstr "An error occurred while storing the booking." -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:524 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:546 msgid "Do not forget to store the booking" msgstr "Do not forget to store the booking" +#. TRANS: %1$s is the booking identifier +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:617 +#, php-format +msgid "No booking #%1$s." +msgstr "No booking #%1$s." + #. TRANS: %1$s is the member name, %2$s the event name. -#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:641 +#: ../lib/GaletteEvents/Controllers/Crud/BookingsController.php:698 #, php-format msgid "Remove booking for %1$s on %2$s" msgstr "Remove booking for %1$s on %2$s" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:94 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:96 msgid "Activities management" msgstr "Activities management" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:156 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:164 msgid "Activity" msgstr "Activity" -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:212 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:218 msgid "New activity has been successfully added." msgstr "New activity has been successfully added." -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:214 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:220 msgid "Activity has been modified." msgstr "Activity has been modified." -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:218 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:227 msgid "An error occurred while storing the activity." msgstr "An error occurred while storing the activity." +#. TRANS: %1$s is the activity identifier +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:282 +#, php-format +msgid "No activity #%1$s." +msgstr "No activity #%1$s." + #. TRANS %1$s is activity name -#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:297 +#: ../lib/GaletteEvents/Controllers/Crud/ActivitiesController.php:341 #, php-format msgid "Remove activity %1$s" msgstr "Remove activity %1$s" -#: ../lib/GaletteEvents/Booking.php:177 +#: ../lib/GaletteEvents/Booking.php:118 msgid "Event is mandatory" msgstr "Event is mandatory" -#: ../lib/GaletteEvents/Booking.php:183 +#: ../lib/GaletteEvents/Booking.php:127 msgid "This event cannot be booked." msgstr "This event cannot be booked." -#. TRANS: %1$s is activity name -#: ../lib/GaletteEvents/Booking.php:193 -#, php-format -msgid "%1$s is mandatory for this event!" -msgstr "%1$s is mandatory for this event!" - -#: ../lib/GaletteEvents/Booking.php:231 +#: ../lib/GaletteEvents/Booking.php:151 msgid "Amount must be a number." msgstr "Amount must be a number." -#: ../lib/GaletteEvents/Booking.php:236 +#: ../lib/GaletteEvents/Booking.php:156 msgid "Please specify amount if booking has been paid ;)" msgstr "Please specify amount if booking has been paid ;)" -#: ../lib/GaletteEvents/Booking.php:257 +#: ../lib/GaletteEvents/Booking.php:177 msgid "Member is mandatory" msgstr "Member is mandatory" -#: ../lib/GaletteEvents/Booking.php:271 +#: ../lib/GaletteEvents/Booking.php:191 msgid "You can only book other members on events of groups you manage." msgstr "You can only book other members on events of groups you manage." -#: ../lib/GaletteEvents/Booking.php:281 +#: ../lib/GaletteEvents/Booking.php:201 msgid "There must be at least one person" msgstr "There must be at least one person" -#: ../lib/GaletteEvents/Booking.php:290 +#: ../lib/GaletteEvents/Booking.php:210 msgid "Booking date is mandatory!" msgstr "Booking date is mandatory!" -#: ../lib/GaletteEvents/Booking.php:314 +#: ../lib/GaletteEvents/Booking.php:212 msgid "booking date" msgstr "booking date" #. TRANS: first replacement is member name, second is event name -#: ../lib/GaletteEvents/Booking.php:336 +#: ../lib/GaletteEvents/Booking.php:235 #, php-format msgid "A booking already exists for %1$s in %2$s" msgstr "A booking already exists for %1$s in %2$s" -#: ../lib/GaletteEvents/Booking.php:402 -msgid "Booking added" -msgstr "Booking added" +#. TRANS: %1$s is activity name +#: ../lib/GaletteEvents/Booking.php:274 +#, php-format +msgid "%1$s is mandatory for this event!" +msgstr "%1$s is mandatory for this event!" -#: ../lib/GaletteEvents/Booking.php:406 +#: ../lib/GaletteEvents/Booking.php:325 msgid "Fail to add new booking." msgstr "Fail to add new booking." -#: ../lib/GaletteEvents/Booking.php:425 +#: ../lib/GaletteEvents/Booking.php:334 +msgid "Booking added" +msgstr "Booking added" + +#: ../lib/GaletteEvents/Booking.php:350 msgid "Booking updated" msgstr "Booking updated" -#: ../lib/GaletteEvents/Activity.php:221 -msgid "Activity added" -msgstr "Activity added" - -#: ../lib/GaletteEvents/Activity.php:226 +#: ../lib/GaletteEvents/Activity.php:145 msgid "Fail to add new activity." msgstr "Fail to add new activity." -#: ../lib/GaletteEvents/Activity.php:245 +#: ../lib/GaletteEvents/Activity.php:154 +msgid "Activity added" +msgstr "Activity added" + +#: ../lib/GaletteEvents/Activity.php:170 msgid "Activity updated" msgstr "Activity updated" -#: ../lib/GaletteEvents/Repository/Events.php:170 +#: ../lib/GaletteEvents/Repository/Events.php:182 msgid "Event information" msgstr "Event information" -#: ../lib/GaletteEvents/Repository/Events.php:174 +#: ../lib/GaletteEvents/Repository/Events.php:186 msgid "Start date:" msgstr "Start date:" -#: ../lib/GaletteEvents/Repository/Events.php:175 +#: ../lib/GaletteEvents/Repository/Events.php:187 msgid "End date:" msgstr "End date:" -#: ../lib/GaletteEvents/Repository/Events.php:176 +#: ../lib/GaletteEvents/Repository/Events.php:188 msgid "Location:" msgstr "Location:" -#: ../lib/GaletteEvents/Repository/Events.php:178 +#: ../lib/GaletteEvents/Repository/Events.php:190 msgid "Comment:" msgstr "Comment:" #. TRANS: %1$s is the number of paid attendees -#: ../lib/GaletteEvents/Repository/Events.php:195 +#: ../lib/GaletteEvents/Repository/Events.php:199 #, php-format msgid "%1$s paid" msgstr "%1$s paid" -#: ../lib/GaletteEvents/Repository/Events.php:200 +#: ../lib/GaletteEvents/Repository/Events.php:204 msgid "Attendees:" msgstr "Attendees:" -#: ../lib/GaletteEvents/Repository/Events.php:208 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:93 +#: ../lib/GaletteEvents/Repository/Events.php:212 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:95 #: ../templates/default/booking.html.twig:78 #: ../templates/default/event.html.twig:108 msgid "Activities" msgstr "Activities" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:58 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:62 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:60 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:64 #: ../templates/default/activities.html.twig:41 #: ../templates/default/activities.html.twig:74 msgid "Events" msgstr "Events" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:69 -#: ../lib/GaletteEvents/PluginGaletteEvents.php:135 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:71 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:137 msgid "Calendar" msgstr "Calendar" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:75 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:77 msgid "Bookings" msgstr "Bookings" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:156 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:158 msgid "New event booking" msgstr "New event booking" -#: ../lib/GaletteEvents/PluginGaletteEvents.php:212 +#: ../lib/GaletteEvents/PluginGaletteEvents.php:214 msgid "Upcoming events" msgstr "Upcoming events"