diff --git a/db/factories/Shifts/ShiftEntryFactory.php b/db/factories/Shifts/ShiftEntryFactory.php new file mode 100644 index 00000000..23b3227d --- /dev/null +++ b/db/factories/Shifts/ShiftEntryFactory.php @@ -0,0 +1,29 @@ +faker->optional(.01, false)->boolean(); + + return [ + 'shift_id' => Shift::factory(), + 'angel_type_id' => AngelType::factory(), + 'user_id' => User::factory(), + 'user_comment' => $this->faker->optional(.05, '')->text(), + 'freeloaded' => $freeloaded, + 'freeloaded_comment' => $freeloaded ? $this->faker->text() : '', + ]; + } +} diff --git a/db/migrations/2022_12_30_000000_create_shift_entries_table.php b/db/migrations/2022_12_30_000000_create_shift_entries_table.php new file mode 100644 index 00000000..b798a33d --- /dev/null +++ b/db/migrations/2022_12_30_000000_create_shift_entries_table.php @@ -0,0 +1,114 @@ +schema->getConnection(); + $previous = $this->schema->hasTable('ShiftEntry'); + + $this->schema->create('shift_entries', function (Blueprint $table): void { + $table->increments('id'); + $this->references($table, 'shifts'); + $this->references($table, 'angel_types'); + $this->referencesUser($table); + + $table->mediumText('user_comment')->default(''); + + $table->boolean('freeloaded')->default(false)->index(); + $table->mediumText('freeloaded_comment')->default(''); + + $table->index(['angel_type_id', 'shift_id']); + }); + + if (!$previous) { + return; + } + + /** @var stdClass[] $records */ + $records = $connection + ->table('ShiftEntry') + ->get(); + foreach ($records as $record) { + $connection->table('shift_entries')->insert([ + 'id' => $record->id, + 'shift_id' => $record->SID, + 'angel_type_id' => $record->TID, + 'user_id' => $record->UID, + 'user_comment' => $record->Comment, + 'freeloaded' => (bool) $record->freeloaded, + 'freeloaded_comment' => $record->freeload_comment, + ]); + } + + $this->changeReferences( + 'ShiftEntry', + 'id', + 'shift_entries', + 'id' + ); + + $this->schema->drop('ShiftEntry'); + } + + /** + * Recreates the previous table, copies the data and drops the new one + */ + public function down(): void + { + $connection = $this->schema->getConnection(); + + $this->schema->create('ShiftEntry', function (Blueprint $table): void { + $table->increments('id'); + $this->references($table, 'shifts', 'SID')->default(0); + $this->references($table, 'angel_types', 'TID')->default(0); + $this->references($table, 'users', 'UID')->default(0); + + $table->mediumText('Comment')->nullable(); + + $table->mediumText('freeload_comment')->nullable()->default(null); + $table->boolean('freeloaded')->index(); + + $table->index(['SID', 'TID']); + }); + + /** @var stdClass[] $records */ + $records = $connection + ->table('shift_entries') + ->get(); + foreach ($records as $record) { + $connection->table('ShiftEntry')->insert([ + 'id' => $record->id, + 'SID' => $record->shift_id, + 'TID' => $record->angel_type_id, + 'UID' => $record->user_id, + 'Comment' => $record->user_comment, + 'freeloaded' => (bool) $record->freeloaded, + 'freeload_comment' => $record->freeloaded_comment, + ]); + } + + $this->changeReferences( + 'shift_entries', + 'id', + 'ShiftEntry', + 'id' + ); + + $this->schema->drop('shift_entries'); + } +} diff --git a/includes/controller/public_dashboard_controller.php b/includes/controller/public_dashboard_controller.php index 4b09732e..823f6a86 100644 --- a/includes/controller/public_dashboard_controller.php +++ b/includes/controller/public_dashboard_controller.php @@ -107,8 +107,8 @@ function public_dashboard_needed_angels($needed_angels, ShiftsFilter $filter = n $result = []; foreach ($needed_angels as $needed_angel) { $need = $needed_angel['count'] - $needed_angel['taken']; - if ($need > 0 && (!$filter || in_array($needed_angel['TID'], $filter->getTypes()))) { - $angeltype = AngelType::find($needed_angel['TID']); + if ($need > 0 && (!$filter || in_array($needed_angel['angel_type_id'], $filter->getTypes()))) { + $angeltype = AngelType::find($needed_angel['angel_type_id']); if ($angeltype->show_on_dashboard) { $result[] = [ 'need' => $need, diff --git a/includes/controller/shift_entries_controller.php b/includes/controller/shift_entries_controller.php index a06f4d7f..7cd7b352 100644 --- a/includes/controller/shift_entries_controller.php +++ b/includes/controller/shift_entries_controller.php @@ -2,6 +2,7 @@ use Engelsystem\Models\AngelType; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\User; use Engelsystem\Models\UserAngelType; use Engelsystem\ShiftSignupState; @@ -27,7 +28,7 @@ function shift_entries_controller(): array return match ($action) { 'create' => shift_entry_create_controller(), 'delete' => shift_entry_delete_controller(), - default => ['', ''], + default => ['', ''], }; } @@ -41,7 +42,7 @@ function shift_entry_create_controller(): array $user = auth()->user(); $request = request(); - if (User_is_freeloader($user)) { + if ($user->isFreeloader()) { throw_redirect(page_link_to('user_myshifts')); } @@ -99,14 +100,12 @@ function shift_entry_create_controller_admin(Shift $shift, ?AngelType $angeltype } if ($request->hasPostData('submit')) { - ShiftEntry_create([ - 'SID' => $shift->id, - 'TID' => $angeltype->id, - 'UID' => $signup_user->id, - 'Comment' => '', - 'freeloaded' => false, - 'freeload_comment' => '' - ]); + $shiftEntry = new ShiftEntry(); + $shiftEntry->shift()->associate($shift); + $shiftEntry->angelType()->associate($angeltype); + $shiftEntry->user()->associate($signup_user); + $shiftEntry->save(); + ShiftEntry_onCreate($shiftEntry); success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user))); throw_redirect(shift_link($shift)); @@ -150,14 +149,12 @@ function shift_entry_create_controller_supporter(Shift $shift, AngelType $angelt } if ($request->hasPostData('submit')) { - ShiftEntry_create([ - 'SID' => $shift->id, - 'TID' => $angeltype->id, - 'UID' => $signup_user->id, - 'Comment' => '', - 'freeloaded' => false, - 'freeload_comment' => '' - ]); + $shiftEntry = new ShiftEntry(); + $shiftEntry->shift()->associate($shift); + $shiftEntry->angelType()->associate($angeltype); + $shiftEntry->user()->associate($signup_user); + $shiftEntry->save(); + ShiftEntry_onCreate($shiftEntry); success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user))); throw_redirect(shift_link($shift)); @@ -214,7 +211,9 @@ function shift_entry_create_controller_user(Shift $shift, AngelType $angeltype): $signup_user = auth()->user(); $needed_angeltype = (new AngelType())->forceFill(NeededAngeltype_by_Shift_and_Angeltype($shift, $angeltype)); - $shift_entries = ShiftEntries_by_shift_and_angeltype($shift->id, $angeltype->id); + $shift_entries = $shift->shiftEntries() + ->where('angel_type_id', $angeltype->id) + ->get(); $shift_signup_state = Shift_signup_allowed( $signup_user, $shift, @@ -232,14 +231,14 @@ function shift_entry_create_controller_user(Shift $shift, AngelType $angeltype): $comment = ''; if ($request->hasPostData('submit')) { $comment = strip_request_item_nl('comment'); - ShiftEntry_create([ - 'SID' => $shift->id, - 'TID' => $angeltype->id, - 'UID' => $signup_user->id, - 'Comment' => $comment, - 'freeloaded' => false, - 'freeload_comment' => '' - ]); + + $shiftEntry = new ShiftEntry(); + $shiftEntry->shift()->associate($shift); + $shiftEntry->angelType()->associate($angeltype); + $shiftEntry->user()->associate($signup_user); + $shiftEntry->user_comment = $comment; + $shiftEntry->save(); + ShiftEntry_onCreate($shiftEntry); if ( !$angeltype->restricted @@ -299,7 +298,7 @@ function shift_entry_create_link_admin(Shift $shift, $params = []) /** * Load a shift entry from get parameter shift_entry_id. * - * @return array + * @return ShiftEntry */ function shift_entry_load() { @@ -308,11 +307,7 @@ function shift_entry_load() if (!$request->has('shift_entry_id') || !test_request_int('shift_entry_id')) { throw_redirect(page_link_to('user_shifts')); } - $shiftEntry = ShiftEntry($request->input('shift_entry_id')); - if (empty($shiftEntry)) { - error(__('Shift entry not found.')); - throw_redirect(page_link_to('user_shifts')); - } + $shiftEntry = ShiftEntry::findOrFail($request->input('shift_entry_id')); return $shiftEntry; } @@ -328,9 +323,9 @@ function shift_entry_delete_controller() $request = request(); $shiftEntry = shift_entry_load(); - $shift = Shift($shiftEntry['SID']); - $angeltype = AngelType::find($shiftEntry['TID']); - $signout_user = User::find($shiftEntry['UID']); + $shift = Shift($shiftEntry->shift); + $angeltype = $shiftEntry->angelType; + $signout_user = $shiftEntry->user; if (!Shift_signout_allowed($shift, $angeltype, $signout_user->id)) { error(__( 'You are not allowed to remove this shift entry. If necessary, ask your supporter or heaven to do so.' @@ -339,7 +334,8 @@ function shift_entry_delete_controller() } if ($request->hasPostData('delete')) { - ShiftEntry_delete($shiftEntry); + $shiftEntry->delete(); + ShiftEntry_onDelete($shiftEntry); success(__('Shift entry removed.')); throw_redirect(shift_link($shift)); } @@ -347,7 +343,7 @@ function shift_entry_delete_controller() if ($user->id == $signout_user->id) { return [ ShiftEntry_delete_title(), - ShiftEntry_delete_view($shift, $angeltype, $signout_user->id) + ShiftEntry_delete_view($shift, $angeltype, $signout_user) ]; } @@ -360,8 +356,8 @@ function shift_entry_delete_controller() /** * Link to delete a shift entry. * - * @param array|Shift $shiftEntry - * @param array $params + * @param Shift|ShiftEntry $shiftEntry + * @param array $params * @return string URL */ function shift_entry_delete_link($shiftEntry, $params = []) diff --git a/includes/controller/shifts_controller.php b/includes/controller/shifts_controller.php index df582ff6..bbaf2d23 100644 --- a/includes/controller/shifts_controller.php +++ b/includes/controller/shifts_controller.php @@ -6,7 +6,6 @@ use Engelsystem\Models\AngelType; use Engelsystem\Models\Shifts\ScheduleShift; use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\Shifts\ShiftType; -use Engelsystem\Models\User\User; use Engelsystem\ShiftSignupState; use Illuminate\Support\Collection; @@ -159,7 +158,6 @@ function shift_edit_controller() $shift->updatedBy()->associate(auth()->user()); // Remove merged data as it is not really part of the model and thus can't be saved - unset($shift->shiftEntry); unset($shift->neededAngels); $shift->save(); @@ -244,24 +242,23 @@ function shift_delete_controller() // Schicht löschen bestätigt if ($request->hasPostData('delete')) { - foreach ($shift->shiftEntry as $entry) { - $type = AngelType::find($entry['TID']); + foreach ($shift->shiftEntries as $entry) { event('shift.entry.deleting', [ - 'user' => User::find($entry['user_id']), + 'user' => $entry->user, 'start' => $shift->start, 'end' => $shift->end, 'name' => $shift->shiftType->name, 'title' => $shift->title, - 'type' => $type->name, + 'type' => $entry->angelType->name, 'room' => $shift->room, - 'freeloaded' => (bool) $entry['freeloaded'], + 'freeloaded' => $entry->freeloaded, ]); } $shift->delete(); engelsystem_log( - 'Deleted shift ' . $shift->title . ': ' . $shift->shiftType->name + 'Deleted shift ' . $shift->title . ': ' . $shift->shiftType->name . ' from ' . $shift->start->format('Y-m-d H:i') . ' to ' . $shift->end->format('Y-m-d H:i') ); @@ -318,7 +315,9 @@ function shift_controller() continue; } - $shift_entries = ShiftEntries_by_shift_and_angeltype($shift->id, $angeltype->id); + $shift_entries = $shift->shiftEntries() + ->where('angel_type_id', $angeltype->id) + ->get(); $needed_angeltype = (new AngelType())->forceFill($needed_angeltype); $angeltype_signup_state = Shift_signup_allowed( @@ -351,8 +350,8 @@ function shifts_controller() } return match ($request->input('action')) { - 'view' => shift_controller(), - 'next' => shift_next_controller(), // throws redirect + 'view' => shift_controller(), + 'next' => shift_next_controller(), // throws redirect default => throw_redirect(page_link_to('/')), }; } @@ -368,8 +367,8 @@ function shift_next_controller() $upcoming_shifts = ShiftEntries_upcoming_for_user(auth()->user()); - if (!empty($upcoming_shifts)) { - throw_redirect(shift_link($upcoming_shifts[0])); + if (!$upcoming_shifts->isEmpty()) { + throw_redirect(shift_link($upcoming_shifts[0]->shift)); } throw_redirect(page_link_to('user_shifts')); @@ -406,41 +405,41 @@ function shifts_json_export_controller() // See engelsystem-base/src/main/kotlin/info/metadude/kotlin/library/engelsystem/models/Shift.kt $data = [ // Name of the shift (type) - 'name' => $shift->shiftType->name, + 'name' => $shift->shiftType->name, // Shift / Talk title - 'title' => $shift->title, + 'title' => $shift->title, // Shift description - 'description' => $shift->description, + 'description' => $shift->description, // Users comment - 'Comment' => $shift->Comment, + 'Comment' => $shift->user_comment, // Shift id - 'SID' => $shift->id, + 'SID' => $shift->id, // Shift type id - 'shifttype_id' => $shift->shift_type_id, + 'shifttype_id' => $shift->shift_type_id, // Talk URL - 'URL' => $shift->url, + 'URL' => $shift->url, // Room name - 'Name' => $shift->room->name, + 'Name' => $shift->room->name, // Location map url - 'map_url' => $shift->room->map_url, + 'map_url' => $shift->room->map_url, // Start timestamp /** @deprecated start_date should be used */ - 'start' => $shift->start->timestamp, + 'start' => $shift->start->timestamp, // Start date - 'start_date' => $shift->start->toRfc3339String(), + 'start_date' => $shift->start->toRfc3339String(), // End timestamp /** @deprecated end_date should be used */ - 'end' => $shift->end->timestamp, + 'end' => $shift->end->timestamp, // End date - 'end_date' => $shift->end->toRfc3339String(), + 'end_date' => $shift->end->toRfc3339String(), // Timezone offset like "+01:00" /** @deprecated should be retrieved from start_date or end_date */ - 'timezone' => $timeZone->toOffsetName(), + 'timezone' => $timeZone->toOffsetName(), // The events timezone like "Europe/Berlin" 'event_timezone' => $timeZone->getName(), ]; diff --git a/includes/controller/users_controller.php b/includes/controller/users_controller.php index 7037105c..ee88475f 100644 --- a/includes/controller/users_controller.php +++ b/includes/controller/users_controller.php @@ -1,6 +1,7 @@ needed_angeltypes = Db::select( ' SELECT DISTINCT `angel_types`.* - FROM `ShiftEntry` - JOIN `angel_types` ON `ShiftEntry`.`TID`=`angel_types`.`id` - WHERE `ShiftEntry`.`SID` = ? + FROM `shift_entries` + JOIN `angel_types` ON `shift_entries`.`angel_type_id`=`angel_types`.`id` + WHERE `shift_entries`.`shift_id` = ? ORDER BY `angel_types`.`name` ', [$shift->id] @@ -220,11 +221,11 @@ function user_controller() foreach ($neededAngeltypes as &$needed_angeltype) { $needed_angeltype['users'] = Db::select( ' - SELECT `ShiftEntry`.`freeloaded`, `users`.* - FROM `ShiftEntry` - JOIN `users` ON `ShiftEntry`.`UID`=`users`.`id` - WHERE `ShiftEntry`.`SID` = ? - AND `ShiftEntry`.`TID` = ? + SELECT `shift_entries`.`freeloaded`, `users`.* + FROM `shift_entries` + JOIN `users` ON `shift_entries`.`user_id`=`users`.`id` + WHERE `shift_entries`.`shift_id` = ? + AND `shift_entries`.`angel_type_id` = ? ', [$shift->id, $needed_angeltype['id']] ); @@ -247,7 +248,7 @@ function user_controller() User_view( $user_source, auth()->can('admin_user'), - User_is_freeloader($user_source), + $user_source->isFreeloader(), $user_source->userAngelTypes, $user_source->groups, $shifts, @@ -300,7 +301,12 @@ function users_list_controller() ->orderBy('name') ->get(); foreach ($users as $user) { - $user->setAttribute('freeloads', count(ShiftEntries_freeloaded_by_user($user->id))); + $user->setAttribute( + 'freeloads', + $user->shiftEntries() + ->where('freeloaded', true) + ->count() + ); } $users = $users->sortBy(function (User $user) use ($order_by) { @@ -321,7 +327,7 @@ function users_list_controller() State::whereArrived(true)->count(), State::whereActive(true)->count(), State::whereForceActive(true)->count(), - ShiftEntries_freeloaded_count(), + ShiftEntry::whereFreeloaded(true)->count(), State::whereGotShirt(true)->count(), State::query()->sum('got_voucher') ) @@ -360,6 +366,7 @@ function shiftCalendarRendererByShiftFilter(ShiftsFilter $shiftsFilter) $shift_entries_source = ShiftEntries_by_ShiftsFilter($shiftsFilter); $needed_angeltypes = []; + /** @var ShiftEntry[][] $shift_entries */ $shift_entries = []; foreach ($shifts as $shift) { $needed_angeltypes[$shift->id] = []; @@ -367,8 +374,8 @@ function shiftCalendarRendererByShiftFilter(ShiftsFilter $shiftsFilter) } foreach ($shift_entries_source as $shift_entry) { - if (isset($shift_entries[$shift_entry['SID']])) { - $shift_entries[$shift_entry['SID']][] = $shift_entry; + if (isset($shift_entries[$shift_entry->shift_id])) { + $shift_entries[$shift_entry->shift_id][] = $shift_entry; } } @@ -403,8 +410,8 @@ function shiftCalendarRendererByShiftFilter(ShiftsFilter $shiftsFilter) foreach ($shift_entries[$shift->id] as $shift_entry) { if ( - $needed_angeltype['angel_type_id'] == $shift_entry['TID'] - && $shift_entry['freeloaded'] == 0 + $needed_angeltype['angel_type_id'] == $shift_entry->angel_type_id + && !$shift_entry->freeloaded ) { $taken++; } diff --git a/includes/mailer/shifts_mailer.php b/includes/mailer/shifts_mailer.php index ef0976ba..c1eee921 100644 --- a/includes/mailer/shifts_mailer.php +++ b/includes/mailer/shifts_mailer.php @@ -1,11 +1,16 @@ id); + /** @var ShiftEntry[]|Collection $shiftEntries */ + $shiftEntries = $old_shift->shiftEntries() + ->with(['user', 'user.settings']) + ->get(); $old_room = $old_shift->room; $new_room = $new_shift->room; @@ -65,8 +70,8 @@ function mail_shift_change(Shift $old_shift, Shift $new_shift) $message .= $new_room->name . "\n\n"; $message .= url('/shifts', ['action' => 'view', 'shift_id' => $new_shift->id]) . "\n"; - foreach ($users as $user) { - $user = (new User())->forceFill($user); + foreach ($shiftEntries as $shiftEntry) { + $user = $shiftEntry->user; if ($user->settings->email_shiftinfo) { engelsystem_email_to_user( $user, diff --git a/includes/model/NeededAngelTypes_model.php b/includes/model/NeededAngelTypes_model.php index 9b6b1ab9..c2d0c9b6 100644 --- a/includes/model/NeededAngelTypes_model.php +++ b/includes/model/NeededAngelTypes_model.php @@ -1,6 +1,8 @@ where('shift_id', $shiftId) + ->get(); $needed_angeltypes = []; foreach ($needed_angeltypes_source as $angeltype) { $angeltype['shift_entries'] = []; $angeltype['taken'] = 0; foreach ($shift_entries as $shift_entry) { - if ($shift_entry['TID'] == $angeltype['angel_type_id'] && $shift_entry['freeloaded'] == 0) { + if ($shift_entry->angel_type_id == $angeltype['angel_type_id'] && !$shift_entry->freeloaded) { $angeltype['taken']++; $angeltype['shift_entries'][] = $shift_entry; } diff --git a/includes/model/ShiftEntry_model.php b/includes/model/ShiftEntry_model.php index 1323603c..21ec0921 100644 --- a/includes/model/ShiftEntry_model.php +++ b/includes/model/ShiftEntry_model.php @@ -1,153 +1,40 @@ shiftType; - $room = $shift->room; - $angeltype = AngelType::find($shift_entry['TID']); - $result = Db::insert( - ' - INSERT INTO `ShiftEntry` ( - `SID`, - `TID`, - `UID`, - `Comment`, - `freeload_comment`, - `freeloaded` - ) - VALUES(?, ?, ?, ?, ?, ?) - ', - [ - $shift_entry['SID'], - $shift_entry['TID'], - $shift_entry['UID'], - $shift_entry['Comment'], - $shift_entry['freeload_comment'], - (int) $shift_entry['freeloaded'], - ] - ); + $shift = $shiftEntry->shift; engelsystem_log( - 'User ' . User_Nick_render($user, true) - . ' signed up for shift ' . $shift->title - . ' (' . $shifttype->name . ')' - . ' at ' . $room->name + 'User ' . User_Nick_render($shiftEntry->user, true) + . ' signed up for shift ' . $shiftEntry->shift->title + . ' (' . $shift->shiftType->name . ')' + . ' at ' . $shift->room->name . ' from ' . $shift->start->format('Y-m-d H:i') . ' to ' . $shift->end->format('Y-m-d H:i') - . ' as ' . $angeltype->name + . ' as ' . $shiftEntry->angelType->name ); - mail_shift_assign($user, $shift); - - return $result; -} - -/** - * Update a shift entry. - * - * @param array $shift_entry - */ -function ShiftEntry_update($shift_entry) -{ - Db::update( - ' - UPDATE `ShiftEntry` - SET - `Comment` = ?, - `freeload_comment` = ?, - `freeloaded` = ? - WHERE `id` = ? - ', - [ - $shift_entry['Comment'], - $shift_entry['freeload_comment'], - (int) $shift_entry['freeloaded'], - $shift_entry['id'] - ] - ); -} - -/** - * Get a shift entry. - * - * @param int $shift_entry_id - * @return array|null - */ -function ShiftEntry($shift_entry_id) -{ - $shiftEntry = Db::selectOne('SELECT * FROM `ShiftEntry` WHERE `id` = ?', [$shift_entry_id]); - - return empty($shiftEntry) ? null : $shiftEntry; + mail_shift_assign($shiftEntry->user, $shift); } /** * Delete a shift entry. * - * @param array $shiftEntry + * @param ShiftEntry $shiftEntry */ -function ShiftEntry_delete($shiftEntry) +function ShiftEntry_onDelete(ShiftEntry $shiftEntry) { - Db::delete('DELETE FROM `ShiftEntry` WHERE `id` = ?', [$shiftEntry['id']]); - - $signout_user = User::find($shiftEntry['UID']); - $shift = Shift($shiftEntry['SID']); + $signout_user = $shiftEntry->user; + $shift = Shift($shiftEntry->shift); $shifttype = $shift->shiftType; $room = $shift->room; - $angeltype = AngelType::find($shiftEntry['TID']); + $angeltype = $shiftEntry->angelType; engelsystem_log( 'Shift signout: ' . User_Nick_render($signout_user, true) @@ -159,100 +46,44 @@ function ShiftEntry_delete($shiftEntry) . ' as ' . $angeltype->name ); - mail_shift_removed(User::find($shiftEntry['UID']), $shift); + mail_shift_removed($signout_user, $shift); } /** * Returns next (or current) shifts of given user. * * @param User $user - * @return array + * @return ShiftEntry[]|Collection */ function ShiftEntries_upcoming_for_user(User $user) { - return Db::select( - ' - SELECT *, shifts.id as shift_id - FROM `ShiftEntry` - JOIN `shifts` ON (`shifts`.`id` = `ShiftEntry`.`SID`) - JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id` - WHERE `ShiftEntry`.`UID` = ? - AND `shifts`.`end` > NOW() - ORDER BY `shifts`.`end` - ', - [ - $user->id - ] - ); + return $user->shiftEntries() + ->with(['shift', 'shift.shiftType']) + ->join('shifts', 'shift_entries.shift_id', 'shifts.id') + ->where('shifts.end', '>', Carbon::now()) + ->orderBy('shifts.end') + ->get(); } /** * Returns shifts completed by the given user. * - * @param User $user + * @param User $user * @param Carbon|null $sinceTime - * @return array + * @return ShiftEntry[]|Collection */ function ShiftEntries_finished_by_user(User $user, Carbon $sinceTime = null) { - return Db::select( - ' - SELECT * - FROM `ShiftEntry` - JOIN `shifts` ON (`shifts`.`id` = `ShiftEntry`.`SID`) - JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id` - WHERE `ShiftEntry`.`UID` = ? - AND `shifts`.`end` < NOW() - AND `ShiftEntry`.`freeloaded` = 0 - ' . ($sinceTime ? 'AND shifts.start >= "' . $sinceTime->toString() . '"' : '') . ' - ORDER BY `shifts`.`end` desc - ', - [ - $user->id, - ] - ); -} + $query = $user->shiftEntries() + ->with(['shift', 'shift.shiftType']) + ->join('shifts', 'shift_entries.shift_id', 'shifts.id') + ->where('shifts.end', '<', Carbon::now()) + ->where('freeloaded', false) + ->orderByDesc('shifts.end'); -/** - * Returns all shift entries in given shift for given angeltype. - * - * @param int $shift_id - * @param int $angeltype_id - * @return array - */ -function ShiftEntries_by_shift_and_angeltype($shift_id, $angeltype_id) -{ - return Db::select( - ' - SELECT * - FROM `ShiftEntry` - WHERE `SID` = ? - AND `TID` = ? - ', - [ - $shift_id, - $angeltype_id, - ] - ); -} + if ($sinceTime) { + $query = $query->where('shifts.start', '>=', $sinceTime); + } -/** - * Returns all freeloaded shifts for given user. - * - * @param int $userId - * @return array - */ -function ShiftEntries_freeloaded_by_user($userId) -{ - return Db::select( - ' - SELECT * - FROM `ShiftEntry` - WHERE `freeloaded` = 1 - AND `UID` = ? - ', - [ - $userId - ] - ); + return $query->get(); } diff --git a/includes/model/Shifts_model.php b/includes/model/Shifts_model.php index 185f3633..5bdcd136 100644 --- a/includes/model/Shifts_model.php +++ b/includes/model/Shifts_model.php @@ -4,11 +4,13 @@ use Engelsystem\Database\Db; use Engelsystem\Helpers\Carbon; use Engelsystem\Models\AngelType; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\User; use Engelsystem\Models\UserAngelType; use Engelsystem\ShiftsFilter; use Engelsystem\ShiftSignupState; -use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Collection as SupportCollection; /** * @param AngelType $angeltype @@ -57,7 +59,7 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null) LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id WHERE (`end` > ? AND `start` < ?) AND (SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`shifts`.`id`' . ($filter ? ' AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') - > (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`shifts`.`id` AND `freeloaded`=0' . ($filter ? ' AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') + > (SELECT COUNT(*) FROM `shift_entries` WHERE `shift_entries`.`shift_id`=`shifts`.`id` AND shift_entries.`freeloaded`=0' . ($filter ? ' AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') AND s.shift_id IS NULL ' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . ' @@ -68,7 +70,7 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null) LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id WHERE (`end` > ? AND `start` < ?) AND (SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`room_id`=`shifts`.`room_id`' . ($filter ? ' AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') - > (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`shifts`.`id` AND `freeloaded`=0' . ($filter ? ' AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') + > (SELECT COUNT(*) FROM `shift_entries` WHERE `shift_entries`.`shift_id`=`shifts`.`id` AND `freeloaded`=0' . ($filter ? ' AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') AND NOT s.shift_id IS NULL ' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . ' ) AS `tmp` @@ -246,41 +248,21 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty /** * @param ShiftsFilter $shiftsFilter - * @return array[] + * @return ShiftEntry[]|Collection */ function ShiftEntries_by_ShiftsFilter(ShiftsFilter $shiftsFilter) { - $sql = sprintf( - ' - SELECT - users.*, - `ShiftEntry`.`UID`, - `ShiftEntry`.`TID`, - `ShiftEntry`.`SID`, - `ShiftEntry`.`Comment`, - `ShiftEntry`.`freeloaded` - FROM `shifts` - JOIN `ShiftEntry` ON `ShiftEntry`.`SID`=`shifts`.`id` - JOIN `users` ON `ShiftEntry`.`UID`=`users`.`id` - WHERE `shifts`.`room_id` IN (%s) - AND `start` BETWEEN ? AND ? - ORDER BY `shifts`.`start` - ', - implode(',', $shiftsFilter->getRooms()) - ); - return Db::select( - $sql, - [ - $shiftsFilter->getStart(), - $shiftsFilter->getEnd(), - ] - ); + return ShiftEntry::with('user') + ->join('shifts', 'shifts.id', 'shift_entries.shift_id') + ->whereIn('shifts.room_id', $shiftsFilter->getRooms()) + ->whereBetween('start', [$shiftsFilter->getStart(), $shiftsFilter->getEnd()]) + ->get(); } /** * Check if a shift collides with other shifts (in time). * - * @param Shift $shift + * @param Shift $shift * @param Shift[]|Collection $shifts * @return bool */ @@ -290,8 +272,8 @@ function Shift_collides(Shift $shift, $shifts) if ($shift->id != $other_shift->id) { if ( !( - $shift->start->timestamp >= $other_shift->end->timestamp - || $shift->end->timestamp <= $other_shift->start->timestamp + $shift->start->timestamp >= $other_shift->end->timestamp + || $shift->end->timestamp <= $other_shift->start->timestamp ) ) { return true; @@ -304,15 +286,15 @@ function Shift_collides(Shift $shift, $shifts) /** * Returns the number of needed angels/free shift entries for an angeltype. * - * @param AngelType $needed_angeltype - * @param array[] $shift_entries + * @param AngelType $needed_angeltype + * @param ShiftEntry[]|Collection $shift_entries * @return int */ function Shift_free_entries(AngelType $needed_angeltype, $shift_entries) { $taken = 0; foreach ($shift_entries as $shift_entry) { - if ($shift_entry['freeloaded'] == 0) { + if (!$shift_entry->freeloaded) { $taken++; } } @@ -330,7 +312,7 @@ function Shift_free_entries(AngelType $needed_angeltype, $shift_entries) * @param array|null $user_angeltype * @param SHift[]|Collection|null $user_shifts List of the users shifts * @param AngelType $needed_angeltype - * @param array[] $shift_entries + * @param ShiftEntry[]|Collection $shift_entries * @return ShiftSignupState */ function Shift_signup_allowed_angel( @@ -410,8 +392,8 @@ function Shift_signup_allowed_angel( /** * Check if an angeltype supporter can sign up a user to a shift. * - * @param AngelType $needed_angeltype - * @param array[] $shift_entries + * @param AngelType $needed_angeltype + * @param ShiftEntry[]|Collection $shift_entries * @return ShiftSignupState */ function Shift_signup_allowed_angeltype_supporter(AngelType $needed_angeltype, $shift_entries) @@ -427,8 +409,8 @@ function Shift_signup_allowed_angeltype_supporter(AngelType $needed_angeltype, $ /** * Check if an admin can sign up a user to a shift. * - * @param AngelType $needed_angeltype - * @param array[] $shift_entries + * @param AngelType $needed_angeltype + * @param ShiftEntry[]|Collection $shift_entries * @return ShiftSignupState */ function Shift_signup_allowed_admin(AngelType $needed_angeltype, $shift_entries) @@ -484,7 +466,7 @@ function Shift_signout_allowed(Shift $shift, AngelType $angeltype, $signout_user * @param array|null $user_angeltype * @param Shift[]|Collection|null $user_shifts List of the users shifts * @param AngelType $needed_angeltype - * @param array[] $shift_entries + * @param ShiftEntry[]|Collection $shift_entries * @return ShiftSignupState */ function Shift_signup_allowed( @@ -522,10 +504,10 @@ function Shift_signup_allowed( * Return users shifts. * * @param int $userId - * @param bool $include_freeload_comments - * @return Collection|Shift[] + * @param bool $include_freeloaded_comments + * @return SupportCollection|Shift[] */ -function Shifts_by_user($userId, $include_freeload_comments = false) +function Shifts_by_user($userId, $include_freeloaded_comments = false) { $shiftsData = Db::select( ' @@ -534,19 +516,19 @@ function Shifts_by_user($userId, $include_freeload_comments = false) `rooms`.name AS Name, `shift_types`.`id` AS `shifttype_id`, `shift_types`.`name`, - `ShiftEntry`.`id` as shift_entry_id, - `ShiftEntry`.`SID`, - `ShiftEntry`.`TID`, - `ShiftEntry`.`UID`, - `ShiftEntry`.`freeloaded`, - `ShiftEntry`.`Comment`, - ' . ($include_freeload_comments ? '`ShiftEntry`.`freeload_comment`, ' : '') . ' + `shift_entries`.`id` as shift_entry_id, + `shift_entries`.`shift_id`, + `shift_entries`.`angel_type_id`, + `shift_entries`.`user_id`, + `shift_entries`.`freeloaded`, + `shift_entries`.`user_comment`, + ' . ($include_freeloaded_comments ? '`shift_entries`.`freeloaded_comment`, ' : '') . ' `shifts`.* - FROM `ShiftEntry` - JOIN `shifts` ON (`ShiftEntry`.`SID` = `shifts`.`id`) + FROM `shift_entries` + JOIN `shifts` ON (`shift_entries`.`shift_id` = `shifts`.`id`) JOIN `shift_types` ON (`shift_types`.`id` = `shifts`.`shift_type_id`) JOIN `rooms` ON (`shifts`.`room_id` = `rooms`.`id`) - WHERE ShiftEntry.`UID` = ? + WHERE shift_entries.`user_id` = ? ORDER BY `start` ', [ @@ -578,22 +560,14 @@ function Shift($shift) return null; } - $shift->shiftEntry = Db::select(' - SELECT - `ShiftEntry`.`id`, `ShiftEntry`.`TID` , `ShiftEntry`.`UID` , `ShiftEntry`.`freeloaded`, - `users`.`name` AS `username`, `users`.`id` AS `user_id` - FROM `ShiftEntry` - LEFT JOIN `users` ON (`users`.`id` = `ShiftEntry`.`UID`) - WHERE `SID`=?', [$shift->id]); - $neededAngels = []; $angelTypes = NeededAngelTypes_by_shift($shift->id); foreach ($angelTypes as $type) { $neededAngels[] = [ - 'TID' => $type['angel_type_id'], - 'count' => $type['count'], - 'restricted' => $type['restricted'], - 'taken' => $type['taken'] + 'angel_type_id' => $type['angel_type_id'], + 'count' => $type['count'], + 'restricted' => $type['restricted'], + 'taken' => $type['taken'] ]; } $shift->neededAngels = $neededAngels; diff --git a/includes/model/Stats.php b/includes/model/Stats.php index ec20725d..04168c64 100644 --- a/includes/model/Stats.php +++ b/includes/model/Stats.php @@ -16,10 +16,10 @@ function stats_currently_working(ShiftsFilter $filter = null) ' SELECT SUM(( SELECT COUNT(*) - FROM `ShiftEntry` - WHERE `ShiftEntry`.`SID`=`shifts`.`id` + FROM `shift_entries` + WHERE `shift_entries`.`shift_id`=`shifts`.`id` AND `freeloaded`=0 - ' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' + ' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' )) AS `count` FROM `shifts` WHERE (`end` >= NOW() AND `start` <= NOW()) @@ -89,12 +89,12 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null) AND `NeededAngelTypes`.`shift_id`=`shifts`.`id` ' . ($filter ? 'AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) - ( - SELECT COUNT(*) FROM `ShiftEntry` - JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID` + SELECT COUNT(*) FROM `shift_entries` + JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id` WHERE `angel_types`.`show_on_dashboard`=TRUE - AND `ShiftEntry`.`SID`=`shifts`.`id` + AND `shift_entries`.`shift_id`=`shifts`.`id` AND `freeloaded`=0 - ' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' + ' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) ) AS `count` @@ -116,12 +116,13 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null) AND `NeededAngelTypes`.`room_id`=`shifts`.`room_id` ' . ($filter ? 'AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) - ( - SELECT COUNT(*) FROM `ShiftEntry` - JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID` + SELECT COUNT(*) + FROM `shift_entries` + JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id` WHERE `angel_types`.`show_on_dashboard`=TRUE - AND `ShiftEntry`.`SID`=`shifts`.`id` + AND `shift_entries`.`shift_id`=`shifts`.`id` AND `freeloaded`=0 - ' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' + ' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) ) AS `count` @@ -168,12 +169,12 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null) AND `NeededAngelTypes`.`shift_id`=`shifts`.`id` ' . ($filter ? 'AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) - ( - SELECT COUNT(*) FROM `ShiftEntry` - JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID` + SELECT COUNT(*) FROM `shift_entries` + JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id` WHERE `angel_types`.`show_on_dashboard`=TRUE - AND `ShiftEntry`.`SID`=`shifts`.`id` - AND `freeloaded`=0 - ' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' + AND `shift_entries`.`shift_id`=`shifts`.`id` + AND shift_entries.`freeloaded`=0 + ' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) ) AS `count` @@ -195,12 +196,12 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null) AND `NeededAngelTypes`.`room_id`=`shifts`.`room_id` ' . ($filter ? 'AND angel_types.id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) - ( - SELECT COUNT(*) FROM `ShiftEntry` - JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID` + SELECT COUNT(*) FROM `shift_entries` + JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id` WHERE `angel_types`.`show_on_dashboard`=TRUE - AND `ShiftEntry`.`SID`=`shifts`.`id` + AND `shift_entries`.`shift_id`=`shifts`.`id` AND `freeloaded`=0 - ' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' + ' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ) ) AS `count` diff --git a/includes/model/User_model.php b/includes/model/User_model.php index 1c500fb7..fd907a6b 100644 --- a/includes/model/User_model.php +++ b/includes/model/User_model.php @@ -25,8 +25,8 @@ function User_tshirt_score($userId) $shift_sum_formula = User_get_shifts_sum_query(); $result_shifts = Db::selectOne(sprintf(' SELECT ROUND((%s) / 3600, 2) AS `tshirt_score` - FROM `users` LEFT JOIN `ShiftEntry` ON `users`.`id` = `ShiftEntry`.`UID` - LEFT JOIN `shifts` ON `ShiftEntry`.`SID` = `shifts`.`id` + FROM `users` LEFT JOIN `shift_entries` ON `users`.`id` = `shift_entries`.`user_id` + LEFT JOIN `shifts` ON `shift_entries`.`shift_id` = `shifts`.`id` WHERE `users`.`id` = ? AND `shifts`.`end` < NOW() GROUP BY `users`.`id` @@ -45,17 +45,6 @@ function User_tshirt_score($userId) return $result_shifts['tshirt_score'] + $worklogHours; } -/** - * Returns true if user is freeloader - * - * @param User $user - * @return bool - */ -function User_is_freeloader($user) -{ - return count(ShiftEntries_freeloaded_by_user($user->id)) >= config('max_freeloadable_shifts'); -} - /** * Returns all users that are not member of given angeltype. * @@ -196,15 +185,15 @@ function User_get_eligable_voucher_count($user) ? Carbon::createFromFormat('Y-m-d', $voucher_settings['voucher_start'])->setTime(0, 0) : null; - $shifts = ShiftEntries_finished_by_user($user, $start); + $shiftEntries = ShiftEntries_finished_by_user($user, $start); $worklog = UserWorkLogsForUser($user->id, $start); $shifts_done = - count($shifts) + count($shiftEntries) + $worklog->count(); $shiftsTime = 0; - foreach ($shifts as $shift) { - $shiftsTime += (Carbon::make($shift['end'])->timestamp - Carbon::make($shift['start'])->timestamp) / 60 / 60; + foreach ($shiftEntries as $shiftEntry) { + $shiftsTime += ($shiftEntry->shift->end->timestamp - $shiftEntry->shift->start->timestamp) / 60 / 60; } foreach ($worklog as $entry) { $shiftsTime += $entry->hours; @@ -248,7 +237,7 @@ function User_get_shifts_sum_query() OR (HOUR(shifts.start) <= %1$d AND HOUR(shifts.end) >= %2$d) )) * (UNIX_TIMESTAMP(shifts.end) - UNIX_TIMESTAMP(shifts.start)) - * (1 - (%3$d + 1) * `ShiftEntry`.`freeloaded`) + * (1 - (%3$d + 1) * `shift_entries`.`freeloaded`) ), 0) ', $nightShifts['start'], diff --git a/includes/pages/admin_active.php b/includes/pages/admin_active.php index 5fed6dd7..73105b74 100644 --- a/includes/pages/admin_active.php +++ b/includes/pages/admin_active.php @@ -60,7 +60,7 @@ function admin_active() sprintf( ' users.*, - COUNT(ShiftEntry.id) AS shift_count, + COUNT(shift_entries.id) AS shift_count, (%s + ( SELECT COALESCE(SUM(`hours`) * 3600, 0) FROM `worklogs` WHERE `user_id`=`users`.`id` @@ -70,8 +70,8 @@ function admin_active() $shift_sum_formula ) ) - ->leftJoin('ShiftEntry', 'users.id', '=', 'ShiftEntry.UID') - ->leftJoin('shifts', 'ShiftEntry.SID', '=', 'shifts.id') + ->leftJoin('shift_entries', 'users.id', '=', 'shift_entries.user_id') + ->leftJoin('shifts', 'shift_entries.shift_id', '=', 'shifts.id') ->leftJoin('users_state', 'users.id', '=', 'users_state.user_id') ->where('users_state.arrived', '=', true) ->groupBy('users.id') @@ -152,7 +152,7 @@ function admin_active() sprintf( ' users.*, - COUNT(ShiftEntry.id) AS shift_count, + COUNT(shift_entries.id) AS shift_count, (%s + ( SELECT COALESCE(SUM(`hours`) * 3600, 0) FROM `worklogs` WHERE `user_id`=`users`.`id` @@ -162,10 +162,10 @@ function admin_active() $shift_sum_formula ) ) - ->leftJoin('ShiftEntry', 'users.id', '=', 'ShiftEntry.UID') + ->leftJoin('shift_entries', 'users.id', '=', 'shift_entries.user_id') ->leftJoin('shifts', function ($join) use ($show_all_shifts) { /** @var JoinClause $join */ - $join->on('ShiftEntry.SID', '=', 'shifts.id'); + $join->on('shift_entries.shift_id', '=', 'shifts.id'); if (!$show_all_shifts) { $join->where(function ($query) { /** @var Builder $query */ diff --git a/includes/pages/admin_free.php b/includes/pages/admin_free.php index 0287ec1e..44388b4d 100644 --- a/includes/pages/admin_free.php +++ b/includes/pages/admin_free.php @@ -40,11 +40,11 @@ function admin_free() if ($request->has('submit')) { $query = User::with('personalData') ->select('users.*') - ->leftJoin('ShiftEntry', 'users.id', 'ShiftEntry.UID') + ->leftJoin('shift_entries', 'users.id', 'shift_entries.user_id') ->leftJoin('users_state', 'users.id', 'users_state.user_id') ->leftJoin('shifts', function ($join) { /** @var JoinClause $join */ - $join->on('ShiftEntry.SID', '=', 'shifts.id') + $join->on('shift_entries.shift_id', '=', 'shifts.id') ->where('shifts.start', '<', Carbon::now()) ->where('shifts.end', '>', Carbon::now()); }) diff --git a/includes/pages/admin_rooms.php b/includes/pages/admin_rooms.php index 0874d363..e52fb915 100644 --- a/includes/pages/admin_rooms.php +++ b/includes/pages/admin_rooms.php @@ -189,15 +189,14 @@ function admin_rooms() $shifts = $room->shifts; foreach ($shifts as $shift) { $shift = Shift($shift); - foreach ($shift->shiftEntry as $entry) { - $type = AngelType::find($entry['TID']); + foreach ($shift->shiftEntries as $entry) { event('shift.entry.deleting', [ 'user' => User::find($entry['user_id']), 'start' => $shift->start, 'end' => $shift->end, 'name' => $shift->shiftType->name, 'title' => $shift->title, - 'type' => $type->name, + 'type' => $entry->angelType->name, 'room' => $room, 'freeloaded' => (bool) $entry['freeloaded'], ]); diff --git a/includes/pages/admin_shifts.php b/includes/pages/admin_shifts.php index ba24b9dc..7cfd3000 100644 --- a/includes/pages/admin_shifts.php +++ b/includes/pages/admin_shifts.php @@ -563,17 +563,16 @@ function admin_shifts_history(): string foreach ($shifts as $shift) { $shift = Shift($shift); - foreach ($shift->shiftEntry as $entry) { - $type = AngelType::find($entry['TID']); + foreach ($shift->shiftEntries as $entry) { event('shift.entry.deleting', [ - 'user' => User::find($entry['user_id']), + 'user' => $entry->user, 'start' => $shift->start, 'end' => $shift->end, 'name' => $shift->shiftType->name, 'title' => $shift->title, - 'type' => $type->name, + 'type' => $entry->angelType->name, 'room' => $shift->room, - 'freeloaded' => (bool) $entry['freeloaded'], + 'freeloaded' => $entry->freeloaded, ]); } diff --git a/includes/pages/schedule/ImportSchedule.php b/includes/pages/schedule/ImportSchedule.php index 3538e011..c0fb4158 100644 --- a/includes/pages/schedule/ImportSchedule.php +++ b/includes/pages/schedule/ImportSchedule.php @@ -266,15 +266,15 @@ class ImportSchedule extends BaseController protected function fireDeleteShiftEntryEvents(Event $event): void { $shiftEntries = $this->db - ->table('ShiftEntry') + ->table('shift_entries') ->select([ 'shift_types.name', 'shifts.title', 'angel_types.name AS type', 'rooms.id AS room_id', - 'shifts.start', 'shifts.end', 'ShiftEntry.UID as user_id', 'ShiftEntry.freeloaded' + 'shifts.start', 'shifts.end', 'shift_entries.user_id', 'shift_entries.freeloaded' ]) - ->join('shifts', 'shifts.id', 'ShiftEntry.SID') + ->join('shifts', 'shifts.id', 'shift_entries.shift_id') ->join('schedule_shift', 'shifts.id', 'schedule_shift.shift_id') ->join('rooms', 'rooms.id', 'shifts.room_id') - ->join('angel_types', 'angel_types.id', 'ShiftEntry.TID') + ->join('angel_types', 'angel_types.id', 'shift_entries.angel_type_id') ->join('shift_types', 'shift_types.id', 'shifts.shift_type_id') ->where('schedule_shift.guid', $event->getGuid()) ->get(); diff --git a/includes/pages/user_ical.php b/includes/pages/user_ical.php index 1ff7e86c..132abe76 100644 --- a/includes/pages/user_ical.php +++ b/includes/pages/user_ical.php @@ -60,8 +60,8 @@ function make_ical_entry_from_shift(Shift $shift) $output .= 'UID:' . md5($shift->start->timestamp . $shift->end->timestamp . $shift->shiftType->name) . "\r\n"; $output .= 'SUMMARY:' . str_replace("\n", "\\n", $shift->shiftType->name) . ' (' . str_replace("\n", "\\n", $shift->title) . ")\r\n"; - if (isset($shift->Comment)) { - $output .= 'DESCRIPTION:' . str_replace("\n", "\\n", $shift->Comment) . "\r\n"; + if (isset($shift->user_comment)) { + $output .= 'DESCRIPTION:' . str_replace("\n", "\\n", $shift->user_comment) . "\r\n"; } $output .= 'DTSTAMP:' . $shift->start->utc()->format('Ymd\THis\Z') . "\r\n"; $output .= 'DTSTART:' . $shift->start->utc()->format('Ymd\THis\Z') . "\r\n"; diff --git a/includes/pages/user_myshifts.php b/includes/pages/user_myshifts.php index 7cd89e0f..68397f28 100644 --- a/includes/pages/user_myshifts.php +++ b/includes/pages/user_myshifts.php @@ -1,7 +1,6 @@ has('edit') && preg_match('/^\d+$/', $request->input('edit'))) { $shift_entry_id = $request->input('edit'); - $shift = Db::selectOne( - ' - SELECT - `ShiftEntry`.`freeloaded`, - `ShiftEntry`.`freeload_comment`, - `ShiftEntry`.`Comment`, - `ShiftEntry`.`UID`, - `shift_types`.`name`, - `shifts`.*, - `angel_types`.`name` AS `angel_type` - FROM `ShiftEntry` - JOIN `angel_types` ON (`ShiftEntry`.`TID` = `angel_types`.`id`) - JOIN `shifts` ON (`ShiftEntry`.`SID` = `shifts`.`id`) - JOIN `shift_types` ON (`shift_types`.`id` = `shifts`.`shift_type_id`) - WHERE `ShiftEntry`.`id`=? - AND `UID`=? - LIMIT 1 - ', - [ - $shift_entry_id, - $shifts_user->id, - ] - ); - if (!empty($shift)) { - /** @var Shift $shift */ - $shift = (new Shift())->forceFill($shift); - - $freeloaded = $shift->freeloaded; - $freeload_comment = $shift->freeloaded_comment; + /** @var ShiftEntry $shiftEntry */ + $shiftEntry = ShiftEntry::where('id', $shift_entry_id) + ->where('user_id', $shifts_user->id) + ->with(['shift', 'shift.shiftType', 'shift.room', 'user']) + ->first(); + if (!empty($shiftEntry)) { + $shift = $shiftEntry->shift; + $freeloaded = $shiftEntry->freeloaded; + $freeloaded_comment = $shiftEntry->freeloaded_comment; if ($request->hasPostData('submit')) { $valid = true; if (auth()->can('user_shifts_admin')) { $freeloaded = $request->has('freeloaded'); - $freeload_comment = strip_request_item_nl('freeload_comment'); - if ($freeloaded && $freeload_comment == '') { + $freeloaded_comment = strip_request_item_nl('freeloaded_comment'); + if ($freeloaded && $freeloaded_comment == '') { $valid = false; error(__('Please enter a freeload comment!')); } } - $comment = $shift->Comment; - $user_source = User::find($shift->UID); + $comment = $shiftEntry->user_comment; + $user_source = $shiftEntry->user; if (auth()->user()->id == $user_source->id) { $comment = strip_request_item_nl('comment'); } if ($valid) { - ShiftEntry_update([ - 'id' => $shift_entry_id, - 'Comment' => $comment, - 'freeloaded' => $freeloaded, - 'freeload_comment' => $freeload_comment - ]); + $shiftEntry->user_comment = $comment; + $shiftEntry->freeloaded = $freeloaded; + $shiftEntry->freeloaded_comment = $freeloaded_comment; + $shiftEntry->save(); engelsystem_log( 'Updated ' . User_Nick_render($user_source, true) . '\'s shift ' @@ -110,7 +87,7 @@ function user_myshifts() . ' from ' . $shift->start->format('Y-m-d H:i') . ' to ' . $shift->end->format('Y-m-d H:i') . ' with comment ' . $comment - . '. Freeloaded: ' . ($freeloaded ? 'YES Comment: ' . $freeload_comment : 'NO') + . '. Freeloaded: ' . ($freeloaded ? 'YES Comment: ' . $freeloaded_comment : 'NO') ); success(__('Shift saved.')); throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id])); @@ -122,10 +99,10 @@ function user_myshifts() $shift->start->format('Y-m-d H:i') . ', ' . shift_length($shift), $shift->room->name, $shift->shiftType->name, - $shift->angel_type, - $shift->Comment, - $shift->freeloaded, - $shift->freeload_comment, + $shiftEntry->angelType->name, + $shiftEntry->user_comment, + $shiftEntry->freeloaded, + $shiftEntry->freeloaded_comment, auth()->can('user_shifts_admin') ); } else { diff --git a/includes/pages/user_shifts.php b/includes/pages/user_shifts.php index 990d2c0c..149922dd 100644 --- a/includes/pages/user_shifts.php +++ b/includes/pages/user_shifts.php @@ -31,7 +31,7 @@ function user_shifts() { $request = request(); - if (User_is_freeloader(auth()->user())) { + if (auth()->user()->isFreeloader()) { throw_redirect(page_link_to('user_myshifts')); } diff --git a/includes/view/ShiftCalendarRenderer.php b/includes/view/ShiftCalendarRenderer.php index b2937398..ff14f90d 100644 --- a/includes/view/ShiftCalendarRenderer.php +++ b/includes/view/ShiftCalendarRenderer.php @@ -3,6 +3,8 @@ namespace Engelsystem; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; +use Illuminate\Support\Collection; class ShiftCalendarRenderer { @@ -45,10 +47,10 @@ class ShiftCalendarRenderer /** * ShiftCalendarRenderer constructor. * - * @param Shift[] $shifts - * @param array[] $needed_angeltypes - * @param array[] $shift_entries - * @param ShiftsFilter $shiftsFilter + * @param Shift[] $shifts + * @param array[] $needed_angeltypes + * @param ShiftEntry[][]|Collection $shift_entries + * @param ShiftsFilter $shiftsFilter */ public function __construct($shifts, private $needed_angeltypes, private $shift_entries, ShiftsFilter $shiftsFilter) { @@ -204,7 +206,7 @@ class ShiftCalendarRenderer /** * Renders a tick/block for given time * - * @param int $time unix timestamp + * @param int $time unix timestamp * @param boolean $label Should time labels be generated? * @return string rendered tick html */ diff --git a/includes/view/ShiftCalendarShiftRenderer.php b/includes/view/ShiftCalendarShiftRenderer.php index ea8f1f63..9e5bfb1c 100644 --- a/includes/view/ShiftCalendarShiftRenderer.php +++ b/includes/view/ShiftCalendarShiftRenderer.php @@ -4,7 +4,9 @@ namespace Engelsystem; use Engelsystem\Models\AngelType; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\User; +use Illuminate\Support\Collection; use function theme_type; @@ -16,10 +18,10 @@ class ShiftCalendarShiftRenderer /** * Renders a shift * - * @param Shift $shift The shift to render - * @param array[] $needed_angeltypes - * @param array $shift_entries - * @param User $user The user who is viewing the shift calendar + * @param Shift $shift The shift to render + * @param array[] $needed_angeltypes + * @param ShiftEntry[]|Collection $shift_entries + * @param User $user The user who is viewing the shift calendar * @return array */ public function render(Shift $shift, $needed_angeltypes, $shift_entries, $user) @@ -80,10 +82,10 @@ class ShiftCalendarShiftRenderer } /** - * @param Shift $shift - * @param array[] $needed_angeltypes - * @param array[] $shift_entries - * @param User $user + * @param Shift $shift + * @param array[] $needed_angeltypes + * @param ShiftEntry[]|Collection $shift_entries + * @param User $user * @return array */ private function renderShiftNeededAngeltypes(Shift $shift, $needed_angeltypes, $shift_entries, $user) @@ -93,7 +95,7 @@ class ShiftCalendarShiftRenderer $shift_entries_filtered[$needed_angeltype['id']] = []; } foreach ($shift_entries as $shift_entry) { - $shift_entries_filtered[$shift_entry['TID']][] = $shift_entry; + $shift_entries_filtered[$shift_entry->angel_type_id][] = $shift_entry; } $html = ''; @@ -144,11 +146,11 @@ class ShiftCalendarShiftRenderer /** * Renders a list entry containing the needed angels for an angeltype * - * @param Shift $shift The shift which is rendered - * @param array[] $shift_entries - * @param array $angeltype The angeltype, containing information about needed angeltypes + * @param Shift $shift The shift which is rendered + * @param ShiftEntry[]|Collection $shift_entries + * @param array $angeltype The angeltype, containing information about needed angeltypes * and already signed up angels - * @param User $user The user who is viewing the shift calendar + * @param User $user The user who is viewing the shift calendar * @return array */ private function renderShiftNeededAngeltype(Shift $shift, $shift_entries, $angeltype, $user) @@ -156,8 +158,8 @@ class ShiftCalendarShiftRenderer $angeltype = (new AngelType())->forceFill($angeltype); $entry_list = []; foreach ($shift_entries as $entry) { - $class = $entry['freeloaded'] ? 'text-decoration-line-through' : ''; - $entry_list[] = '' . User_Nick_render($entry) . ''; + $class = $entry->freeloaded ? 'text-decoration-line-through' : ''; + $entry_list[] = '' . User_Nick_render($entry->user) . ''; } $shift_signup_state = Shift_signup_allowed( $user, diff --git a/includes/view/ShiftEntry_view.php b/includes/view/ShiftEntry_view.php index d84cf24c..1da3ce07 100644 --- a/includes/view/ShiftEntry_view.php +++ b/includes/view/ShiftEntry_view.php @@ -39,11 +39,11 @@ function ShiftEntry_delete_view_admin(Shift $shift, AngelType $angeltype, User $ * * @param Shift $shift * @param AngelType $angeltype - * @param int $signoff_user_id + * @param User $signoff_user * * @return string HTML */ -function ShiftEntry_delete_view(Shift $shift, AngelType $angeltype, $signoff_user_id) +function ShiftEntry_delete_view(Shift $shift, AngelType $angeltype, User $signoff_user) { return page_with_title(ShiftEntry_delete_title(), [ info(sprintf( @@ -56,7 +56,7 @@ function ShiftEntry_delete_view(Shift $shift, AngelType $angeltype, $signoff_use form([ buttons([ - button(user_link($signoff_user_id), icon('x-lg') . __('cancel')), + button(user_link($signoff_user->id), icon('x-lg') . __('cancel')), form_submit('delete', icon('trash') . __('delete'), 'btn-danger', false), ]), ]), @@ -180,7 +180,7 @@ function ShiftEntry_create_title() * @param string $type * @param string $comment * @param bool $freeloaded - * @param string $freeload_comment + * @param string $freeloaded_comment * @param bool $user_admin_shifts * @return string */ @@ -192,7 +192,7 @@ function ShiftEntry_edit_view( $type, $comment, $freeloaded, - $freeload_comment, + $freeloaded_comment, $user_admin_shifts = false ) { $freeload_form = []; @@ -200,9 +200,9 @@ function ShiftEntry_edit_view( $freeload_form = [ form_checkbox('freeloaded', __('Freeloaded'), $freeloaded), form_textarea( - 'freeload_comment', + 'freeloaded_comment', __('Freeload comment (Only for shift coordination):'), - $freeload_comment + $freeloaded_comment ) ]; } diff --git a/includes/view/Shifts_view.php b/includes/view/Shifts_view.php index d7b9d2ad..7da4bdc5 100644 --- a/includes/view/Shifts_view.php +++ b/includes/view/Shifts_view.php @@ -3,8 +3,8 @@ use Engelsystem\Models\AngelType; use Engelsystem\Models\Room; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftType; -use Engelsystem\Models\User\User; use Engelsystem\Models\UserAngelType; use Engelsystem\ShiftSignupState; use Illuminate\Support\Collection; @@ -75,8 +75,8 @@ function Shift_editor_info_render(Shift $shift) } /** - * @param Shift $shift - * @param AngelType $angeltype + * @param Shift $shift + * @param AngelType $angeltype * @return string */ function Shift_signup_button_render(Shift $shift, AngelType $angeltype) @@ -136,16 +136,16 @@ function Shift_view(Shift $shift, ShiftType $shifttype, Room $room, $angeltypes_ $needed_angels .= Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, $shift, $user_shift_admin); } - $shiftEntry = new Collection($shift->shiftEntry); - foreach ($shiftEntry->groupBy('TID') as $angelTypes) { + $shiftEntry = $shift->shiftEntries; + foreach ($shiftEntry->groupBy('angel_type_id') as $angelTypes) { /** @var Collection $angelTypes */ - $type = $angelTypes->first()['TID']; - if (!$neededAngels->where('TID', $type)->first()) { + $type = $angelTypes->first()['angel_type_id']; + if (!$neededAngels->where('angel_type_id', $type)->first()) { $needed_angels .= Shift_view_render_needed_angeltype([ - 'TID' => $type, - 'count' => 0, - 'restricted' => true, - 'taken' => $angelTypes->count(), + 'angel_type_id' => $type, + 'count' => 0, + 'restricted' => true, + 'taken' => $angelTypes->count(), ], $angeltypes, $shift, $user_shift_admin); } } @@ -213,7 +213,7 @@ function Shift_view(Shift $shift, ShiftType $shifttype, Room $room, $angeltypes_ */ function Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, Shift $shift, $user_shift_admin) { - $angeltype = $angeltypes[$needed_angeltype['TID']]; + $angeltype = $angeltypes[$needed_angeltype['angel_type_id']]; $angeltype_supporter = auth()->user()->isAngelTypeSupporter($angeltype) || auth()->can('admin_user_angeltypes'); @@ -242,8 +242,8 @@ function Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, Shif ); $angels = []; - foreach ($shift->shiftEntry as $shift_entry) { - if ($shift_entry['TID'] == $needed_angeltype['TID']) { + foreach ($shift->shiftEntries as $shift_entry) { + if ($shift_entry->angel_type_id == $needed_angeltype['angel_type_id']) { $angels[] = Shift_view_render_shift_entry($shift_entry, $user_shift_admin, $angeltype_supporter, $shift); } } @@ -255,30 +255,30 @@ function Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, Shif } /** - * @param array $shift_entry + * @param ShiftEntry $shift_entry * @param bool $user_shift_admin * @param bool $angeltype_supporter * @param Shift $shift * @return string */ -function Shift_view_render_shift_entry($shift_entry, $user_shift_admin, $angeltype_supporter, Shift $shift) +function Shift_view_render_shift_entry(ShiftEntry $shift_entry, $user_shift_admin, $angeltype_supporter, Shift $shift) { - $entry = User_Nick_render(User::find($shift_entry['UID'])); - if ($shift_entry['freeloaded']) { + $entry = User_Nick_render($shift_entry->user); + if ($shift_entry->freeloaded) { $entry = '' . $entry . ''; } - $isUser = $shift_entry['UID'] == auth()->user()->id; + $isUser = $shift_entry->user_id == auth()->user()->id; if ($user_shift_admin || $angeltype_supporter || $isUser) { $entry .= '
'; if ($user_shift_admin || $isUser) { $entry .= button_icon( - page_link_to('user_myshifts', ['edit' => $shift_entry['id'], 'id' => $shift_entry['UID']]), + page_link_to('user_myshifts', ['edit' => $shift_entry->id, 'id' => $shift_entry->user_id]), 'pencil', 'btn-sm' ); } - $angeltype = AngelType::find($shift_entry['TID']); - $disabled = Shift_signout_allowed($shift, $angeltype, $shift_entry['UID']) ? '' : ' btn-disabled'; + $angeltype = $shift_entry->angelType; + $disabled = Shift_signout_allowed($shift, $angeltype, $shift_entry->user_id) ? '' : ' btn-disabled'; $entry .= button_icon(shift_entry_delete_link($shift_entry), 'trash', 'btn-sm' . $disabled); $entry .= '
'; } diff --git a/includes/view/User_view.php b/includes/view/User_view.php index 9ddb4ab0..a5bbb5f8 100644 --- a/includes/view/User_view.php +++ b/includes/view/User_view.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Engelsystem\Models\AngelType; use Engelsystem\Models\Group; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\User; use Engelsystem\Models\Worklog; use Illuminate\Support\Collection; @@ -197,14 +198,15 @@ function User_shift_state_render($user) } $upcoming_shifts = ShiftEntries_upcoming_for_user($user); - if (empty($upcoming_shifts)) { + if ($upcoming_shifts->isEmpty()) { return '' . __('Free') . ''; } - $nextShift = array_shift($upcoming_shifts); + /** @var ShiftEntry $nextShiftEntry */ + $nextShiftEntry = $upcoming_shifts->first(); - $start = Carbon::make($nextShift['start']); - $end = Carbon::make($nextShift['end']); + $start = $nextShiftEntry->shift->start; + $end = $nextShiftEntry->shift->end; $startFormat = $start->format(__('Y-m-d H:i')); $endFormat = $end->format(__('Y-m-d H:i')); $startTimestamp = $start->timestamp; @@ -244,8 +246,9 @@ function User_last_shift_render($user) return ''; } - $lastShift = array_shift($last_shifts); - $end = Carbon::make($lastShift['end']); + /** @var ShiftEntry $lastShiftEntry */ + $lastShiftEntry = $last_shifts->first(); + $end = $lastShiftEntry->shift->end; return '' . __('Shift ended %c') @@ -307,7 +310,7 @@ function User_view_myshift(Shift $shift, $user_source, $its_me) ]; if ($its_me) { - $myshift['comment'] = $shift->Comment; + $myshift['comment'] = $shift->user_comment; } if ($shift->freeloaded) { @@ -316,7 +319,7 @@ function User_view_myshift(Shift $shift, $user_source, $its_me) . '

'; if (auth()->can('user_shifts_admin')) { $myshift['comment'] .= '
' - . '

' . __('Freeloaded') . ': ' . $shift->freeload_comment . '

'; + . '

' . __('Freeloaded') . ': ' . $shift->freeloaded_comment . '

'; } else { $myshift['comment'] .= '

' . __('Freeloaded') . '

'; } @@ -332,7 +335,8 @@ function User_view_myshift(Shift $shift, $user_source, $its_me) 'btn-sm' ); } - if (Shift_signout_allowed($shift, (new AngelType())->forceFill(['id' => $shift->TID]), $user_source->id)) { + + if (Shift_signout_allowed($shift, (new AngelType())->forceFill(['id' => $shift->angel_type_id]), $user_source->id)) { $myshift['actions'][] = button( shift_entry_delete_link($shift), icon('trash') . __('sign off'), @@ -517,7 +521,7 @@ function User_view( return page_with_title( ' ' . ( - (config('enable_pronoun') && $user_source->personalData->pronoun) + (config('enable_pronoun') && $user_source->personalData->pronoun) ? '' . htmlspecialchars($user_source->personalData->pronoun) . ' ' : '' ) @@ -554,7 +558,7 @@ function User_view( ), icon('valentine') . __('Vouchers') ) - : '', + : '', $admin_user_worklog_privilege ? button( url('/admin/user/' . $user_source->id . '/worklog'), icon('clock-history') . __('worklog.add') @@ -572,13 +576,13 @@ function User_view( icon('braces') . __('JSON Export') ) : '', ($its_me && ( - $auth->can('shifts_json_export') - || $auth->can('ical') - || $auth->can('atom') - )) ? button( - page_link_to('user_myshifts', ['reset' => 1]), - icon('arrow-repeat') . __('Reset API key') - ) : '' + $auth->can('shifts_json_export') + || $auth->can('ical') + || $auth->can('atom') + )) ? button( + page_link_to('user_myshifts', ['reset' => 1]), + icon('arrow-repeat') . __('Reset API key') + ) : '' ]) ]) ]), @@ -591,7 +595,7 @@ function User_view( . $user_source->contact->dect . '' ) - : '' , + : '', config('enable_mobile_show') && $user_source->contact->mobile ? $user_source->settings->mobile_show ? heading( @@ -600,15 +604,15 @@ function User_view( . $user_source->contact->mobile . '' ) - : '' - : '' , + : '' + : '', $auth->can('user_messages') ? heading( '' . icon('envelope') . '' ) - : '' , + : '', ]), User_view_state($admin_user_privilege, $freeloader, $user_source), User_angeltypes_render($user_angeltypes), @@ -794,8 +798,8 @@ function User_oauth_render(User $user) foreach ($user->oauth as $oauth) { $output[] = __( isset($config[$oauth->provider]['name']) - ? $config[$oauth->provider]['name'] - : Str::ucfirst($oauth->provider) + ? $config[$oauth->provider]['name'] + : Str::ucfirst($oauth->provider) ); } @@ -887,7 +891,7 @@ function render_user_departure_date_hint() */ function render_user_freeloader_hint() { - if (User_is_freeloader(auth()->user())) { + if (auth()->user()->isFreeloader()) { return sprintf( __('You freeloaded at least %s shifts. Shift signup is locked. Please go to heavens desk to be unlocked again.'), config('max_freeloadable_shifts') diff --git a/src/Controllers/Metrics/Stats.php b/src/Controllers/Metrics/Stats.php index 5ae326e0..9e4fbbb6 100644 --- a/src/Controllers/Metrics/Stats.php +++ b/src/Controllers/Metrics/Stats.php @@ -45,27 +45,25 @@ class Stats $query = State::whereArrived(true); if (!is_null($working)) { - // @codeCoverageIgnoreStart $query ->leftJoin('worklogs', 'worklogs.user_id', '=', 'users_state.user_id') - ->leftJoin('ShiftEntry', 'ShiftEntry.UID', '=', 'users_state.user_id') + ->leftJoin('shift_entries', 'shift_entries.user_id', '=', 'users_state.user_id') ->distinct(); $query->where(function ($query) use ($working): void { /** @var QueryBuilder $query */ if ($working) { $query - ->whereNotNull('ShiftEntry.SID') + ->whereNotNull('shift_entries.shift_id') ->orWhereNotNull('worklogs.hours'); return; } $query - ->whereNull('ShiftEntry.SID') + ->whereNull('shift_entries.shift_id') ->whereNull('worklogs.hours'); }); - // @codeCoverageIgnoreEnd } return $query->count('users_state.user_id'); @@ -104,18 +102,17 @@ class Stats * The number of currently working users * * @param bool|null $freeloaded - * @codeCoverageIgnore */ public function currentlyWorkingUsers(bool $freeloaded = null): int { $query = User::query() - ->join('ShiftEntry', 'ShiftEntry.UID', '=', 'users.id') - ->join('shifts', 'shifts.id', '=', 'ShiftEntry.SID') + ->join('shift_entries', 'shift_entries.user_id', '=', 'users.id') + ->join('shifts', 'shifts.id', '=', 'shift_entries.shift_id') ->where('shifts.start', '<=', Carbon::now()) ->where('shifts.end', '>', Carbon::now()); if (!is_null($freeloaded)) { - $query->where('ShiftEntry.freeloaded', '=', $freeloaded); + $query->where('shift_entries.freeloaded', '=', $freeloaded); } return $query->count(); @@ -202,13 +199,13 @@ class Stats * @param bool|null $done * @param bool|null $freeloaded * - * @codeCoverageIgnore + * @codeCoverageIgnore because it is only used in functions that use TIMESTAMPDIFF */ protected function workSecondsQuery(bool $done = null, bool $freeloaded = null): QueryBuilder { $query = $this - ->getQuery('ShiftEntry') - ->join('shifts', 'shifts.id', '=', 'ShiftEntry.SID'); + ->getQuery('shift_entries') + ->join('shifts', 'shifts.id', '=', 'shift_entries.shift_id'); if (!is_null($freeloaded)) { $query->where('freeloaded', '=', $freeloaded); @@ -227,13 +224,13 @@ class Stats * @param bool|null $done * @param bool|null $freeloaded * - * @codeCoverageIgnore + * @codeCoverageIgnore as TIMESTAMPDIFF is not implemented in SQLite */ public function workSeconds(bool $done = null, bool $freeloaded = null): int { $query = $this->workSecondsQuery($done, $freeloaded); - return (int) $query->sum($this->raw('end - start')); + return (int) $query->sum($this->raw('TIMESTAMPDIFF(MINUTE, start, end) * 60')); } /** @@ -242,22 +239,19 @@ class Stats * @param bool|null $done * @param bool|null $freeloaded * - * @codeCoverageIgnore + * @codeCoverageIgnore as TIMESTAMPDIFF is not implemented in SQLite */ public function workBuckets(array $buckets, bool $done = null, bool $freeloaded = null): array { return $this->getBuckets( $buckets, $this->workSecondsQuery($done, $freeloaded), - 'UID', - 'SUM(end - start)', - 'SUM(end - start)' + 'user_id', + 'SUM(TIMESTAMPDIFF(MINUTE, start, end) * 60)', + 'SUM(TIMESTAMPDIFF(MINUTE, start, end) * 60)' ); } - /** - * @codeCoverageIgnore As long as its only used for old tables - */ protected function getBuckets( array $buckets, BuilderContract $basicQuery, @@ -281,18 +275,12 @@ class Stats return $return; } - /** - * @codeCoverageIgnore - */ public function worklogSeconds(): int { return (int) Worklog::query() ->sum($this->raw('hours * 60 * 60')); } - /** - * @codeCoverageIgnore - */ public function worklogBuckets(array $buckets): array { return $this->getBuckets( diff --git a/src/Models/AngelType.php b/src/Models/AngelType.php index 085c1c15..a4543768 100644 --- a/src/Models/AngelType.php +++ b/src/Models/AngelType.php @@ -4,28 +4,31 @@ declare(strict_types=1); namespace Engelsystem\Models; +use Engelsystem\Models\Shifts\ShiftEntry; use Illuminate\Database\Eloquent\Builder; use Engelsystem\Models\User\User; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Query\Builder as QueryBuilder; /** - * @property int $id - * @property string $name - * @property string $description - * @property string $contact_name - * @property string $contact_dect - * @property string $contact_email - * @property boolean $restricted # If users need an introduction - * @property boolean $requires_driver_license # If users must have a driver license - * @property boolean $no_self_signup # Users can sign up for shifts - * @property boolean $show_on_dashboard # Show on public dashboard - * @property boolean $hide_register # Hide from registration page + * @property int $id + * @property string $name + * @property string $description + * @property string $contact_name + * @property string $contact_dect + * @property string $contact_email + * @property boolean $restricted # If users need an introduction + * @property boolean $requires_driver_license # If users must have a driver license + * @property boolean $no_self_signup # Users can sign up for shifts + * @property boolean $show_on_dashboard # Show on public dashboard + * @property boolean $hide_register # Hide from registration page * - * @property-read Collection|User[] $userAngelTypes - * @property-read UserAngelType $pivot + * @property-read UserAngelType $pivot + * @property-read Collection|ShiftEntry[] $shiftEntries + * @property-read Collection|User[] $userAngelTypes * * @method static QueryBuilder|AngelType[] whereId($value) * @method static QueryBuilder|AngelType[] whereName($value) @@ -68,6 +71,11 @@ class AngelType extends BaseModel 'hide_register' => 'boolean', ]; + public function shiftEntries(): HasMany + { + return $this->hasMany(ShiftEntry::class); + } + public function userAngelTypes(): BelongsToMany { return $this diff --git a/src/Models/Shifts/Shift.php b/src/Models/Shifts/Shift.php index 9e8af282..21cdb0ac 100644 --- a/src/Models/Shifts/Shift.php +++ b/src/Models/Shifts/Shift.php @@ -8,31 +8,34 @@ use Carbon\Carbon; use Engelsystem\Models\BaseModel; use Engelsystem\Models\Room; use Engelsystem\Models\User\User; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Query\Builder as QueryBuilder; /** - * @property int $id - * @property string $title - * @property string $description - * @property string $url - * @property Carbon $start - * @property Carbon $end - * @property int $shift_type_id - * @property int $room_id - * @property string $transaction_id - * @property int $created_by - * @property int|null $updated_by - * @property Carbon|null $created_at - * @property Carbon|null $updated_at + * @property int $id + * @property string $title + * @property string $description + * @property string $url + * @property Carbon $start + * @property Carbon $end + * @property int $shift_type_id + * @property int $room_id + * @property string $transaction_id + * @property int $created_by + * @property int|null $updated_by + * @property Carbon|null $created_at + * @property Carbon|null $updated_at * - * @property-read QueryBuilder|Schedule $schedule - * @property-read QueryBuilder|ShiftType $shiftType - * @property-read QueryBuilder|Room $room - * @property-read QueryBuilder|User $createdBy - * @property-read QueryBuilder|User|null $updatedBy + * @property-read QueryBuilder|Schedule $schedule + * @property-read QueryBuilder|Collection|ShiftEntry[] $shiftEntries + * @property-read QueryBuilder|ShiftType $shiftType + * @property-read QueryBuilder|Room $room + * @property-read QueryBuilder|User $createdBy + * @property-read QueryBuilder|User|null $updatedBy * * @method static QueryBuilder|Shift[] whereId($value) * @method static QueryBuilder|Shift[] whereTitle($value) @@ -88,6 +91,11 @@ class Shift extends BaseModel return $this->hasOneThrough(Schedule::class, ScheduleShift::class, null, 'id', null, 'schedule_id'); } + public function shiftEntries(): HasMany + { + return $this->hasMany(ShiftEntry::class); + } + public function shiftType(): BelongsTo { return $this->belongsTo(ShiftType::class); diff --git a/src/Models/Shifts/ShiftEntry.php b/src/Models/Shifts/ShiftEntry.php new file mode 100644 index 00000000..5014bce1 --- /dev/null +++ b/src/Models/Shifts/ShiftEntry.php @@ -0,0 +1,63 @@ + */ + protected $fillable = [ // phpcs:ignore + 'shift_id', + 'angel_type_id', + 'user_id', + 'user_comment', + 'freeloaded', + 'freeloaded_comment', + ]; + + /** @var array default attributes */ + protected $attributes = [ // phpcs:ignore + 'user_comment' => '', + 'freeloaded' => false, + 'freeloaded_comment' => '', + ]; + + public function shift(): BelongsTo + { + return $this->belongsTo(Shift::class); + } + + public function angelType(): BelongsTo + { + return $this->belongsTo(AngelType::class); + } +} diff --git a/src/Models/User/User.php b/src/Models/User/User.php index 15fccde0..baa771b7 100644 --- a/src/Models/User/User.php +++ b/src/Models/User/User.php @@ -13,6 +13,7 @@ use Engelsystem\Models\OAuth; use Engelsystem\Models\Privilege; use Engelsystem\Models\Question; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\UserAngelType; use Engelsystem\Models\Worklog; use Illuminate\Database\Eloquent\Builder; @@ -47,6 +48,7 @@ use Illuminate\Support\Collection as SupportCollection; * @property-read SupportCollection|Privilege[] $privileges * @property-read Collection|AngelType[] $userAngelTypes * @property-read UserAngelType $pivot + * @property-read Collection|ShiftEntry[] $shiftEntries * @property-read Collection|Worklog[] $worklogs * @property-read Collection|Worklog[] $worklogsCreated * @property-read Collection|Question[] $questionsAsked @@ -109,6 +111,14 @@ class User extends BaseModel return $this->belongsToMany(Group::class, 'users_groups'); } + public function isFreeloader(): bool + { + return $this->shiftEntries() + ->where('freeloaded', true) + ->count() + >= config('max_freeloadable_shifts'); + } + public function license(): HasOne { return $this @@ -189,6 +199,11 @@ class User extends BaseModel return $this->hasMany(OAuth::class); } + public function shiftEntries(): HasMany + { + return $this->hasMany(ShiftEntry::class); + } + public function worklogs(): HasMany { return $this->hasMany(Worklog::class); diff --git a/src/Renderer/TwigServiceProvider.php b/src/Renderer/TwigServiceProvider.php index 354c2c70..3bd4b0ee 100644 --- a/src/Renderer/TwigServiceProvider.php +++ b/src/Renderer/TwigServiceProvider.php @@ -76,8 +76,10 @@ class TwigServiceProvider extends ServiceProvider $this->app->instance('twig.loader', $twigLoader); $cache = $this->app->get('path.cache.views'); + $twigDebug = false; if ($config->get('environment') == 'development') { $cache = false; + $twigDebug = true; } $twig = $this->app->make( @@ -86,7 +88,8 @@ class TwigServiceProvider extends ServiceProvider 'options' => [ 'cache' => $cache, 'auto_reload' => true, - 'strict_variables' => ($config->get('environment') == 'development'), + 'debug' => $twigDebug, + 'strict_variables' => $twigDebug, ], ] ); diff --git a/tests/Unit/Controllers/Metrics/StatsTest.php b/tests/Unit/Controllers/Metrics/StatsTest.php index 9696178f..049608f7 100644 --- a/tests/Unit/Controllers/Metrics/StatsTest.php +++ b/tests/Unit/Controllers/Metrics/StatsTest.php @@ -13,6 +13,7 @@ use Engelsystem\Models\OAuth; use Engelsystem\Models\Question; use Engelsystem\Models\Room; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\License; use Engelsystem\Models\User\PasswordReset; use Engelsystem\Models\User\PersonalData; @@ -163,6 +164,35 @@ class StatsTest extends TestCase $this->assertEquals(2.4 * 60 * 60 + 1.2 * 60 * 60, $seconds); } + /** + * @covers \Engelsystem\Controllers\Metrics\Stats::worklogBuckets + * @covers \Engelsystem\Controllers\Metrics\Stats::getBuckets + */ + public function testWorklogBuckets(): void + { + Worklog::factory()->create(['hours' => 1.2, 'worked_at' => Carbon::now()->subDay()]); + Worklog::factory()->create(['hours' => 1.9, 'worked_at' => Carbon::now()->subDay()]); + Worklog::factory()->create(['hours' => 3, 'worked_at' => Carbon::now()->subDay()]); + Worklog::factory()->create(['hours' => 10, 'worked_at' => Carbon::now()->subDay()]); + + $stats = new Stats($this->database); + $buckets = $stats->worklogBuckets([ + 1 * 60 * 60, + 2 * 60 * 60, + 3 * 60 * 60, + 4 * 60 * 60, + '+Inf' + ]); + + $this->assertEquals([ + 3600 => 0, + 7200 => 2, + 10800 => 3, + 14400 => 3, + '+Inf' => 4, + ], $buckets); + } + /** * @covers \Engelsystem\Controllers\Metrics\Stats::rooms */ @@ -251,9 +281,13 @@ class StatsTest extends TestCase public function testArrivedUsers(): void { $this->addUsers(); + ShiftEntry::factory()->create(['user_id' => 3]); + ShiftEntry::factory()->create(['user_id' => 4]); $stats = new Stats($this->database); $this->assertEquals(7, $stats->arrivedUsers()); + $this->assertEquals(5, $stats->arrivedUsers(false)); + $this->assertEquals(2, $stats->arrivedUsers(true)); } /** @@ -293,6 +327,25 @@ class StatsTest extends TestCase $this->assertEquals(1, $stats->email('news')); } + /** + * @covers \Engelsystem\Controllers\Metrics\Stats::currentlyWorkingUsers + */ + public function testCurrentlyWorkingUsers(): void + { + $this->addUsers(); + /** @var Shift $shift */ + $shift = Shift::factory()->create(['start' => Carbon::now()->subHour(), 'end' => Carbon::now()->addHour()]); + + ShiftEntry::factory()->create(['shift_id' => $shift->id]); + ShiftEntry::factory()->create(['shift_id' => $shift->id]); + ShiftEntry::factory()->create(['shift_id' => $shift->id, 'freeloaded' => true]); + + $stats = new Stats($this->database); + $this->assertEquals(3, $stats->currentlyWorkingUsers()); + $this->assertEquals(2, $stats->currentlyWorkingUsers(false)); + $this->assertEquals(1, $stats->currentlyWorkingUsers(true)); + } + /** * @covers \Engelsystem\Controllers\Metrics\Stats::faq */ diff --git a/tests/Unit/FactoriesTest.php b/tests/Unit/FactoriesTest.php index 01c18dda..4ea8c3f7 100644 --- a/tests/Unit/FactoriesTest.php +++ b/tests/Unit/FactoriesTest.php @@ -10,6 +10,7 @@ use Engelsystem\Models\Question; use Engelsystem\Models\Room; use Engelsystem\Models\Shifts\Schedule; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\Contact; use Engelsystem\Models\User\License; use Engelsystem\Models\User\PasswordReset; @@ -41,6 +42,7 @@ class FactoriesTest extends TestCase [Question::class], [Room::class], [Schedule::class], + [ShiftEntry::class], [Settings::class], [Shift::class], [State::class], diff --git a/tests/Unit/Models/AngelTypeTest.php b/tests/Unit/Models/AngelTypeTest.php index 7e3d43e6..d21ffcff 100644 --- a/tests/Unit/Models/AngelTypeTest.php +++ b/tests/Unit/Models/AngelTypeTest.php @@ -3,6 +3,7 @@ namespace Engelsystem\Test\Unit\Models; use Engelsystem\Models\AngelType; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\User; use Engelsystem\Models\UserAngelType; @@ -47,8 +48,7 @@ class AngelTypeTest extends ModelTest User::factory(1)->create(); $user2 = User::factory()->create(); - $angelType = new AngelType(['name' => 'Test']); - $angelType->save(); + $angelType = AngelType::create(['name' => 'Test']); $angelType->userAngelTypes()->attach($user1); $angelType->userAngelTypes()->attach($user2); @@ -61,6 +61,18 @@ class AngelTypeTest extends ModelTest $this->assertCount(2, $angeltypes); } + /** + * @covers \Engelsystem\Models\AngelType::shiftEntries + */ + public function testShiftEntries(): void + { + $angelType = AngelType::create(['name' => 'test type']); + + ShiftEntry::factory(3)->create(['angel_type_id' => $angelType->id]); + + $this->assertCount(3, $angelType->shiftEntries); + } + /** * @covers \Engelsystem\Models\AngelType::boot */ diff --git a/tests/Unit/Models/Shifts/ShiftEntryTest.php b/tests/Unit/Models/Shifts/ShiftEntryTest.php new file mode 100644 index 00000000..af47c44d --- /dev/null +++ b/tests/Unit/Models/Shifts/ShiftEntryTest.php @@ -0,0 +1,37 @@ +create(); + /** @var AngelType $angelType */ + $angelType = AngelType::factory()->create(); + /** @var User $user */ + $user = User::factory()->create(); + + $model = new ShiftEntry(); + $model->shift()->associate($shift); + $model->angelType()->associate($angelType); + $model->user()->associate($user); + $model->save(); + + $model = ShiftEntry::find(1); + $this->assertEquals($shift->id, $model->shift->id); + $this->assertEquals($angelType->id, $model->angelType->id); + $this->assertEquals($user->id, $model->user->id); + } +} diff --git a/tests/Unit/Models/Shifts/ShiftTest.php b/tests/Unit/Models/Shifts/ShiftTest.php index b74855d2..fae30feb 100644 --- a/tests/Unit/Models/Shifts/ShiftTest.php +++ b/tests/Unit/Models/Shifts/ShiftTest.php @@ -9,6 +9,7 @@ use Engelsystem\Models\Room; use Engelsystem\Models\Shifts\Schedule; use Engelsystem\Models\Shifts\ScheduleShift; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftType; use Engelsystem\Models\User\User; use Engelsystem\Test\Unit\Models\ModelTest; @@ -73,4 +74,18 @@ class ShiftTest extends ModelTest $this->assertEquals(1, Shift::find(2)->schedule->id); $this->assertEquals(1, Shift::find(3)->schedule->id); } + + /** + * @covers \Engelsystem\Models\Shifts\Shift::shiftEntries + */ + public function testShiftEntries(): void + { + /** @var Shift $shift */ + $shift = Shift::factory()->make(); + $shift->save(); + + ShiftEntry::factory(5)->create(['shift_id' => $shift->id]); + + $this->assertCount(5, $shift->shiftEntries); + } } diff --git a/tests/Unit/Models/User/UserTest.php b/tests/Unit/Models/User/UserTest.php index f4e38d51..a2679e8f 100644 --- a/tests/Unit/Models/User/UserTest.php +++ b/tests/Unit/Models/User/UserTest.php @@ -4,6 +4,7 @@ namespace Engelsystem\Test\Unit\Models\User; use Carbon\Carbon; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; +use Engelsystem\Config\Config; use Engelsystem\Models\AngelType; use Engelsystem\Models\BaseModel; use Engelsystem\Models\Group; @@ -13,6 +14,7 @@ use Engelsystem\Models\OAuth; use Engelsystem\Models\Privilege; use Engelsystem\Models\Question; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\User\Contact; use Engelsystem\Models\User\HasUserModel; use Engelsystem\Models\User\License; @@ -209,6 +211,30 @@ class UserTest extends ModelTest $this->assertEquals($relatedModelIds, $user->{$name}->modelKeys()); } + /** + * @covers \Engelsystem\Models\User\User::isFreeloader + */ + public function testIsFreeloader(): void + { + $this->app->instance('config', new Config([ + 'max_freeloadable_shifts' => 2, + ])); + + $user = new User($this->data); + $user->save(); + $this->assertFalse($user->isFreeloader()); + + ShiftEntry::factory()->create(['user_id' => $user->id]); + ShiftEntry::factory()->create(['user_id' => $user->id, 'freeloaded' => true]); + $this->assertFalse($user->isFreeloader()); + + ShiftEntry::factory()->create(['user_id' => $user->id, 'freeloaded' => true]); + $this->assertTrue($user->isFreeloader()); + + ShiftEntry::factory()->create(['user_id' => $user->id, 'freeloaded' => true]); + $this->assertTrue($user->isFreeloader()); + } + /** * @covers \Engelsystem\Models\User\User::userAngelTypes */ @@ -334,6 +360,19 @@ class UserTest extends ModelTest $this->assertCount(1, $oauth); } + /** + * @covers \Engelsystem\Models\User\User::shiftEntries + */ + public function testShiftEntries(): void + { + $user = new User($this->data); + $user->save(); + + ShiftEntry::factory(2)->create(['user_id' => $user->id]); + + $this->assertCount(2, $user->shiftEntries); + } + /** * @covers \Engelsystem\Models\User\User::worklogs */ diff --git a/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php b/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php index 188463a0..2d512daf 100644 --- a/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php +++ b/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php @@ -40,7 +40,7 @@ abstract class ExtensionTest extends TestCase */ protected function assertExtensionExists( string $name, - callable $callback, + mixed $callback, array $functions, array $options = [] ): void { diff --git a/tests/Unit/Renderer/TwigServiceProviderTest.php b/tests/Unit/Renderer/TwigServiceProviderTest.php index c05937ee..0b289128 100644 --- a/tests/Unit/Renderer/TwigServiceProviderTest.php +++ b/tests/Unit/Renderer/TwigServiceProviderTest.php @@ -136,7 +136,12 @@ class TwigServiceProviderTest extends ServiceProviderTest ->method('make') ->withConsecutive( [TwigLoader::class, ['paths' => $viewsPath]], - [Twig::class, ['options' => ['cache' => false, 'auto_reload' => true, 'strict_variables' => true]]], + [Twig::class, ['options' => [ + 'cache' => false, + 'auto_reload' => true, + 'strict_variables' => true, + 'debug' => true, + ]]], [TwigEngine::class] )->willReturnOnConsecutiveCalls( $twigLoader, @@ -162,10 +167,10 @@ class TwigServiceProviderTest extends ServiceProviderTest $this->setExpects($app, 'tag', ['renderer.twigEngine', ['renderer.engine']]); - $config->expects($this->exactly(3)) + $config->expects($this->exactly(2)) ->method('get') - ->withConsecutive(['environment'], ['environment'], ['timezone']) - ->willReturnOnConsecutiveCalls('development', 'development', 'The/World'); + ->withConsecutive(['environment'], ['timezone']) + ->willReturnOnConsecutiveCalls('development', 'The/World'); $twig->expects($this->once()) ->method('getExtension')