Compare commits

...

185 Commits

Author SHA1 Message Date
Igor Scheller 197d0d724a Shifts creation: Add reset to previous input 2023-12-26 20:47:05 +01:00
Igor Scheller 7888dfad78 Send notifications on news updates, make notifications optional 2023-12-26 20:36:02 +01:00
Igor Scheller 4429516a22 Added shift update event to send templated emails 2023-12-26 16:32:02 +01:00
Igor Scheller 4bbeb93d64 Implemented loading angels by shift type on schedule import 2023-12-26 16:32:02 +01:00
Igor Scheller 909f7bba5a Fix formatting... 2023-12-26 16:32:02 +01:00
Igor Scheller 2baa101a8f Random shit: Fix selection to exclude parallel shifts 2023-12-26 12:54:21 +01:00
Igor Scheller a3a36de985 Fixed formatting 2023-12-25 13:38:44 +01:00
Igor Scheller 4244acfb4d Header/Footer items config: Add configurable permissions 2023-12-25 13:32:49 +01:00
Xu 4fa99b8a04 Show "not marked as arrived" Notification only from set arrival date 2023-12-24 18:15:31 +01:00
Igor Scheller 14dbe7f5d9 Added random shift button to shifts view 2023-12-23 11:17:50 +01:00
Igor Scheller dc7c62ffe5 API: Added event / api info endpoint, simplified spec 2023-12-22 15:14:01 +01:00
Igor Scheller da8178b0bc API: Allow usage of self in user routes 2023-12-22 15:14:01 +01:00
Igor Scheller 02f998fc38 API: Added user info and shifts by user and angeltype, simplified neededAngelTypes 2023-12-22 15:14:01 +01:00
Igor Scheller 4de882ef85 API: Add API settings page 2023-12-22 15:14:01 +01:00
Igor Scheller fe836e281e API: Add openapi endpoint to view specification 2023-12-22 15:14:01 +01:00
Igor Scheller 8894f183f2 API: Use resource classes to serialize models 2023-12-22 15:14:01 +01:00
Igor Scheller 5b237febf8 API: Add /users/{id}/angeltypes, updated openapi formatting 2023-12-22 15:14:01 +01:00
Igor Scheller 497c1772f7 API: Rename rooms to locations and start/end to starts_at/ends_at 2023-12-22 15:14:01 +01:00
Igor Scheller 2e38b55167 API: Added API value tests 2023-12-22 15:14:01 +01:00
Igor Scheller 1a250dc250 API: Update index response codes and api descriptions 2023-12-22 15:14:01 +01:00
Igor Scheller ef3bd7c319 API: Show needed/added users by angeltype in shifts 2023-12-22 15:14:01 +01:00
Igor Scheller ea93e27a9d API: Add urls to response, configure required fields 2023-12-22 15:14:01 +01:00
Igor Scheller a5cebc8535 API: Add /angeltypes, /rooms, /rooms/{id}/shifts 2023-12-22 15:14:01 +01:00
Igor Scheller 72649c9522 API: Load api info from yaml 2023-12-22 15:14:01 +01:00
Igor Scheller b5d94971bc API: Split to multiple controllers, removed / from routes 2023-12-22 15:14:01 +01:00
Igor Scheller ca0a69b17d API: Wrapped lists in data objects, specified datetime format 2023-12-22 15:14:01 +01:00
Igor Scheller 1505d0229d API: Documented additional endpoints in OpenAPI 2023-12-22 15:14:01 +01:00
Igor Scheller e2e18db460 API: Moved json handling and route-api tagging to ApiRouteHandler 2023-12-22 15:14:01 +01:00
Igor Scheller 8adad075bf API: Init with news endpoint (ro) 2023-12-22 15:14:01 +01:00
Igor Scheller f826cee63c Move api key reset to authenticator, set api_key on registration 2023-12-22 15:14:01 +01:00
Igor Scheller 0dbf88ad1c Cleanup short api keys 2023-12-22 15:14:01 +01:00
Igor Scheller 8185a74edc Added user id to logs, implemented filter by user 2023-12-22 11:17:07 +01:00
Xu 162116998c edit user nick needs buerocrat 2023-12-21 14:01:40 +01:00
xuwhite ac73489aed
Icons (#1279)
* add icons to design page
* make icons more consistent
2023-12-21 13:08:29 +01:00
Xu f4c3f7a39e highlight freeloaded shifts in user myshifts view 2023-12-21 11:35:40 +01:00
Igor Scheller 23de3579af Set cookie secure attribute matching request 2023-12-15 11:13:30 +01:00
Igor Scheller ba4ba8f2f8 Lint & fix translations 2023-12-15 11:13:30 +01:00
Igor Scheller 8b1cd8130e Fix & cleanup page titles 2023-12-15 11:13:30 +01:00
xuwhite 48ea35562e
show user-info icon with admin_arrive permission 2023-12-14 22:15:17 +01:00
Xu a35a3580e0 highlight actual time in users shifts list 2023-12-14 22:09:28 +01:00
Igor Scheller 15a6ba1c52 Implemented schedule deletion 2023-12-13 22:18:20 +01:00
Xu d89fe01ddd shirt edit in edit user requires bureaucrat 2023-12-07 20:26:33 +01:00
Xu f292ce5331 Edit Shirt requires bureaucrat permission 2023-12-07 20:26:33 +01:00
Xu 269541293c password minimal length hint 2023-12-07 13:15:29 +01:00
Xu 545d2a7ccf fix email formatting 2023-12-07 12:59:56 +01:00
Xu dfd72d2d69 fix footer translations 2023-12-07 12:59:56 +01:00
Igor Scheller f2edb1a45c Merge tag 'v3.4.1' 2023-12-06 19:08:38 +01:00
Igor Scheller efda1ffc1c Escape text outputs in includes 2023-12-06 18:18:05 +01:00
dependabot[bot] b8095492ec Updated build dependencies 2023-12-06 14:51:20 +01:00
Igor Scheller 4e6ba3d684 Fix password reset e-mail wording in de translation 2023-12-01 12:03:10 +01:00
Igor Scheller 1b91d84b5f Added error page translations 2023-11-24 17:10:10 +01:00
Xu 47ad0a6133 fix translation keys in twig templates 2023-11-24 17:10:10 +01:00
Xu fec2f17bea fix date translations 2023-11-24 17:10:10 +01:00
xuwhite fd56966435
add user angeltype info in shift entries (#1262) 2023-11-24 15:11:36 +01:00
Xu d8310ed6e7 minor improvements on angeltypes 2023-11-24 14:44:55 +01:00
Xu 3ffb0a38e8 add user-info hint 2023-11-19 20:51:04 +01:00
Xu e9b8977728 add user info to user view 2023-11-19 20:51:04 +01:00
Igor Scheller 9acd06cb04 Feed export: Convert to string/int, updated docs 2023-11-19 19:04:13 +01:00
Igor Scheller b17dbf46e0 Route resolving: Show better error message when method is not callable 2023-11-19 19:04:13 +01:00
Igor Scheller 7f2f5ab7ed Templates: Update spacing handling 2023-11-19 19:04:13 +01:00
Igor Scheller 58c457be86 Fix choices dropdown search order and z-index to show on top of shifts table 2023-11-19 19:04:13 +01:00
Igor Scheller 36c7db40a7 Test all factories 2023-11-19 19:04:13 +01:00
Igor Scheller 0b165bc24c Recreated shift type admin backend 2023-11-19 18:55:06 +01:00
Igor Scheller bf83e6a300 Recreated shifts history page 2023-11-19 18:32:47 +01:00
Igor Scheller ac74ab489d Migration: Fix worklog times and schedule shift type 2023-11-19 18:18:26 +01:00
Igor Scheller f3347ba140 Migration: Trim api key length before down migration 2023-11-19 18:18:26 +01:00
Igor Scheller 39dbfabea7 Enabled foreign keys in sqlite tests, added missing fields to factories 2023-11-19 18:18:26 +01:00
Igor Scheller 0a0cf5265c Cleanup migrations: removed old install files 2023-11-19 18:18:26 +01:00
Igor Scheller 176a0b65c5 Decouple setting admin password from auth provider 2023-11-19 18:18:26 +01:00
Xu 7fda1fc14b unify T-shirt wording 2023-11-19 13:09:49 +01:00
Xu 66738298a9 clarify hint for nightshifts, simplify username regex, add missing translation
fix untranslated string
2023-11-19 13:09:49 +01:00
Xu 5d14109dbd unify e-mail wording 2023-11-19 13:09:49 +01:00
Xu 44efd910c6 Add day of event to date dropdowns, move task notice 2023-11-18 21:04:50 +01:00
Igor Scheller ecc3976c27 Show specific error message titles 2023-11-13 18:28:27 +01:00
Xu 93270a10fd Added more confirmation dialogs to delete forms 2023-11-13 18:28:27 +01:00
Igor Scheller ff179360cc Added confirmation dialog to delete forms 2023-11-13 18:28:27 +01:00
Igor Scheller 1b21bcf769 Added bootstrap modals 2023-11-13 18:28:27 +01:00
Xu b6bd3eba56 Replace page_link_to() with url() 2023-11-13 17:37:52 +01:00
Xu 6477e5dabd refactor driving license into the user certificate settings 2023-11-13 16:36:07 +01:00
Xu 24f91ce9b5 disable needed angels input if shift is imported 2023-11-13 01:09:31 +01:00
Xu 6022d792dc add hints for required profile settings 2023-11-12 18:46:19 +01:00
Igor Scheller 9e3adf6179 Rename user sign up to registration 2023-11-12 18:35:50 +01:00
Xu 6564056f16 Sign-up page wording fixed 2023-11-12 18:35:50 +01:00
Xu 343ce8241c wording on user page 2023-11-12 18:35:50 +01:00
Igor Scheller 7f6e1ff18e Rename rooms to locations 2023-11-06 20:39:01 +01:00
Igor Scheller a31534d9b7 Shifts feed: Added URL to shift and shifttype name & description 2023-11-03 18:45:30 +01:00
Xu 24204b1f3c add more buttons 2023-11-03 16:23:39 +01:00
xuwhite 27323bfba5
make required fields configurable 2023-11-03 15:15:44 +01:00
Xu 1397fe90ce creation of new entity's from overview pages (+ sign) 2023-11-03 13:32:39 +01:00
Xu 89321306bc back buttons on edit and deletion pages 2023-11-03 12:29:17 +01:00
Xu 185b7e3fb6 rename shirt size keys G to F 2023-11-03 12:09:17 +01:00
Igor Scheller 9ffe739b24 Fixed erroneous translation replacements 2023-10-20 23:31:14 +02:00
Xu 9fb6bd4d10 clear way more translations 2023-10-20 21:38:34 +02:00
Xu fe37258b35 clear even more translations 2023-10-20 21:38:34 +02:00
Xu 6195692d3d cleaning more translations 2023-10-20 21:38:34 +02:00
dependabot[bot] fc58d55274 Bump @babel/traverse from 7.20.13 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.13 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-19 17:44:51 +02:00
Igor Scheller 9fcee133eb Updated email_goody opt in text 2023-10-15 23:14:22 +02:00
Xu fed27210eb removing pt translation 2023-10-15 23:04:01 +02:00
Xu aeea3067b0 cleaning translations 2023-10-15 21:30:26 +02:00
Igor Scheller 38838352e2 Handle email send errors in Mailer class 2023-10-15 18:45:37 +02:00
Igor Scheller d251b4c7f7 Shifts: Fix sign up text when self sign up is disallowed 2023-10-15 18:44:11 +02:00
Igor Scheller 7dbc0481b9 Fix form buttons in btn-group 2023-10-15 18:37:27 +02:00
Igor Scheller 0aa4cdd2b0 Tests: Fix flaky NewsController tests 2023-10-15 18:21:41 +02:00
Igor Scheller 1d5f16a59e Tests: Fix flaky freeloader test 2023-10-15 18:21:41 +02:00
Igor Scheller fddae62669 Tests: Fix string length validation tests 2023-10-15 18:21:41 +02:00
Igor Scheller 599fff26d4 Tests: Fix --repeat in EventDispatcherTest 2023-10-15 18:21:41 +02:00
Igor Scheller cd8c01c080 Speedup password tests 2023-10-15 18:21:41 +02:00
Igor Scheller a70bc6ded8 Upgrade composer packages 2023-10-15 18:13:43 +02:00
Igor Scheller 7ce2cca052 Update composer packages 2023-10-15 18:13:43 +02:00
Igor Scheller cf4dc63495 Profile settings page: Add "back to my shifts" menu item and icons 2023-10-15 18:10:12 +02:00
Igor Scheller dc9441d925 SettingsControllerTest: Fix assertion order 2023-10-15 18:10:12 +02:00
Igor Scheller 8438b8dc51 Legacy logger: be less noisy on cli (no double logging and oneliners) 2023-10-15 18:10:12 +02:00
xuwhite 00f4afa2ab
rename rooms to locations (#1226) 2023-10-13 11:53:13 +02:00
Tobias Fiebig 9f113958ca
Update SECURITY.md
Included statement on the use of external reporting services / bug bounty services.
2023-10-13 10:16:17 +02:00
dependabot[bot] b3dd2b1d47 Bump postcss from 8.4.21 to 8.4.31
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-08 12:48:02 +02:00
xuwhite adf00b2739
add hide angeltype in shift view option for angeltypes (#1195) 2023-09-27 20:25:10 +02:00
xuwhite 8ebaffd71a
rename is_important to is_highlighted (#1215) 2023-09-27 18:15:12 +02:00
Xu 383f8ebde5 refactor no self signup to shift self signup 2023-09-26 18:02:23 +02:00
Igor Scheller 4cd7103121 Fix users page link 2023-09-24 22:50:44 +02:00
weeman 4267a76adb Redirect to sign-up if already logged in 2023-09-24 22:50:44 +02:00
msquare a2a57ec852 prefill fields from oauth 2023-09-24 22:50:44 +02:00
Igor Scheller 29a4b244dc Fixed some smaller error messages due to null values 2023-09-24 22:50:44 +02:00
Michael Weimann 4329ee4af9 Implement new sign up page 2023-09-24 22:50:44 +02:00
Tim Neumann c2dd25fc7c
Allow admins to remove entries from some config arrays (#1155)
This is done by setting the respective value to null
2023-09-24 21:42:44 +02:00
xuwhite 3b241529b7
Add event name and day of event to mail footer (#1214) 2023-09-24 15:10:10 +02:00
Igor Scheller 40b93e3d8b Sessions: Only show part of the session ID 2023-09-18 19:00:51 +02:00
Igor Scheller c06cb767da Delete all other sessions after setting a new password 2023-09-18 18:10:40 +02:00
Igor Scheller dbb089315f Delete all sessions on password reset 2023-09-18 18:10:40 +02:00
Igor Scheller 5c59fec1cf Add page to view and delete user sessions 2023-09-18 18:10:40 +02:00
Igor Scheller 102c8428c8 Save user id in Session model 2023-09-18 18:10:40 +02:00
Igor Scheller 67d5950926 Add Session model 2023-09-18 18:10:40 +02:00
Igor Scheller ee7d30b339 Schedule import: Show error message on schedule parsing errors 2023-09-18 17:27:15 +02:00
msquare 87bd4f4fa1
Create SECURITY.md 2023-09-12 12:39:38 +02:00
Igor Scheller 9a9ffcfdaf Hide arrival hint if signup does not require arrival 2023-09-02 18:26:14 +02:00
Xu f3ec62e121 update models with defaults 2023-08-30 20:25:13 +02:00
Xu 1ca9b99612 added tests for config ifsg_light_enabled 2023-08-30 20:04:32 +02:00
Xu 6b273288bd add config option for ifsg_light 2023-08-30 20:04:32 +02:00
Igor Scheller d1d0acf622 Fixed styling 2023-08-30 17:32:00 +02:00
Igor Scheller 68dd73e333 Shifts creation: Fix start/end date value after going back 2023-08-30 17:21:03 +02:00
Igor Scheller 94ba51bc46 Shifts creation: Fix start hours before 10:00 2023-08-30 17:21:03 +02:00
Igor Scheller 24ecea0d65 Shifts: Fix day marker on start of day 2023-08-29 20:11:37 +02:00
Igor Scheller 5e702cd177 News creation: Show error on duplicates 2023-08-29 20:11:37 +02:00
Igor Scheller f966b1521f Arrive search: Ignore datetime 2023-08-29 20:11:37 +02:00
Igor Scheller 2252819800 Shifts preview: added end year on hover and hours 2023-08-29 20:11:37 +02:00
Igor Scheller 931f3ba10d Sort shift types by name 2023-08-29 20:11:37 +02:00
Igor Scheller a60c5987ab Worklog: create log entry on successfull creation 2023-08-29 20:11:37 +02:00
Igor Scheller 80bec733bd Add table for ifsg to angeltype 2023-08-29 20:11:37 +02:00
msquare c63a671dc4 goodie manager needs to see shifts and worklogs from users 2023-08-18 16:42:16 +02:00
Igor Scheller 009b0f3f27 Fixed tests 2023-08-18 15:18:45 +02:00
Michael Weimann f4030b86af Add day of event in footer and on dashboard 2023-08-18 14:46:19 +02:00
Igor Scheller e0b552d18b Show angel name in tshirt view
resolves #1186
2023-08-17 20:13:46 +02:00
Jan-Philipp Litza 74989df119 Fix msgfmt error: keyword "settings" unknown 2023-08-17 15:22:00 +02:00
Xu b5803caf44 fix work log suggestion during the event 2023-08-16 20:54:35 +02:00
Michael Weimann e03f2936e7 Make news footer responsive 2023-08-15 22:06:34 +02:00
msquare 6c3bb7521f fixes #1181: fix misleading back link in admin edit worklog 2023-08-14 16:49:17 +02:00
Michael Weimann 85bc95fea9
Fix code style 2023-08-14 16:38:44 +02:00
msquare 0a3a3c3b56 make tests happy again 2023-08-14 16:30:14 +02:00
msquare df4f744f6d fixes #1180: meeting/news pagination 2023-08-14 16:02:11 +02:00
msquare e11b0db526 fixes #1157: secure cookies???ßßß 2023-08-14 11:11:27 +02:00
msquare 7f41d5eb1e fixes #1179: stop choices.js from escaping choices 2023-08-14 10:52:02 +02:00
hexchen 4a907600b7 Improve translation on admin_user page 2023-08-14 00:02:44 +02:00
msquare 47f0587cd9 fixes #1169: improve shift change hour sanitation 2023-08-13 16:52:41 +02:00
msquare cffc9854f8 fixes #1173: removes double escapes form input twig macro 2023-08-13 15:56:35 +02:00
msquare af2ac1bc3e fixes #1175: time lane scrolling out of screen 2023-08-13 15:31:51 +02:00
Igor Scheller 49300900d6 News: Catch all throwable exceptions 2023-08-13 13:47:37 +02:00
Michael Weimann 86da8758a4 Fix camp23 input group 2023-08-12 20:06:42 +02:00
msquare 3ae8424aea so much space... 2023-08-12 13:09:09 +02:00
msquare 98d2316b08 further improvements on hint link colors 2023-08-12 12:57:07 +02:00
msquare 6622680baf add ifsg tests 2023-08-12 12:19:24 +02:00
Xu 21423ef305 renaming ifsg 2023-08-12 12:19:24 +02:00
msquare c93c241dc9 make info+danger hint links readable again 2023-08-12 12:19:24 +02:00
Xu 4378fa2d7d fix tests after ifsg implementation 2023-08-12 12:19:24 +02:00
Xu 8dd4af1bb6 add requires ifsg certificate 2023-08-12 12:19:24 +02:00
Xu f345942e46 add ifsg certificates 2023-08-12 12:19:24 +02:00
Michael Weimann da2baa75bb Fix pre-code blocks 2023-08-03 19:37:36 +02:00
Michael Weimann 2c702fc67d Increase camp23 contrast 2023-08-03 19:37:36 +02:00
Igor Scheller d9b93e4236 News: Show updated date besides "updated" state 2023-07-30 18:57:49 +02:00
Igor Scheller 1c4c164c39 Fix shift sign up angel button not always displayed 2023-07-30 18:57:49 +02:00
Igor Scheller e407a3b780 OAuth: Fix error when user id is numeric 2023-07-30 18:57:49 +02:00
Igor Scheller 24f958b00d Fix 500 error on angeltypes edit page when logged out 2023-07-30 18:57:49 +02:00
msquare db4c5eec1c improme cccamp23 theme contrast 2023-07-26 20:25:28 +02:00
msquare 0a1c85d6bd cccamp23 theme improvements on badges 2023-07-24 18:49:32 +02:00
msquare c2e6dc5223 cccamp23 theme much prettier 2023-07-18 21:21:25 +02:00
msquare 19a5673231 cccamp23 theme 2023-07-18 21:02:31 +02:00
Michael Weimann 9feed46d4e Add TrimMiddleware to trim all request values 2023-07-10 12:59:02 +02:00
347 changed files with 15446 additions and 9418 deletions

View File

@ -155,6 +155,15 @@ yarn lint:
- apk add --no-cache git
- yarn lint
translations lint:
image: alpine
stage: prepare
before_script:
- apk add gettext
script:
- find resources/lang -type f -name '*.po' -exec sh -c 'msgfmt "${1%.*}.po" -o"${1%.*}.mo"' shell {} \;
- '[[ $(find resources/lang -type f -name "*.po" | wc -l) == $(find resources/lang -type f -name "*.mo" | wc -l) ]]'
#
# Build
#
@ -177,6 +186,7 @@ build-image:
- composer validate
- yarn check
- yarn lint
- translations lint
- generate-version
dependencies:
- generate-version

View File

@ -13,10 +13,14 @@ Please ensure that your pull requests follow the [PSR-12](https://www.php-fig.or
You can check that by running
```bash
composer run phpcs
# with docker
docker exec engelsystem_dev-es_workspace-1 composer run phpcs
```
You may auto fix reported issues by running
```bash
composer run phpcbf
# with docker
docker exec engelsystem_dev-es_workspace-1 composer run phpcbf
```
## Pre-commit hooks
@ -68,7 +72,7 @@ docker compose exec es_workspace yarn build
docker compose exec -e THEMES=0,1 es_workspace yarn build
# Update the translation files
docker compose exec es_workspace find /var/www/resources/lang -type f -name '*.po' -exec sh -c 'file="{}"; msgfmt "${file%.*}.po" -o "${file%.*}.mo"' \;
docker compose exec es_workspace find /var/www/resources/lang -type f -name '*.po' -exec sh -c 'msgfmt "${1%.*}.po" -o"${1%.*}.mo"' shell {} \;
# Run the migrations
docker compose exec es_workspace bin/migrate
@ -114,7 +118,7 @@ The following instructions explain how to get, build and run the latest Engelsys
```
* Generate translation files
```bash
find resources/lang/ -type f -name '*.po' -exec sh -c 'file="{}"; msgfmt "${file%.*}.po" -o "${file%.*}.mo"' \;
find resources/lang/ -type f -name '*.po' -exec sh -c 'msgfmt "${1%.*}.po" -o"${1%.*}.mo"' shell {} \;
```
## Testing

View File

@ -40,7 +40,7 @@ The Engelsystem may be installed manually or by using the provided [docker setup
* Recommended: Directory Listing should be disabled.
* There must be a MySQL database set up with a user who has full rights to that database.
* If necessary, create a `config/config.php` to override values from `config/config.default.php`.
* To edit values from the `footer_items`, `themes`, `locales`, `tshirt_sizes` or `headers` lists, directly modify the `config/config.default.php` file or rename it to `config/config.php`.
* To disable/remove values from the `themes`, `tshirt_sizes`, `headers`, `header_items`, `footer_items`, or `locales` lists, set the value of the entry to `null`.
* To import the database, the `bin/migrate` script has to be run. If you can't execute scripts, you can use the `initial-install.sql` file from the release zip.
* In the browser, login with credentials `admin` : `asdfasdf` and change the password.

16
SECURITY.md Normal file
View File

@ -0,0 +1,16 @@
# Security Policy
## Reporting a Vulnerability
If you want to contact us directly regarding a security concern, please write an e-mail to contact@engelsystem.de and explain your findings.
Thank you!
## Use of external reporting / bug bounty services
We kindly ask you to not use any external reporting / bug bounty service. We do not collaborate with any external service and experiences in the past showed that these services usually add a lot of unnecessary overhead.
Please send security critical bug reports to contact@engelsystem.de.
If you feel like we are not reacting fast enough (generally no more than 14 days should go by until an initial response; This is a volunteer project mostly used internally after all), please feel free to go for full disclosure via our github issue tracker, and tag the issue there by creating a title prefixed with [SECURITY].
If you find a critical vulnerability that warrants a CVE, we will also take care of issuing a CVE without any bug bounty platform having to be involved.

View File

@ -3,10 +3,24 @@
# immediate exit after an error
set -e
testing() {
echo
echo "🔎 Checking ${1}"
}
testing 'JS & CSS 🎨'
yarn check
yarn lint
testing 'PHP ⚙️'
composer validate
composer phpcs
composer phpstan
./vendor/bin/phpunit
testing 'translations 🗺️'
find resources/lang -type f -name '*.po' -exec sh -c 'msgfmt "${1%.*}.po" -o"${1%.*}.mo"' shell {} \;
[ "$(find resources/lang -type f -name '*.po' | wc -l)" -eq "$(find resources/lang -type f -name '*.mo' | wc -l)" ]
find resources/lang -type f -name '*.mo' -exec rm {} \;
echo '✅ Done 🎉'

View File

@ -35,38 +35,40 @@
"ext-pdo": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"doctrine/dbal": "^3.5",
"doctrine/dbal": "^3.6",
"erusev/parsedown": "^1.7",
"gettext/gettext": "^5.7",
"gettext/translator": "^1.1",
"guzzlehttp/guzzle": "^7.5",
"illuminate/container": "^9.43",
"illuminate/database": "^9.43",
"illuminate/support": "^9.43",
"league/oauth2-client": "^2.6",
"guzzlehttp/guzzle": "^7.8",
"illuminate/container": "^10.23",
"illuminate/database": "^10.23",
"illuminate/support": "^10.23",
"league/oauth2-client": "^2.7",
"league/openapi-psr7-validator": "^0.21",
"nikic/fast-route": "^1.3",
"nyholm/psr7": "^1.5",
"nyholm/psr7": "^1.8",
"psr/container": "^2.0",
"psr/http-message": "^1.1",
"psr/http-server-middleware": "^1.0",
"psr/log": "^3.0",
"rcrowe/twigbridge": "^0.14.0",
"respect/validation": "^1.1",
"symfony/http-foundation": "^6.2",
"symfony/mailer": "^6.2",
"symfony/psr-http-message-bridge": "^2.1",
"twig/twig": "^3.4",
"symfony/http-foundation": "^6.3",
"symfony/mailer": "^6.3",
"symfony/psr-http-message-bridge": "^2.3",
"twig/twig": "^3.7",
"vlucas/phpdotenv": "^5.5"
},
"require-dev": {
"dms/phpunit-arraysubset-asserts": "^0.4",
"fakerphp/faker": "^1.20",
"dms/phpunit-arraysubset-asserts": "^0.5",
"fakerphp/faker": "^1.23",
"fig/log-test": "^1.1",
"filp/whoops": "^2.14",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^9.5",
"slevomat/coding-standard": "^8.6",
"filp/whoops": "^2.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.6",
"slevomat/coding-standard": "^8.13",
"squizlabs/php_codesniffer": "^3.7",
"symfony/var-dumper": "^6.2"
"symfony/var-dumper": "^6.3"
},
"autoload": {
"psr-4": {

2271
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,6 @@ return [
\Engelsystem\Renderer\TwigServiceProvider::class,
\Engelsystem\Middleware\RouteDispatcherServiceProvider::class,
\Engelsystem\Middleware\RequestHandlerServiceProvider::class,
\Engelsystem\Middleware\SessionHandlerServiceProvider::class,
\Engelsystem\Http\Validation\ValidationServiceProvider::class,
\Engelsystem\Http\RedirectServiceProvider::class,
@ -38,6 +37,7 @@ return [
\Engelsystem\Http\HttpClientServiceProvider::class,
\Engelsystem\Helpers\DumpServerServiceProvider::class,
\Engelsystem\Helpers\UuidServiceProvider::class,
\Engelsystem\Controllers\Api\UsesAuthServiceProvider::class,
],
// Application middleware
@ -50,9 +50,11 @@ return [
\Engelsystem\Middleware\SetLocale::class,
\Engelsystem\Middleware\ETagHandler::class,
\Engelsystem\Middleware\AddHeaders::class,
\Engelsystem\Middleware\TrimInput::class,
// The application code
\Engelsystem\Middleware\ErrorHandler::class,
\Engelsystem\Middleware\ApiRouteHandler::class,
\Engelsystem\Middleware\VerifyCsrfToken::class,
\Engelsystem\Middleware\RouteDispatcher::class,
\Engelsystem\Middleware\SessionHandler::class,
@ -74,6 +76,7 @@ return [
'message.created' => \Engelsystem\Events\Listener\Messages::class . '@created',
'news.created' => \Engelsystem\Events\Listener\News::class . '@created',
'news.updated' => \Engelsystem\Events\Listener\News::class . '@updated',
'oauth2.login' => \Engelsystem\Events\Listener\OAuth2::class . '@login',
@ -81,5 +84,7 @@ return [
\Engelsystem\Events\Listener\Shift::class . '@deletedEntryCreateWorklog',
\Engelsystem\Events\Listener\Shift::class . '@deletedEntrySendEmail',
],
'shift.updating' => \Engelsystem\Events\Listener\Shift::class . '@updatedShiftSendEmail',
],
];

View File

@ -30,14 +30,24 @@ return [
// Header links
// Available link placeholders: %lang%
// To disable a header_item in the config.php, you can set its value to null
'header_items' => [
//'Foo' => 'https://foo.bar/batz-%lang%.html',
// Name can be a translation string, permission is a engelsystem privilege
// 'Name' => 'URL',
// 'Name' => ['URL', 'permission'],
//'Foo' => ['https://foo.bar/batz-%lang%.html', 'logout'], // Permission: for logged-in users
],
// Footer links
// To disable a footer item in the config.php, you can set its value to null
'footer_items' => [
// Name can be a translation string, permission is a engelsystem privilege
// 'Name' => 'URL',
// 'Name' => ['URL', 'permission'],
// URL to the angel faq and job description
'FAQ' => env('FAQ_URL', '/faq'),
'faq.faq' => [env('FAQ_URL', '/faq'), 'faq.view'],
// Contact email address, linked on every page
'Contact' => env('CONTACT_EMAIL', 'mailto:ticket@c3heaven.de'),
@ -131,7 +141,14 @@ return [
// Default theme, 1=style1.css
'theme' => env('THEME', 1),
// Supported themes
// To disable a theme in the config.php, you can set its value to null
'themes' => [
16 => [
'name' => 'Engelsystem cccamp23 (2023)',
'type' => 'dark',
'navbar_classes' => 'navbar-dark',
],
15 => [
'name' => 'Engelsystem rC3 (2021)',
'type' => 'dark',
@ -224,6 +241,16 @@ return [
// Users are able to sign up
'registration_enabled' => (bool) env('REGISTRATION_ENABLED', true),
// Required user fields
'required_user_fields' => [
'pronoun' => (bool) env('PRONOUN_REQUIRED', false),
'firstname' => (bool) env('FIRSTNAME_REQUIRED', false),
'lastname' => (bool) env('LASTNAME_REQUIRED', false),
'tshirt_size' => (bool) env('TSHIRT_SIZE_REQUIRED', true),
'mobile' => (bool) env('MOBILE_REQUIRED', false),
'dect' => (bool) env('DECT_REQUIRED', false),
],
// Only arrived angels can sign up for shifts
'signup_requires_arrival' => (bool) env('SIGNUP_REQUIRES_ARRIVAL', false),
@ -268,7 +295,7 @@ return [
// Regular expression describing a FALSE username.
// Per default usernames must only contain alphanumeric chars, "-", "_" or ".".
'username_regex' => (string) env('USERNAME_REGEX', '/([^\p{L}\p{N}\-_.]+)/ui'),
'username_regex' => (string) env('USERNAME_REGEX', '/([^\p{L}\p{N}_.-]+)/ui'),
// Enables first name and last name
'enable_user_name' => (bool) env('ENABLE_USER_NAME', false),
@ -318,7 +345,15 @@ return [
'voucher_start' => env('VOUCHER_START', null) ?: null,
],
# Instruction in accordance with § 43 Para. 1 of the German Infection Protection Act (IfSG)
'ifsg_enabled' => (bool) env('IFSG_ENABLED', false),
# Instruction only onsite in accordance with § 43 Para. 1 of the German Infection Protection Act (IfSG)
'ifsg_light_enabled' => (bool) env('IFSG_LIGHT_ENABLED', false)
&& env('IFSG_ENABLED', false),
// Available locales in /resources/lang/
// To disable a locale in the config.php, you can set its value to null
'locales' => [
'de_DE' => 'Deutsch',
'en_US' => 'English',
@ -327,21 +362,28 @@ return [
// The default locale to use
'default_locale' => env('DEFAULT_LOCALE', 'en_US'),
// Available T-Shirt sizes, set value to null if not available
// Available T-Shirt sizes
// To disable a t-shirt size in the config.php, you can set its value to null
'tshirt_sizes' => [
'S' => 'Small Straight-Cut',
'S-G' => 'Small Fitted-Cut',
'S-F' => 'Small Fitted-Cut',
'M' => 'Medium Straight-Cut',
'M-G' => 'Medium Fitted-Cut',
'M-F' => 'Medium Fitted-Cut',
'L' => 'Large Straight-Cut',
'L-G' => 'Large Fitted-Cut',
'L-F' => 'Large Fitted-Cut',
'XL' => 'XLarge Straight-Cut',
'XL-G' => 'XLarge Fitted-Cut',
'XL-F' => 'XLarge Fitted-Cut',
'2XL' => '2XLarge Straight-Cut',
'3XL' => '3XLarge Straight-Cut',
'4XL' => '4XLarge Straight-Cut',
],
// Whether to show the current day of the event (-2, -1, 0, 1, 2…) in footer and on the dashboard.
// The event start date has to be set for it to appear.
'enable_show_day_of_event' => false,
// If true there will be a day 0 (-1, 0, 1…). If false there won't (-1, 1…)
'event_has_day0' => true,
'metrics' => [
// User work buckets in seconds
'work' => [1 * 60 * 60, 1.5 * 60 * 60, 2 * 60 * 60, 3 * 60 * 60, 5 * 60 * 60, 10 * 60 * 60, 20 * 60 * 60],
@ -370,6 +412,8 @@ return [
// Add additional headers
'add_headers' => (bool) env('ADD_HEADERS', true),
// Predefined headers
// To disable a header in the config.php, you can set its value to null
'headers' => [
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'sameorigin',

View File

@ -8,6 +8,8 @@ use FastRoute\RouteCollector;
// Pages
$route->get('/', 'HomeController@index');
$route->get('/register', 'RegistrationController@view');
$route->post('/register', 'RegistrationController@save');
$route->get('/credits', 'CreditsController@index');
$route->get('/health', 'HealthController@index');
@ -38,7 +40,14 @@ $route->addGroup(
$route->post('/theme', 'SettingsController@saveTheme');
$route->get('/language', 'SettingsController@language');
$route->post('/language', 'SettingsController@saveLanguage');
$route->get('/certificates', 'SettingsController@certificate');
$route->post('/certificates/ifsg', 'SettingsController@saveIfsgCertificate');
$route->post('/certificates/driving', 'SettingsController@saveDrivingLicense');
$route->get('/api', 'SettingsController@api');
$route->post('/api', 'SettingsController@apiKeyReset');
$route->get('/oauth', 'SettingsController@oauth');
$route->get('/sessions', 'SettingsController@sessions');
$route->post('/sessions', 'SettingsController@sessionsDelete');
}
);
@ -62,6 +71,11 @@ $route->addGroup('/angeltypes', function (RouteCollector $route): void {
$route->get('/about', 'AngelTypesController@about');
});
// Shifts
$route->addGroup('/shifts', function (RouteCollector $route): void {
$route->get('/random', 'ShiftsController@random');
});
// News
$route->get('/meetings', 'NewsController@meetings');
$route->addGroup(
@ -101,7 +115,42 @@ $route->addGroup(
);
// API
$route->get('/api[/{resource:.+}]', 'ApiController@index');
$route->addGroup(
'/api',
function (RouteCollector $route): void {
$route->get('', 'Api\IndexController@index');
$route->addGroup(
'/v0-beta',
function (RouteCollector $route): void {
$route->addRoute(['OPTIONS'], '[/{resource:.+}]', 'Api\IndexController@options');
$route->get('', 'Api\IndexController@indexV0');
$route->get('/openapi', 'Api\IndexController@openApiV0');
$route->get('/info', 'Api\IndexController@info');
$route->get('/angeltypes', 'Api\AngelTypeController@index');
$route->get('/angeltypes/{angeltype_id:\d+}/shifts', 'Api\ShiftsController@entriesByAngeltype');
$route->get('/locations', 'Api\LocationsController@index');
$route->get('/locations/{location_id:\d+}/shifts', 'Api\ShiftsController@entriesByLocation');
$route->get('/news', 'Api\NewsController@index');
$route->get('/users/{user_id:(?:\d+|self)}', 'Api\UsersController@user');
$route->get('/users/{user_id:(?:\d+|self)}/angeltypes', 'Api\AngelTypeController@ofUser');
$route->get('/users/{user_id:(?:\d+|self)}/shifts', 'Api\ShiftsController@entriesByUser');
$route->addRoute(
['POST', 'PUT', 'DELETE', 'PATCH'],
'/[{resource:.+}]',
'Api\IndexController@notImplemented'
);
$route->get('/[{resource:.+}]', 'Api\IndexController@notFound');
}
);
$route->get('/[{resource:.+}]', 'Api\IndexController@notFound');
}
);
// Feeds
$route->get('/atom', 'FeedController@atom');
@ -146,6 +195,27 @@ $route->addGroup(
}
);
// Shifts
$route->addGroup(
'/shifts',
function (RouteCollector $route): void {
$route->get('/history', 'Admin\\ShiftsController@history');
$route->post('/history', 'Admin\\ShiftsController@deleteTransaction');
}
);
// Shift types
$route->addGroup(
'/shifttypes',
function (RouteCollector $route): void {
$route->get('', 'Admin\\ShiftTypesController@index');
$route->post('', 'Admin\\ShiftTypesController@delete');
$route->get('/{shift_type_id:\d+}', 'Admin\\ShiftTypesController@view');
$route->get('/edit[/{shift_type_id:\d+}]', 'Admin\\ShiftTypesController@edit');
$route->post('/edit[/{shift_type_id:\d+}]', 'Admin\\ShiftTypesController@save');
}
);
// Questions
$route->addGroup(
'/questions',
@ -157,14 +227,14 @@ $route->addGroup(
}
);
// Rooms
// Locations
$route->addGroup(
'/rooms',
'/locations',
function (RouteCollector $route): void {
$route->get('', 'Admin\\RoomsController@index');
$route->post('', 'Admin\\RoomsController@delete');
$route->get('/edit[/{room_id:\d+}]', 'Admin\\RoomsController@edit');
$route->post('/edit[/{room_id:\d+}]', 'Admin\\RoomsController@save');
$route->get('', 'Admin\\LocationsController@index');
$route->post('', 'Admin\\LocationsController@delete');
$route->get('/edit[/{location_id:\d+}]', 'Admin\\LocationsController@edit');
$route->post('/edit[/{location_id:\d+}]', 'Admin\\LocationsController@save');
}
);

View File

@ -24,9 +24,11 @@ class AngelTypeFactory extends Factory
'restricted' => $this->faker->boolean(),
'requires_driver_license' => $this->faker->boolean(),
'no_self_signup' => $this->faker->boolean(),
'requires_ifsg_certificate' => $this->faker->boolean(),
'shift_self_signup' => $this->faker->boolean(),
'show_on_dashboard' => $this->faker->boolean(),
'hide_register' => $this->faker->boolean(),
'hide_on_shift_view' => $this->faker->boolean(),
];
}
}

View File

@ -4,13 +4,13 @@ declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Illuminate\Database\Eloquent\Factories\Factory;
class RoomFactory extends Factory
class LocationFactory extends Factory
{
/** @var string */
protected $model = Room::class; // phpcs:ignore
protected $model = Location::class; // phpcs:ignore
public function definition(): array
{
@ -18,6 +18,7 @@ class RoomFactory extends Factory
'name' => $this->faker->unique()->firstName(),
'map_url' => $this->faker->url(),
'description' => $this->faker->text(),
'dect' => $this->faker->optional()->numberBetween(1000, 9999),
];
}
}

View File

@ -16,12 +16,12 @@ class NewsFactory extends Factory
public function definition(): array
{
return [
'title' => $this->faker->text(50),
'text' => $this->faker->realText(),
'is_meeting' => $this->faker->boolean(),
'is_pinned' => $this->faker->boolean(.1),
'is_important' => $this->faker->boolean(.1),
'user_id' => User::factory(),
'title' => $this->faker->text(50),
'text' => $this->faker->realText(),
'is_meeting' => $this->faker->boolean(),
'is_pinned' => $this->faker->boolean(.1),
'is_highlighted' => $this->faker->boolean(.1),
'user_id' => User::factory(),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models;
use Engelsystem\Models\OAuth;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class OAuthFactory extends Factory
{
/** @var class-string */
protected $model = OAuth::class; // phpcs:ignore
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'provider' => $this->faker->unique()->word(),
'identifier' => $this->faker->unique()->word(),
'access_token' => $this->faker->unique()->word(),
'refresh_token' => $this->faker->unique()->word(),
'expires_at' => $this->faker->dateTimeInInterval('+5 days', '+3 months')->format('Y-m-d'),
];
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models;
use Engelsystem\Models\Session;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class SessionFactory extends Factory
{
/** @var string */
protected $model = Session::class; // phpcs:ignore
public function definition(): array
{
return [
'id' => $this->faker->lexify('????????????????????????????????'),
'payload' => $this->faker->text(100),
'user_id' => $this->faker->optional()->passthrough(User::factory()),
];
}
}

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models\Shifts;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Shift;
use Illuminate\Database\Eloquent\Factories\Factory;
@ -17,11 +17,12 @@ class NeededAngelTypeFactory extends Factory
public function definition(): array
{
$forRoom = $this->faker->boolean();
$type = $this->faker->numberBetween(0, 2);
return [
'room_id' => $forRoom ? Room::factory() : null,
'shift_id' => $forRoom ? null : Shift::factory(),
'location_id' => $type == 0 ? Location::factory() : null,
'shift_id' => $type == 1 ? null : Shift::factory(),
'shift_type_id' => $type == 2 ? null : Shift::factory(),
'angel_type_id' => AngelType::factory(),
'count' => $this->faker->numberBetween(1, 5),
];

View File

@ -18,6 +18,7 @@ class ScheduleFactory extends Factory
'name' => $this->faker->unique()->words(4, true),
'url' => $this->faker->parse('https://{{safeEmailDomain}}/{{slug}}.xml'),
'shift_type' => $this->faker->numberBetween(1, 5),
'needed_from_shift_type' => $this->faker->boolean(.2),
'minutes_before' => 15,
'minutes_after' => 15,
];

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models\Shifts;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Models\User\User;
@ -25,7 +25,7 @@ class ShiftFactory extends Factory
'start' => $start,
'end' => $this->faker->dateTimeInInterval($start, '+3 hours'),
'shift_type_id' => ShiftType::factory(),
'room_id' => Room::factory(),
'location_id' => Location::factory(),
'transaction_id' => $this->faker->optional()->uuid(),
'created_by' => User::factory(),
'updated_by' => $this->faker->optional(.3)->boolean() ? User::factory() : null,

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models\User;
use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class ContactFactory extends Factory
@ -15,6 +16,7 @@ class ContactFactory extends Factory
public function definition(): array
{
return [
'user_id' => User::factory(),
'dect' => $this->faker->optional()->numberBetween(1000, 9999),
'email' => $this->faker->unique()->optional()->safeEmail(),
'mobile' => $this->faker->optional(.2)->phoneNumber(),

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models\User;
use Engelsystem\Models\User\License;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class LicenseFactory extends Factory
@ -21,13 +22,19 @@ class LicenseFactory extends Factory
$drive_forklift = ($drive_car && $this->faker->boolean(.1))
|| ($drive_12t && $this->faker->boolean(.7));
$ifsg_certificate = $this->faker->boolean(0.1);
$ifsg_certificate_light = $this->faker->boolean(0.5) && !$ifsg_certificate;
return [
'has_car' => $drive_car && $this->faker->boolean(.7),
'drive_forklift' => $drive_forklift,
'drive_car' => $drive_car,
'drive_3_5t' => $drive_3_5t,
'drive_7_5t' => $drive_7_5t,
'drive_12t' => $drive_12t,
'user_id' => User::factory(),
'has_car' => $drive_car && $this->faker->boolean(.7),
'drive_forklift' => $drive_forklift,
'drive_car' => $drive_car,
'drive_3_5t' => $drive_3_5t,
'drive_7_5t' => $drive_7_5t,
'drive_12t' => $drive_12t,
'ifsg_certificate' => $ifsg_certificate,
'ifsg_certificate_light' => $ifsg_certificate_light,
];
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models\User;
use Engelsystem\Models\User\PasswordReset;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PasswordResetFactory extends Factory
@ -15,6 +16,7 @@ class PasswordResetFactory extends Factory
public function definition(): array
{
return [
'user_id' => User::factory(),
'token' => bin2hex(random_bytes(16)),
];
}

View File

@ -6,6 +6,7 @@ namespace Database\Factories\Engelsystem\Models\User;
use Carbon\Carbon;
use Engelsystem\Models\User\PersonalData;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PersonalDataFactory extends Factory
@ -19,6 +20,7 @@ class PersonalDataFactory extends Factory
$departure = $this->faker->optional()->dateTimeThisMonth('2 weeks');
return [
'user_id' => User::factory(),
'first_name' => $this->faker->optional(.7)->firstName(),
'last_name' => $this->faker->optional()->lastName(),
'pronoun' => $this->faker->optional(.3)->pronoun(),

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Database\Factories\Engelsystem\Models\User;
use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class SettingsFactory extends Factory
@ -15,6 +16,7 @@ class SettingsFactory extends Factory
public function definition(): array
{
return [
'user_id' => User::factory(),
'language' => $this->faker->locale(),
'theme' => $this->faker->numberBetween(1, 20),
'email_human' => $this->faker->boolean(),

View File

@ -6,6 +6,7 @@ namespace Database\Factories\Engelsystem\Models\User;
use Carbon\Carbon;
use Engelsystem\Models\User\State;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class StateFactory extends Factory
@ -18,8 +19,10 @@ class StateFactory extends Factory
$arrival = $this->faker->optional()->dateTimeThisMonth();
return [
'user_id' => User::factory(),
'arrived' => (bool) $arrival,
'arrival_date' => $arrival ? Carbon::instance($arrival) : null,
'user_info' => $this->faker->optional(.1)->text(),
'active' => $this->faker->boolean(.3),
'force_active' => $this->faker->boolean(.1),
'got_shirt' => $this->faker->boolean(),

View File

@ -19,6 +19,7 @@ class UserFactory extends Factory
'password' => crypt(random_bytes(16), '$1$salt$'),
'email' => $this->faker->unique()->safeEmail(),
'api_key' => bin2hex(random_bytes(32)),
'updated_at' => $this->faker->dateTimeInInterval('-3 months', 'now'),
];
}
}

View File

@ -1,802 +0,0 @@
-- phpMyAdmin SQL Dump
-- version 4.5.2
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Erstellungszeit: 27. Sep 2016 um 17:48
-- Server-Version: 10.1.10-MariaDB
-- PHP-Version: 7.0.4
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
--
-- Datenbank: `engelsystem`
--
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `AngelTypes`
--
DROP TABLE IF EXISTS `AngelTypes`;
CREATE TABLE `AngelTypes` (
`id` int(11) NOT NULL,
`name` varchar(50) NOT NULL DEFAULT '',
`restricted` int(1) NOT NULL,
`description` text NOT NULL,
`requires_driver_license` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `EventConfig`
--
DROP TABLE IF EXISTS `EventConfig`;
CREATE TABLE `EventConfig` (
`event_name` varchar(255) DEFAULT NULL,
`buildup_start_date` int(11) DEFAULT NULL,
`event_start_date` int(11) DEFAULT NULL,
`event_end_date` int(11) DEFAULT NULL,
`teardown_end_date` int(11) DEFAULT NULL,
`event_welcome_msg` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `GroupPrivileges`
--
DROP TABLE IF EXISTS `GroupPrivileges`;
CREATE TABLE `GroupPrivileges` (
`id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
`privilege_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `GroupPrivileges`
--
INSERT INTO `GroupPrivileges` (`id`, `group_id`, `privilege_id`) VALUES
(85, -7, 10),
(87, -7, 18),
(86, -7, 21),
(216, -6, 5),
(212, -6, 6),
(207, -6, 7),
(211, -6, 12),
(208, -6, 13),
(210, -6, 14),
(214, -6, 16),
(209, -6, 21),
(213, -6, 28),
(206, -6, 31),
(215, -6, 33),
(257, -6, 38),
(219, -5, 14),
(221, -5, 25),
(220, -5, 33),
(241, -4, 5),
(238, -4, 14),
(240, -4, 16),
(237, -4, 19),
(242, -4, 25),
(235, -4, 27),
(239, -4, 28),
(236, -4, 32),
(218, -4, 39),
(258, -3, 31),
(247, -2, 3),
(246, -2, 4),
(255, -2, 8),
(252, -2, 9),
(254, -2, 11),
(248, -2, 15),
(251, -2, 17),
(256, -2, 24),
(253, -2, 26),
(245, -2, 30),
(244, -2, 34),
(249, -2, 35),
(243, -2, 36),
(250, -2, 37),
(88, -1, 1),
(23, -1, 2),
(24, -1, 5);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `Groups`
--
DROP TABLE IF EXISTS `Groups`;
CREATE TABLE `Groups` (
`Name` varchar(35) NOT NULL,
`UID` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `Groups`
--
INSERT INTO `Groups` (`Name`, `UID`) VALUES
('6-Developer', -7),
('5-Bürokrat', -6),
('4-Team Coordinator', -5),
('3-Shift Coordinator', -4),
('Shirt-Manager', -3),
('2-Engel', -2),
('1-Gast', -1);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `LogEntries`
--
DROP TABLE IF EXISTS `LogEntries`;
CREATE TABLE `LogEntries` (
`id` int(11) NOT NULL,
`timestamp` int(11) NOT NULL,
`nick` text NOT NULL,
`message` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `Messages`
--
DROP TABLE IF EXISTS `Messages`;
CREATE TABLE `Messages` (
`id` int(11) NOT NULL,
`Datum` int(11) NOT NULL,
`SUID` int(11) NOT NULL DEFAULT '0',
`RUID` int(11) NOT NULL DEFAULT '0',
`isRead` char(1) NOT NULL DEFAULT 'N',
`Text` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Fuers interen Communikationssystem';
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `NeededAngelTypes`
--
DROP TABLE IF EXISTS `NeededAngelTypes`;
CREATE TABLE `NeededAngelTypes` (
`id` int(11) NOT NULL,
`room_id` int(11) DEFAULT NULL,
`shift_id` int(11) DEFAULT NULL,
`angel_type_id` int(11) NOT NULL,
`count` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `News`
--
DROP TABLE IF EXISTS `News`;
CREATE TABLE `News` (
`ID` int(11) NOT NULL,
`Datum` int(11) NOT NULL,
`Betreff` varchar(150) NOT NULL DEFAULT '',
`Text` text NOT NULL,
`UID` int(11) NOT NULL DEFAULT '0',
`Treffen` tinyint(4) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `NewsComments`
--
DROP TABLE IF EXISTS `NewsComments`;
CREATE TABLE `NewsComments` (
`ID` bigint(11) NOT NULL,
`Refid` int(11) NOT NULL DEFAULT '0',
`Datum` datetime NOT NULL DEFAULT '0001-01-01 00:00:00',
`Text` text NOT NULL,
`UID` int(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `Privileges`
--
DROP TABLE IF EXISTS `Privileges`;
CREATE TABLE `Privileges` (
`id` int(11) NOT NULL,
`name` varchar(128) NOT NULL,
`desc` varchar(1024) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `Privileges`
--
INSERT INTO `Privileges` (`id`, `name`, `desc`) VALUES
(1, 'start', 'Startseite für Gäste/Nicht eingeloggte User'),
(2, 'login', 'Logindialog'),
(3, 'news', 'Anzeigen der News-Seite'),
(4, 'logout', 'User darf sich ausloggen'),
(5, 'register', 'Einen neuen Engel registerieren'),
(6, 'admin_rooms', 'Räume administrieren'),
(7, 'admin_angel_types', 'Engel Typen administrieren'),
(8, 'user_settings', 'User profile settings'),
(9, 'user_messages', 'Writing and reading messages from user to user'),
(10, 'admin_groups', 'Manage usergroups and their rights'),
(11, 'user_questions', 'Let users ask questions'),
(12, 'admin_questions', 'Answer user''s questions'),
(13, 'admin_faq', 'Edit FAQs'),
(14, 'admin_news', 'Administrate the news section'),
(15, 'news_comments', 'User can comment news'),
(16, 'admin_user', 'Administrate the angels'),
(17, 'user_meetings', 'Lists meetings (news)'),
(18, 'admin_language', 'Translate the system'),
(19, 'admin_log', 'Display recent changes'),
(20, 'user_wakeup', 'User wakeup-service organization'),
(21, 'admin_import', 'Import rooms and shifts from pentabarf'),
(22, 'credits', 'View credits'),
(23, 'faq', 'View FAQ'),
(24, 'user_shifts', 'Signup for shifts'),
(25, 'user_shifts_admin', 'Signup other angels for shifts.'),
(26, 'user_myshifts', 'Allow angels to view their own shifts and cancel them.'),
(27, 'admin_arrive', 'Mark angels when they arrive.'),
(28, 'admin_shifts', 'Create shifts'),
(30, 'ical', 'iCal shift export'),
(31, 'admin_active', 'Mark angels as active and if they got a t-shirt.'),
(32, 'admin_free', 'Show a list of free/unemployed angels.'),
(33, 'admin_user_angeltypes', 'Confirm restricted angel types'),
(34, 'atom', ' Atom news export'),
(35, 'shifts_json_export', 'Export shifts in JSON format'),
(36, 'angeltypes', 'View angeltypes'),
(37, 'user_angeltypes', 'Join angeltypes.'),
(38, 'shifttypes', 'Administrate shift types'),
(39, 'admin_event_config', 'Allow editing event config');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `Questions`
--
DROP TABLE IF EXISTS `Questions`;
CREATE TABLE `Questions` (
`QID` bigint(20) NOT NULL,
`UID` int(11) NOT NULL DEFAULT '0',
`Question` text NOT NULL,
`AID` int(11) DEFAULT NULL,
`Answer` text
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Fragen und Antworten';
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `Room`
--
DROP TABLE IF EXISTS `Room`;
CREATE TABLE `Room` (
`RID` int(11) NOT NULL,
`Name` varchar(35) NOT NULL DEFAULT '',
`Man` text,
`FromPentabarf` char(1) NOT NULL DEFAULT 'N',
`show` char(1) NOT NULL DEFAULT 'Y',
`Number` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `Room`
--
INSERT INTO `Room` (`RID`, `Name`, `Man`, `FromPentabarf`, `show`, `Number`) VALUES
(1, 'Testraum', NULL, '', 'Y', 0);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `ShiftEntry`
--
DROP TABLE IF EXISTS `ShiftEntry`;
CREATE TABLE `ShiftEntry` (
`id` int(11) NOT NULL,
`SID` int(11) NOT NULL DEFAULT '0',
`TID` int(11) NOT NULL DEFAULT '0',
`UID` int(11) NOT NULL DEFAULT '0',
`Comment` text,
`freeload_comment` text,
`freeloaded` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `Shifts`
--
DROP TABLE IF EXISTS `Shifts`;
CREATE TABLE `Shifts` (
`SID` int(11) NOT NULL,
`title` text,
`shifttype_id` int(11) NOT NULL,
`start` int(11) NOT NULL,
`end` int(11) NOT NULL,
`RID` int(11) NOT NULL DEFAULT '0',
`URL` text,
`PSID` int(11) DEFAULT NULL,
`created_by_user_id` int(11) DEFAULT NULL,
`created_at_timestamp` int(11) NOT NULL,
`edited_by_user_id` int(11) DEFAULT NULL,
`edited_at_timestamp` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `ShiftTypes`
--
DROP TABLE IF EXISTS `ShiftTypes`;
CREATE TABLE `ShiftTypes` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`angeltype_id` int(11) DEFAULT NULL,
`description` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `ShiftTypes`
--
INSERT INTO `ShiftTypes` (`id`, `name`, `angeltype_id`, `description`) VALUES
(4, 'Schichttyp1', NULL, '');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `User`
--
DROP TABLE IF EXISTS `User`;
CREATE TABLE `User` (
`UID` int(11) NOT NULL,
`Nick` varchar(23) NOT NULL DEFAULT '',
`Name` varchar(23) DEFAULT NULL,
`Vorname` varchar(23) DEFAULT NULL,
`Alter` int(4) DEFAULT NULL,
`Telefon` varchar(40) DEFAULT NULL,
`DECT` varchar(5) DEFAULT NULL,
`Handy` varchar(40) DEFAULT NULL,
`email` varchar(123) DEFAULT NULL,
`email_shiftinfo` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'User wants to be informed by mail about changes in his shifts',
`jabber` varchar(200) DEFAULT NULL,
`Size` varchar(4) DEFAULT NULL,
`Passwort` varchar(128) DEFAULT NULL,
`password_recovery_token` varchar(32) DEFAULT NULL,
`Gekommen` tinyint(4) NOT NULL DEFAULT '0',
`Aktiv` tinyint(4) NOT NULL DEFAULT '0',
`force_active` tinyint(1) NOT NULL,
`Tshirt` tinyint(4) DEFAULT '0',
`color` tinyint(4) DEFAULT '10',
`Sprache` char(64) NOT NULL,
`Menu` char(1) NOT NULL DEFAULT 'L',
`lastLogIn` int(11) NOT NULL,
`CreateDate` datetime NOT NULL DEFAULT '0001-01-01 00:00:00',
`Art` varchar(30) DEFAULT NULL,
`kommentar` text,
`Hometown` varchar(255) NOT NULL DEFAULT '',
`api_key` varchar(32) NOT NULL,
`got_voucher` int(11) NOT NULL,
`arrival_date` int(11) DEFAULT NULL,
`planned_arrival_date` int(11) NOT NULL,
`planned_departure_date` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `User`
--
INSERT INTO `User` (`UID`, `Nick`, `Name`, `Vorname`, `Alter`, `Telefon`, `DECT`, `Handy`, `email`, `email_shiftinfo`, `jabber`, `Size`, `Passwort`, `password_recovery_token`, `Gekommen`, `Aktiv`, `force_active`, `Tshirt`, `color`, `Sprache`, `Menu`, `lastLogIn`, `CreateDate`, `Art`, `kommentar`, `Hometown`, `api_key`, `got_voucher`, `arrival_date`, `planned_arrival_date`, `planned_departure_date`) VALUES
(1, 'admin', 'Gates', 'Bill', 42, '', '-', '', 'admin@example.com', 1, '', 'XL', '$6$rounds=5000$hjXbIhoRTH3vKiRa$Wl2P2iI5T9iRR.HHu/YFHswBW0WVn0yxCfCiX0Keco9OdIoDK6bIAADswP6KvMCJSwTGdV8PgA8g8Xfw5l8BD1', NULL, 1, 1, 0, 1, 0, 'de_DE.UTF-8', 'L', 1474990948, '0001-01-01 00:00:00', '', '', '', '038850abdd1feb264406be3ffa746235', 0, 1439490478, 1436964455, 1440161255);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `UserAngelTypes`
--
DROP TABLE IF EXISTS `UserAngelTypes`;
CREATE TABLE `UserAngelTypes` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`angeltype_id` int(11) NOT NULL,
`confirm_user_id` int(11) DEFAULT NULL,
`coordinator` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `UserDriverLicenses`
--
DROP TABLE IF EXISTS `UserDriverLicenses`;
CREATE TABLE `UserDriverLicenses` (
`user_id` int(11) NOT NULL,
`has_car` tinyint(1) NOT NULL,
`has_license_car` tinyint(1) NOT NULL,
`has_license_3_5t_transporter` tinyint(1) NOT NULL,
`has_license_7_5t_truck` tinyint(1) NOT NULL,
`has_license_12_5t_truck` tinyint(1) NOT NULL,
`has_license_forklift` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `UserDriverLicenses`
--
INSERT INTO `UserDriverLicenses` (`user_id`, `has_car`, `has_license_car`, `has_license_3_5t_transporter`, `has_license_7_5t_truck`, `has_license_12_5t_truck`, `has_license_forklift`) VALUES
(1, 1, 1, 1, 1, 1, 1);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `UserGroups`
--
DROP TABLE IF EXISTS `UserGroups`;
CREATE TABLE `UserGroups` (
`id` int(11) NOT NULL,
`uid` int(11) NOT NULL,
`group_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Daten für Tabelle `UserGroups`
--
INSERT INTO `UserGroups` (`id`, `uid`, `group_id`) VALUES
(3, 1, -7),
(4, 1, -6),
(12, 1, -5),
(2, 1, -4),
(1, 1, -2);
--
-- Indizes der exportierten Tabellen
--
--
-- Indizes für die Tabelle `AngelTypes`
--
ALTER TABLE `AngelTypes`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `Name` (`name`);
--
-- Indizes für die Tabelle `GroupPrivileges`
--
ALTER TABLE `GroupPrivileges`
ADD PRIMARY KEY (`id`),
ADD KEY `group_id` (`group_id`,`privilege_id`),
ADD KEY `privilege_id` (`privilege_id`);
--
-- Indizes für die Tabelle `Groups`
--
ALTER TABLE `Groups`
ADD PRIMARY KEY (`UID`);
--
-- Indizes für die Tabelle `LogEntries`
--
ALTER TABLE `LogEntries`
ADD PRIMARY KEY (`id`),
ADD KEY `timestamp` (`timestamp`);
--
-- Indizes für die Tabelle `Messages`
--
ALTER TABLE `Messages`
ADD PRIMARY KEY (`id`),
ADD KEY `Datum` (`Datum`),
ADD KEY `SUID` (`SUID`),
ADD KEY `RUID` (`RUID`);
--
-- Indizes für die Tabelle `NeededAngelTypes`
--
ALTER TABLE `NeededAngelTypes`
ADD PRIMARY KEY (`id`),
ADD KEY `room_id` (`room_id`,`angel_type_id`),
ADD KEY `shift_id` (`shift_id`),
ADD KEY `angel_type_id` (`angel_type_id`);
--
-- Indizes für die Tabelle `News`
--
ALTER TABLE `News`
ADD PRIMARY KEY (`ID`),
ADD KEY `UID` (`UID`);
--
-- Indizes für die Tabelle `NewsComments`
--
ALTER TABLE `NewsComments`
ADD PRIMARY KEY (`ID`),
ADD KEY `Refid` (`Refid`),
ADD KEY `UID` (`UID`);
--
-- Indizes für die Tabelle `Privileges`
--
ALTER TABLE `Privileges`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`);
--
-- Indizes für die Tabelle `Questions`
--
ALTER TABLE `Questions`
ADD PRIMARY KEY (`QID`),
ADD KEY `UID` (`UID`),
ADD KEY `AID` (`AID`);
--
-- Indizes für die Tabelle `Room`
--
ALTER TABLE `Room`
ADD PRIMARY KEY (`RID`),
ADD UNIQUE KEY `Name` (`Name`);
--
-- Indizes für die Tabelle `ShiftEntry`
--
ALTER TABLE `ShiftEntry`
ADD PRIMARY KEY (`id`),
ADD KEY `TID` (`TID`),
ADD KEY `UID` (`UID`),
ADD KEY `SID` (`SID`,`TID`),
ADD KEY `freeloaded` (`freeloaded`);
--
-- Indizes für die Tabelle `Shifts`
--
ALTER TABLE `Shifts`
ADD PRIMARY KEY (`SID`),
ADD UNIQUE KEY `PSID` (`PSID`),
ADD KEY `RID` (`RID`),
ADD KEY `shifttype_id` (`shifttype_id`),
ADD KEY `created_by_user_id` (`created_by_user_id`),
ADD KEY `edited_by_user_id` (`edited_by_user_id`);
--
-- Indizes für die Tabelle `ShiftTypes`
--
ALTER TABLE `ShiftTypes`
ADD PRIMARY KEY (`id`),
ADD KEY `angeltype_id` (`angeltype_id`);
--
-- Indizes für die Tabelle `User`
--
ALTER TABLE `User`
ADD PRIMARY KEY (`UID`),
ADD UNIQUE KEY `Nick` (`Nick`),
ADD KEY `api_key` (`api_key`),
ADD KEY `password_recovery_token` (`password_recovery_token`),
ADD KEY `force_active` (`force_active`),
ADD KEY `arrival_date` (`arrival_date`,`planned_arrival_date`),
ADD KEY `planned_departure_date` (`planned_departure_date`);
--
-- Indizes für die Tabelle `UserAngelTypes`
--
ALTER TABLE `UserAngelTypes`
ADD PRIMARY KEY (`id`),
ADD KEY `user_id` (`user_id`,`angeltype_id`,`confirm_user_id`),
ADD KEY `angeltype_id` (`angeltype_id`),
ADD KEY `confirm_user_id` (`confirm_user_id`),
ADD KEY `coordinator` (`coordinator`);
--
-- Indizes für die Tabelle `UserDriverLicenses`
--
ALTER TABLE `UserDriverLicenses`
ADD PRIMARY KEY (`user_id`);
--
-- Indizes für die Tabelle `UserGroups`
--
ALTER TABLE `UserGroups`
ADD PRIMARY KEY (`id`),
ADD KEY `uid` (`uid`,`group_id`),
ADD KEY `group_id` (`group_id`);
--
-- AUTO_INCREMENT für exportierte Tabellen
--
--
-- AUTO_INCREMENT für Tabelle `AngelTypes`
--
ALTER TABLE `AngelTypes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
--
-- AUTO_INCREMENT für Tabelle `GroupPrivileges`
--
ALTER TABLE `GroupPrivileges`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=259;
--
-- AUTO_INCREMENT für Tabelle `LogEntries`
--
ALTER TABLE `LogEntries`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `Messages`
--
ALTER TABLE `Messages`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `NeededAngelTypes`
--
ALTER TABLE `NeededAngelTypes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `News`
--
ALTER TABLE `News`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `NewsComments`
--
ALTER TABLE `NewsComments`
MODIFY `ID` bigint(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `Privileges`
--
ALTER TABLE `Privileges`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=40;
--
-- AUTO_INCREMENT für Tabelle `Questions`
--
ALTER TABLE `Questions`
MODIFY `QID` bigint(20) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `Room`
--
ALTER TABLE `Room`
MODIFY `RID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
--
-- AUTO_INCREMENT für Tabelle `ShiftEntry`
--
ALTER TABLE `ShiftEntry`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
--
-- AUTO_INCREMENT für Tabelle `Shifts`
--
ALTER TABLE `Shifts`
MODIFY `SID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=13;
--
-- AUTO_INCREMENT für Tabelle `ShiftTypes`
--
ALTER TABLE `ShiftTypes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
--
-- AUTO_INCREMENT für Tabelle `User`
--
ALTER TABLE `User`
MODIFY `UID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
--
-- AUTO_INCREMENT für Tabelle `UserAngelTypes`
--
ALTER TABLE `UserAngelTypes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
--
-- AUTO_INCREMENT für Tabelle `UserGroups`
--
ALTER TABLE `UserGroups`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=13;
--
-- Constraints der exportierten Tabellen
--
--
-- Constraints der Tabelle `GroupPrivileges`
--
ALTER TABLE `GroupPrivileges`
ADD CONSTRAINT `groupprivileges_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `Groups` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `groupprivileges_ibfk_2` FOREIGN KEY (`privilege_id`) REFERENCES `Privileges` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `Messages`
--
ALTER TABLE `Messages`
ADD CONSTRAINT `messages_ibfk_1` FOREIGN KEY (`SUID`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `messages_ibfk_2` FOREIGN KEY (`RUID`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `NeededAngelTypes`
--
ALTER TABLE `NeededAngelTypes`
ADD CONSTRAINT `neededangeltypes_ibfk_1` FOREIGN KEY (`room_id`) REFERENCES `Room` (`RID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `neededangeltypes_ibfk_2` FOREIGN KEY (`shift_id`) REFERENCES `Shifts` (`SID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `neededangeltypes_ibfk_3` FOREIGN KEY (`angel_type_id`) REFERENCES `AngelTypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `News`
--
ALTER TABLE `News`
ADD CONSTRAINT `news_ibfk_1` FOREIGN KEY (`UID`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `NewsComments`
--
ALTER TABLE `NewsComments`
ADD CONSTRAINT `newscomments_ibfk_1` FOREIGN KEY (`Refid`) REFERENCES `News` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `newscomments_ibfk_2` FOREIGN KEY (`UID`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `Questions`
--
ALTER TABLE `Questions`
ADD CONSTRAINT `questions_ibfk_1` FOREIGN KEY (`UID`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `questions_ibfk_2` FOREIGN KEY (`AID`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `ShiftEntry`
--
ALTER TABLE `ShiftEntry`
ADD CONSTRAINT `shiftentry_ibfk_1` FOREIGN KEY (`SID`) REFERENCES `Shifts` (`SID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `shiftentry_ibfk_2` FOREIGN KEY (`UID`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `shiftentry_ibfk_3` FOREIGN KEY (`TID`) REFERENCES `AngelTypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `Shifts`
--
ALTER TABLE `Shifts`
ADD CONSTRAINT `shifts_ibfk_1` FOREIGN KEY (`RID`) REFERENCES `Room` (`RID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `shifts_ibfk_2` FOREIGN KEY (`shifttype_id`) REFERENCES `ShiftTypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `shifts_ibfk_3` FOREIGN KEY (`created_by_user_id`) REFERENCES `User` (`UID`) ON DELETE SET NULL ON UPDATE CASCADE,
ADD CONSTRAINT `shifts_ibfk_4` FOREIGN KEY (`edited_by_user_id`) REFERENCES `User` (`UID`) ON DELETE SET NULL ON UPDATE CASCADE;
--
-- Constraints der Tabelle `ShiftTypes`
--
ALTER TABLE `ShiftTypes`
ADD CONSTRAINT `shifttypes_ibfk_1` FOREIGN KEY (`angeltype_id`) REFERENCES `AngelTypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `UserAngelTypes`
--
ALTER TABLE `UserAngelTypes`
ADD CONSTRAINT `userangeltypes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `userangeltypes_ibfk_2` FOREIGN KEY (`angeltype_id`) REFERENCES `AngelTypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `userangeltypes_ibfk_3` FOREIGN KEY (`confirm_user_id`) REFERENCES `User` (`UID`) ON DELETE SET NULL ON UPDATE CASCADE;
--
-- Constraints der Tabelle `UserDriverLicenses`
--
ALTER TABLE `UserDriverLicenses`
ADD CONSTRAINT `userdriverlicenses_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `UserGroups`
--
ALTER TABLE `UserGroups`
ADD CONSTRAINT `usergroups_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `Groups` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `usergroups_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `User` (`UID`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -29,24 +29,9 @@ class ImportInstallSql extends Migration
'UserAngelTypes',
'UserDriverLicenses',
'UserGroups',
'UserWorkLog',
];
/**
* Run the migration
*/
public function up(): void
{
foreach ($this->oldTables as $table) {
if ($this->schema->hasTable($table)) {
return;
}
}
$sql = file_get_contents(__DIR__ . '/../install.sql');
$this->schema->getConnection()->unprepared($sql);
}
/**
* Reverse the migration
*/
@ -54,6 +39,7 @@ class ImportInstallSql extends Migration
{
$this->schema->getConnection()->statement('SET FOREIGN_KEY_CHECKS=0;');
// Delete all remaining tables
foreach ($this->oldTables as $table) {
if ($this->schema->hasTable($table)) {
$this->schema->dropIfExists($table);

View File

@ -8,24 +8,5 @@ use Engelsystem\Database\Migration\Migration;
class ImportUpdateSql extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
if ($this->schema->hasTable('UserWorkLog')) {
return;
}
$sql = file_get_contents(__DIR__ . '/../update.sql');
$this->schema->getConnection()->unprepared($sql);
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->dropIfExists('UserWorkLog');
}
// Do nothing as the tables will be created by later migrations and deleted by ImportInstall
}

View File

@ -37,6 +37,7 @@ class CreateWorklogsTable extends Migration
->get();
foreach ($previousRecords as $previousRecord) {
$worked_at = Carbon::createFromTimestamp($previousRecord->work_timestamp);
$created_at = Carbon::createFromTimestamp($previousRecord->created_timestamp);
$this->schema->getConnection()
->table('worklogs')
@ -44,7 +45,7 @@ class CreateWorklogsTable extends Migration
'id' => $previousRecord->id,
'user_id' => $previousRecord->user_id,
'creator_id' => $previousRecord->created_user_id,
'worked_at' => $previousRecord->work_timestamp,
'worked_at' => $worked_at,
'hours' => $previousRecord->work_hours,
'comment' => $previousRecord->comment,
'created_at' => $created_at,
@ -87,11 +88,11 @@ class CreateWorklogsTable extends Migration
->insert([
'id' => $record->id,
'user_id' => $record->user_id,
'work_timestamp' => $record->worked_at->timestamp,
'work_timestamp' => Carbon::createFromFormat('Y-m-d', $record->worked_at)->timestamp,
'work_hours' => $record->hours,
'comment' => $record->comment,
'created_user_id' => $record->creator_id,
'created_timestamp' => $record->created_at->timestamp,
'created_timestamp' => Carbon::createFromFormat('Y-m-d H:i:s', $record->created_at)->timestamp,
]);
}

View File

@ -21,7 +21,7 @@ class AddNameMinutesAndTimestampsToSchedules extends Migration
$this->schema->table('schedules', function (Blueprint $table): void {
$table->string('name')->default('')->after('id');
$table->integer('shift_type')->default(0)->after('name');
$table->unsignedInteger('shift_type')->default(0)->after('name');
$table->integer('minutes_before')->default(0)->after('shift_type');
$table->integer('minutes_after')->default(0)->after('minutes_before');
$table->timestamps();
@ -36,7 +36,7 @@ class AddNameMinutesAndTimestampsToSchedules extends Migration
$this->schema->table('schedules', function (Blueprint $table): void {
$table->string('name')->default(null)->change();
$table->integer('shift_type')->default(null)->change();
$table->unsignedInteger('shift_type')->default(null)->change();
$table->integer('minutes_before')->default(null)->change();
$table->integer('minutes_after')->default(null)->change();
});

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Config\Config;
use Engelsystem\Database\Migration\Migration;
use Engelsystem\Helpers\Carbon;
use Illuminate\Database\Schema\Builder as SchemaBuilder;
use stdClass;
class CreateFirstUser extends Migration
{
use Reference;
public function __construct(SchemaBuilder $schemaBuilder, protected Config $config)
{
parent::__construct($schemaBuilder);
}
/**
* Run the migration
*/
public function up(): void
{
$db = $this->schema->getConnection();
if ($db->table('users')->count() > 0) {
return;
}
$db->table('users')->insert([
'name' => 'admin',
'email' => 'admin@localhost',
'password' => password_hash('asdfasdf', PASSWORD_DEFAULT),
'api_key' => bin2hex(random_bytes(16)),
'created_at' => Carbon::now(),
]);
/** @var stdClass $admin */
$admin = $db->table('users')->where('name', 'admin')->first();
foreach (['users_contact', 'users_personal_data', 'users_state'] as $table) {
$db->table($table)->insert(['user_id' => $admin->id]);
}
$db->table('users_settings')->insert(['user_id' => $admin->id, 'language' => 'en_US', 'theme' => 0]);
}
}

View File

@ -6,14 +6,14 @@ namespace Engelsystem\Migrations;
use Engelsystem\Config\Config;
use Engelsystem\Database\Migration\Migration;
use Engelsystem\Helpers\Authenticator;
use Illuminate\Database\Schema\Builder as SchemaBuilder;
use stdClass;
class SetAdminPassword extends Migration
{
use Reference;
public function __construct(SchemaBuilder $schemaBuilder, protected Authenticator $auth, protected Config $config)
public function __construct(SchemaBuilder $schemaBuilder, protected Config $config)
{
parent::__construct($schemaBuilder);
}
@ -23,12 +23,21 @@ class SetAdminPassword extends Migration
*/
public function up(): void
{
$admin = $this->auth->authenticate('admin', 'asdfasdf');
$db = $this->schema->getConnection();
/** @var stdClass $admin */
$admin = $db->table('users')->where('name', 'admin')->first();
$setupPassword = $this->config->get('setup_admin_password');
if (!$admin || !$setupPassword) {
if (
!$admin
|| !password_verify('asdfasdf', $admin->password)
|| !$setupPassword
) {
return;
}
$this->auth->setPassword($admin, $setupPassword);
$db->table('users')
->where('id', $admin->id)
->update(['password' => password_hash($setupPassword, PASSWORD_DEFAULT)]);
}
}

View File

@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use stdClass;
class FillPrivilegesAndGroupsRelatedTables extends Migration
{
use ChangesReferences;
use Reference;
/**
* Inserts missing data into permissions & groups related tables
*/
public function up(): void
{
$db = $this->schema->getConnection();
if ($db->table('privileges')->count() > 0) {
return;
}
$db->table('groups')
->insert([
['id' => 10, 'name' => 'Guest'],
['id' => 20, 'name' => 'Angel'],
['id' => 30, 'name' => 'Welcome Angel'],
['id' => 35, 'name' => 'Voucher Angel'],
['id' => 50, 'name' => 'Shirt Manager'],
['id' => 60, 'name' => 'Shift Coordinator'],
['id' => 65, 'name' => 'Team Coordinator'],
['id' => 80, 'name' => 'Bureaucrat'],
['id' => 85, 'name' => 'News Admin'],
['id' => 90, 'name' => 'Developer'],
]);
$db->table('privileges')
->insert([
['id' => 1, 'name' => 'start', 'description' => 'Startseite für Gäste/Nicht eingeloggte User'],
['id' => 2, 'name' => 'login', 'description' => 'Logindialog'],
['id' => 3, 'name' => 'news', 'description' => 'Anzeigen der News-Seite'],
['id' => 4, 'name' => 'logout', 'description' => 'User darf sich ausloggen'],
['id' => 5, 'name' => 'register', 'description' => 'Einen neuen Engel registerieren'],
['id' => 6, 'name' => 'admin_rooms', 'description' => 'Räume administrieren'],
['id' => 7, 'name' => 'admin_angel_types', 'description' => 'Engel Typen administrieren'],
['id' => 8, 'name' => 'user_settings', 'description' => 'User profile settings'],
['id' => 9, 'name' => 'user_messages',
'description' => 'Writing and reading messages from user to user'],
['id' => 10, 'name' => 'admin_groups', 'description' => 'Manage usergroups and their rights'],
['id' => 14, 'name' => 'admin_news', 'description' => 'Administrate the news section'],
['id' => 15, 'name' => 'news_comments', 'description' => 'User can comment news'],
['id' => 16, 'name' => 'admin_user', 'description' => 'Administrate the angels'],
['id' => 17, 'name' => 'user_meetings', 'description' => 'Lists meetings (news)'],
['id' => 18, 'name' => 'admin_language', 'description' => 'Translate the system'],
['id' => 19, 'name' => 'admin_log', 'description' => 'Display recent changes'],
['id' => 21, 'name' => 'schedule.import', 'description' => 'Import rooms and shifts from schedule.xml'],
['id' => 24, 'name' => 'user_shifts', 'description' => 'Signup for shifts'],
['id' => 25, 'name' => 'user_shifts_admin', 'description' => 'Signup other angels for shifts.'],
['id' => 26, 'name' => 'user_myshifts',
'description' => 'Allow angels to view their own shifts and cancel them.'],
['id' => 27, 'name' => 'admin_arrive', 'description' => 'Mark angels when they arrive.'],
['id' => 28, 'name' => 'admin_shifts', 'description' => 'Create shifts'],
['id' => 30, 'name' => 'ical', 'description' => 'iCal shift export'],
['id' => 31, 'name' => 'admin_active',
'description' => 'Mark angels as active and if they got a t-shirt.'],
['id' => 32, 'name' => 'admin_free', 'description' => 'Show a list of free/unemployed angels.'],
['id' => 33, 'name' => 'admin_user_angeltypes', 'description' => 'Confirm restricted angel types'],
['id' => 34, 'name' => 'atom', 'description' => ' Atom news export'],
['id' => 35, 'name' => 'shifts_json_export', 'description' => 'Export shifts in JSON format'],
['id' => 36, 'name' => 'angeltypes', 'description' => 'View angeltypes'],
['id' => 37, 'name' => 'user_angeltypes', 'description' => 'Join angeltypes.'],
['id' => 38, 'name' => 'shifttypes', 'description' => 'Administrate shift types'],
['id' => 39, 'name' => 'admin_event_config', 'description' => 'Allow editing event config'],
['id' => 40, 'name' => 'view_rooms', 'description' => 'User can view rooms'],
['id' => 41, 'name' => 'shiftentry_edit_angeltype_supporter',
'description' => 'If user with this privilege is angeltype supporter, '
. 'he can put users in shifts for their angeltype'],
['id' => 43, 'name' => 'admin_user_worklog', 'description' => 'Manage user work log entries.'],
['id' => 44, 'name' => 'faq.view', 'description' => 'View FAQ entries'],
['id' => 45, 'name' => 'faq.edit', 'description' => 'Edit FAQ entries'],
['id' => 46, 'name' => 'question.add', 'description' => 'Ask questions'],
['id' => 47, 'name' => 'question.edit', 'description' => 'Answer questions'],
['id' => 48, 'name' => 'user.edit.shirt', 'description' => 'Edit user shirts'],
['id' => 49, 'name' => 'voucher.edit', 'description' => 'Edit vouchers'],
]);
$db->table('group_privileges')->insert([
['id' => 23, 'group_id' => 10, 'privilege_id' => 2],
['id' => 24, 'group_id' => 10, 'privilege_id' => 5],
['id' => 85, 'group_id' => 90, 'privilege_id' => 10],
['id' => 86, 'group_id' => 90, 'privilege_id' => 21],
['id' => 87, 'group_id' => 90, 'privilege_id' => 18],
['id' => 88, 'group_id' => 10, 'privilege_id' => 1],
['id' => 206, 'group_id' => 80, 'privilege_id' => 31],
['id' => 207, 'group_id' => 80, 'privilege_id' => 7],
['id' => 209, 'group_id' => 80, 'privilege_id' => 21],
['id' => 210, 'group_id' => 80, 'privilege_id' => 14],
['id' => 212, 'group_id' => 80, 'privilege_id' => 6],
['id' => 213, 'group_id' => 80, 'privilege_id' => 28],
['id' => 214, 'group_id' => 80, 'privilege_id' => 16],
['id' => 215, 'group_id' => 80, 'privilege_id' => 33],
['id' => 216, 'group_id' => 80, 'privilege_id' => 5],
['id' => 218, 'group_id' => 60, 'privilege_id' => 39],
['id' => 219, 'group_id' => 65, 'privilege_id' => 14],
['id' => 220, 'group_id' => 65, 'privilege_id' => 33],
['id' => 221, 'group_id' => 65, 'privilege_id' => 25],
['id' => 235, 'group_id' => 60, 'privilege_id' => 27],
['id' => 236, 'group_id' => 60, 'privilege_id' => 32],
['id' => 237, 'group_id' => 60, 'privilege_id' => 19],
['id' => 238, 'group_id' => 60, 'privilege_id' => 14],
['id' => 239, 'group_id' => 60, 'privilege_id' => 28],
['id' => 240, 'group_id' => 60, 'privilege_id' => 16],
['id' => 241, 'group_id' => 60, 'privilege_id' => 5],
['id' => 242, 'group_id' => 60, 'privilege_id' => 25],
['id' => 243, 'group_id' => 20, 'privilege_id' => 36],
['id' => 244, 'group_id' => 20, 'privilege_id' => 34],
['id' => 245, 'group_id' => 20, 'privilege_id' => 30],
['id' => 246, 'group_id' => 20, 'privilege_id' => 4],
['id' => 247, 'group_id' => 20, 'privilege_id' => 3],
['id' => 248, 'group_id' => 20, 'privilege_id' => 15],
['id' => 249, 'group_id' => 20, 'privilege_id' => 35],
['id' => 250, 'group_id' => 20, 'privilege_id' => 37],
['id' => 251, 'group_id' => 20, 'privilege_id' => 17],
['id' => 252, 'group_id' => 20, 'privilege_id' => 9],
['id' => 253, 'group_id' => 20, 'privilege_id' => 26],
['id' => 255, 'group_id' => 20, 'privilege_id' => 8],
['id' => 256, 'group_id' => 20, 'privilege_id' => 24],
['id' => 257, 'group_id' => 80, 'privilege_id' => 38],
['id' => 258, 'group_id' => 50, 'privilege_id' => 31],
['id' => 259, 'group_id' => 20, 'privilege_id' => 40],
['id' => 260, 'group_id' => 85, 'privilege_id' => 14],
['id' => 262, 'group_id' => 60, 'privilege_id' => 43],
['id' => 263, 'group_id' => 20, 'privilege_id' => 41],
['id' => 264, 'group_id' => 30, 'privilege_id' => 27],
['id' => 265, 'group_id' => 10, 'privilege_id' => 44],
['id' => 266, 'group_id' => 20, 'privilege_id' => 44],
['id' => 267, 'group_id' => 60, 'privilege_id' => 45],
['id' => 268, 'group_id' => 20, 'privilege_id' => 46],
['id' => 269, 'group_id' => 60, 'privilege_id' => 47],
['id' => 270, 'group_id' => 60, 'privilege_id' => 48],
['id' => 271, 'group_id' => 50, 'privilege_id' => 48],
['id' => 272, 'group_id' => 50, 'privilege_id' => 27],
['id' => 273, 'group_id' => 60, 'privilege_id' => 49],
['id' => 274, 'group_id' => 35, 'privilege_id' => 49],
['id' => 275, 'group_id' => 35, 'privilege_id' => 27],
]);
/** @var stdClass $admin */
$admin = $db->table('users')->where('name', 'admin')->first();
if (!$admin) {
return;
}
// Angel, ShiCo, Team coordinator, Bureaucrat, Dev
foreach ([20, 60, 65, 80, 90] as $group) {
$db->table('users_groups')->insert(['user_id' => $admin->id, 'group_id' => $group]);
}
}
}

View File

@ -6,6 +6,7 @@ namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Str;
class ChangeApiKeyLength extends Migration
{
@ -24,6 +25,19 @@ class ChangeApiKeyLength extends Migration
*/
public function down(): void
{
$connection = $this->schema->getConnection();
$data = $connection->table('users')->get(['id', 'api_key']);
foreach ($data as $user) {
if (Str::length($user->api_key) <= 32) {
continue;
}
$key = Str::substr($user->api_key, 0, 32);
$connection->table('users')
->where('id', $user->id)
->update(['api_key' => $key]);
}
$this->schema->table('users', function (Blueprint $table): void {
$table->string('api_key', 32)->change();
});

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class CreateApiPermissions extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')->insert([
['name' => 'api', 'description' => 'Use the API'],
]);
$db->table('groups')->insert([
['id' => 40, 'name' => 'API'],
]);
$bureaucratGroup = 80;
$apiId = $db->table('privileges')->where('name', 'api')->first()->id;
$db->table('group_privileges')->insert([
['group_id' => $bureaucratGroup, 'privilege_id' => $apiId],
['group_id' => 40, 'privilege_id' => $apiId],
]);
}
/**
* Reverse the migration
*/
public function down(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->where('name', 'api')
->delete();
$db->table('groups')
->where('id', 40)
->delete();
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class CleanupShortApiKeys extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$db = $this->schema->getConnection();
$db->table('users')
->where($db->raw('LENGTH(api_key)'), '<=', 42)
->update(['api_key' => '']);
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddIfsgCerificatesToUsersLicenses extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('users_licenses', function (Blueprint $table): void {
$table->boolean('ifsg_certificate_light')->default(false)->after('drive_12t');
$table->boolean('ifsg_certificate')->default(false)->after('ifsg_certificate_light');
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('users_licenses', function (Blueprint $table): void {
$table->dropColumn('ifsg_certificate_light');
$table->dropColumn('ifsg_certificate');
});
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddRequiresIfsgCerificateToAngeltypes extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('angel_types', function (Blueprint $table): void {
$table->boolean('requires_ifsg_certificate')->default(false)->after('requires_driver_license');
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('angel_types', function (Blueprint $table): void {
$table->dropColumn('requires_ifsg_certificate');
});
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AngeltypesRenameNoSelfSignupToShiftSelfSignup extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('angel_types', function (Blueprint $table): void {
$table->renameColumn('no_self_signup', 'shift_self_signup')->default(true);
$connection = $this->schema->getConnection();
$connection->table('angel_types')
->update(['no_self_signup' => $connection->raw('NOT no_self_signup'),
]);
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('angel_types', function (Blueprint $table): void {
$table->renameColumn('shift_self_signup', 'no_self_signup');
$connection = $this->schema->getConnection();
$connection->table('angel_types')
->update(['shift_self_signup' => $connection->raw('NOT shift_self_signup'),
]);
});
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddHideOnShiftViewToAngeltypes extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('angel_types', function (Blueprint $table): void {
$table->boolean('hide_on_shift_view')->default(false)->after('hide_register');
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('angel_types', function (Blueprint $table): void {
$table->dropColumn('hide_on_shift_view');
});
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddUserToSessionsTable extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('sessions', function (Blueprint $table): void {
$this->referencesUser($table)->nullable()->index()->after('payload');
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('sessions', function (Blueprint $table): void {
$table->dropForeign('sessions_user_id_foreign');
$table->dropColumn('user_id');
});
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class NewsRenameImportantToHighlight extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('news', function (Blueprint $table): void {
$table->renameColumn('is_important', 'is_highlighted');
});
$this->schema->getConnection()
->table('privileges')
->where('name', 'news.important')
->update(['name' => 'news.highlight', 'description' => 'Highlight News']);
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('news', function (Blueprint $table): void {
$table->renameColumn('is_highlighted', 'is_important');
});
$this->schema->getConnection()
->table('privileges')
->where('name', 'news.highlight')
->update(['name' => 'news.important', 'description' => 'Make News Important']);
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class RenameRoomsToLocations extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$this->schema->rename('rooms', 'locations');
$this->schema->table('shifts', function (Blueprint $table): void {
$table->renameColumn('room_id', 'location_id');
});
$this->schema->table('needed_angel_types', function (Blueprint $table): void {
$table->renameColumn('room_id', 'location_id');
});
$db = $this->schema->getConnection();
$db->table('privileges')->where('name', 'admin_rooms')->update([
'name' => 'admin_locations',
'description' => 'Manage locations',
]);
$db->table('privileges')->where('name', 'view_rooms')->update([
'name' => 'view_locations',
'description' => 'User can view locations',
]);
$db->table('privileges')->where('name', 'schedule.import')->update([
'description' => 'Import locations and shifts from schedule.xml',
]);
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->rename('locations', 'rooms');
$this->schema->table('shifts', function (Blueprint $table): void {
$table->renameColumn('location_id', 'room_id');
});
$this->schema->table('needed_angel_types', function (Blueprint $table): void {
$table->renameColumn('location_id', 'room_id');
});
$db = $this->schema->getConnection();
$db->table('privileges')->where('name', 'admin_locations')->update([
'name' => 'admin_rooms',
'description' => 'Räume administrieren',
]);
$db->table('privileges')->where('name', 'view_locations')->update([
'name' => 'view_rooms',
'description' => 'User can view rooms',
]);
$db->table('privileges')->where('name', 'schedule.import')->update([
'description' => 'Import rooms and shifts from schedule.xml',
]);
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddMissingScheduleForeignKeys extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$schemaManager = $this->schema->getConnection()->getDoctrineSchemaManager();
$hasShiftTypeReference = $schemaManager->introspectTable('schedules')
->hasIndex('schedules_shift_type_foreign');
if (!$hasShiftTypeReference) {
$this->schema->table('schedules', function (Blueprint $table): void {
$table->unsignedInteger('shift_type')->change();
$this->addReference($table, 'shift_type', 'shift_types');
});
}
$hasShiftIdReference = $schemaManager->introspectTable('schedule_shift')
->hasIndex('schedule_shift_schedule_id_foreign');
if (!$hasShiftIdReference) {
$this->schema->table('schedule_shift', function (Blueprint $table): void {
$table->unsignedInteger('shift_id')->change();
$this->addReference($table, 'shift_id', 'shifts');
});
}
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class DegenderShirtSizes extends Migration
{
/** @var string[] */
protected array $sizes = [
'S-G' => 'S-F',
'M-G' => 'M-F',
'L-G' => 'L-F',
'XL-G' => 'XL-F',
];
/**
* Run the migration
*/
public function up(): void
{
$this->migrate($this->sizes);
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->migrate(array_flip($this->sizes));
}
/**
* @param string[] $sizes
*/
private function migrate(array $sizes): void
{
$connection = $this->schema->getConnection();
foreach ($sizes as $from => $to) {
$connection
->table('users_personal_data')
->where('shirt_size', $from)
->update([
'shirt_size' => $to,
]);
}
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddUserInfoToUsersState extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('users_state', function (Blueprint $table): void {
$table->string('user_info')->nullable()->default(null)->after('arrival_date');
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('users_state', function (Blueprint $table): void {
$table->dropColumn('user_info');
});
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class AddUserInfoPermissions extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->insert([
['name' => 'user.info.show', 'description' => 'Show User Info'],
['name' => 'user.info.edit', 'description' => 'Edit User Info'],
]);
$showUserInfo = $db->table('privileges')
->where('name', 'user.info.show')
->get(['id'])
->first();
$editUserInfo = $db->table('privileges')
->where('name', 'user.info.edit')
->get(['id'])
->first();
$buerocrat = 80;
$shico = 60;
$db->table('group_privileges')
->insertOrIgnore([
['group_id' => $buerocrat, 'privilege_id' => $editUserInfo->id],
['group_id' => $shico, 'privilege_id' => $showUserInfo->id],
]);
}
/**
* Reverse the migration
*/
public function down(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->whereIn('name', ['user.info.edit', 'user.info.show'])
->delete();
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddUserIdToLogEntries extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('log_entries', function (Blueprint $table): void {
$table->unsignedInteger('user_id')->after('id')->nullable()->default(null);
$table->foreign('user_id')
->references('id')->on('users')
->onUpdate('cascade')
->nullOnDelete();
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('log_entries', function (Blueprint $table): void {
$table->dropForeign('log_entries_user_id_foreign');
$table->dropColumn('user_id');
});
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Builder as SchemaBuilder;
class ChangeEditShirtRequireBureaucrat extends Migration
{
protected int $bureaucrat = 80;
protected int $shiCo = 60;
protected int $editShirt;
protected Connection $db;
public function __construct(SchemaBuilder $schema)
{
parent::__construct($schema);
$this->db = $this->schema->getConnection();
$this->editShirt = $this->db->table('privileges')
->where('name', 'user.edit.shirt')
->get(['id'])
->first()->id;
}
/**
* Run the migration
*/
public function up(): void
{
$this->movePermission($this->editShirt, $this->shiCo, $this->bureaucrat);
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->movePermission($this->editShirt, $this->bureaucrat, $this->shiCo);
}
protected function movePermission(int $privilege, int $oldGroup, int $newGroup): void
{
$this->db->table('group_privileges')
->insertOrIgnore(['group_id' => $newGroup, 'privilege_id' => $privilege]);
$this->db->table('group_privileges')
->where(['group_id' => $oldGroup, 'privilege_id' => $privilege])
->delete();
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class AddLogsAllPermission extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->insert([
['name' => 'logs.all', 'description' => 'View all logs'],
]);
$logsAll = $db->table('privileges')
->where('name', 'logs.all')
->get(['id'])
->first();
$bureaucrat = 80;
$db->table('group_privileges')
->insertOrIgnore([
['group_id' => $bureaucrat, 'privilege_id' => $logsAll->id],
]);
}
/**
* Reverse the migration
*/
public function down(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->where('name', 'logs.all')
->delete();
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class ScheduleShiftTypeNeededAngelTypes extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('schedules', function (Blueprint $table): void {
$table->boolean('needed_from_shift_type')->after('shift_type')->default(false);
});
$this->schema->table('needed_angel_types', function (Blueprint $table): void {
$this->references($table, 'shift_types')->after('shift_id')->nullable();
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('schedules', function (Blueprint $table): void {
$table->dropColumn('needed_from_shift_type');
});
$this->schema->table('needed_angel_types', function (Blueprint $table): void {
$table->dropForeign('needed_angel_types_shift_type_id_foreign');
$table->dropColumn('shift_type_id');
});
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class AddUserEditPermission extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->insert([
'name' => 'user.edit', 'description' => 'Edit user',
]);
$editUser = $db->table('privileges')
->where('name', 'user.edit')
->get(['id'])
->first();
$buerocrat = 80;
$db->table('group_privileges')
->insertOrIgnore([
'group_id' => $buerocrat, 'privilege_id' => $editUser->id,
]);
}
/**
* Reverse the migration
*/
public function down(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->where('name', 'user.edit')
->delete();
}
}

View File

@ -30,7 +30,7 @@ trait Reference
$table->primary($fromColumn);
}
$this->addReference($table, $fromColumn, $targetTable, $targetColumn ?: 'id');
$this->addReference($table, $fromColumn, $targetTable, $targetColumn);
return $col;
}

View File

@ -1,59 +0,0 @@
INSERT INTO `Privileges` (`id`, `name`, `desc`) VALUES (40, 'view_rooms', 'User can view rooms');
INSERT INTO `GroupPrivileges` (`id`, `group_id`, `privilege_id`) VALUES (NULL, '-2', '40');
ALTER TABLE `UserAngelTypes` CHANGE `coordinator` `supporter` BOOLEAN;
ALTER TABLE `User` ADD COLUMN `email_by_human_allowed` BOOLEAN NOT NULL;
-- No Self Sign Up for some Angel Types
ALTER TABLE AngelTypes ADD no_self_signup TINYINT(1) NOT NULL;
ALTER TABLE `AngelTypes`
ADD `contact_user_id` INT NULL,
ADD `contact_name` VARCHAR(250) NULL,
ADD `contact_dect` VARCHAR(5) NULL,
ADD `contact_email` VARCHAR(250) NULL,
ADD INDEX (`contact_user_id`);
ALTER TABLE `AngelTypes`
ADD FOREIGN KEY (`contact_user_id`) REFERENCES `User`(`UID`) ON DELETE SET NULL ON UPDATE CASCADE;
INSERT INTO `Privileges` (`id`, `name`, `desc`) VALUES (NULL, 'shiftentry_edit_angeltype_supporter', 'If user with this privilege is angeltype supporter, he can put users in shifts for their angeltype');
-- DB Performance
ALTER TABLE `Shifts` ADD INDEX(`start`);
ALTER TABLE `NeededAngelTypes` ADD INDEX(`count`);
-- Security
UPDATE `Groups` SET UID = UID * 10;
INSERT INTO `Groups` (Name, UID) VALUES ('News Admin', -65);
INSERT INTO `Privileges` (id, name, `desc`) VALUES (42, 'admin_news_html', 'Use HTML in news');
INSERT INTO `GroupPrivileges` (group_id, privilege_id) VALUES (-65, 14), (-65, 42);
-- Add log level to LogEntries
ALTER TABLE `LogEntries` CHANGE COLUMN `nick` `level` VARCHAR(20) NOT NULL;
-- Angeltype contact update
ALTER TABLE `AngelTypes` DROP FOREIGN KEY angeltypes_ibfk_1;
ALTER TABLE `AngelTypes` DROP `contact_user_id`;
-- Room update
ALTER TABLE `Room` DROP `Number`;
ALTER TABLE `Room` DROP `show`;
ALTER TABLE `Room` DROP `Man`;
ALTER TABLE `Room` ADD `from_frab` BOOLEAN NOT NULL AFTER `FromPentabarf`;
UPDATE Room SET `from_frab` = (`FromPentabarf` = 'Y');
ALTER TABLE `Room` DROP `FromPentabarf`;
ALTER TABLE `Room` ADD `map_url` VARCHAR(300) NULL AFTER `from_frab`;
ALTER TABLE `Room` ADD `description` TEXT NULL AFTER `map_url`;
-- Dashboard
ALTER TABLE `AngelTypes` ADD `show_on_dashboard` BOOLEAN NOT NULL AFTER `contact_email`;
UPDATE `AngelTypes` SET `show_on_dashboard` = TRUE;
-- Work Log
CREATE TABLE `UserWorkLog` ( `id` INT NOT NULL AUTO_INCREMENT , `user_id` INT NOT NULL , `work_hours` DECIMAL NOT NULL , `comment` VARCHAR(200) NOT NULL , `created_user_id` INT NOT NULL , `created_timestamp` INT NOT NULL , PRIMARY KEY (`id`), INDEX (`user_id`), INDEX (`created_user_id`)) ENGINE = InnoDB;
ALTER TABLE `UserWorkLog` ADD FOREIGN KEY (`created_user_id`) REFERENCES `User`(`UID`) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE `UserWorkLog` ADD FOREIGN KEY (`user_id`) REFERENCES `User`(`UID`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `UserWorkLog` ADD INDEX(`created_timestamp`);
INSERT INTO `Privileges` (`id`, `name`, `desc`) VALUES (NULL, 'admin_user_worklog', 'Manage user work log entries.');
ALTER TABLE `UserWorkLog` CHANGE `work_hours` `work_hours` DECIMAL(10,2) NOT NULL;
ALTER TABLE `UserWorkLog` ADD `work_timestamp` INT NOT NULL AFTER `user_id`;

View File

@ -8,7 +8,7 @@ RUN composer --no-ansi dump-autoload --optimize
FROM alpine AS translation
RUN apk add gettext
COPY resources/lang/ /data
RUN find /data -type f -name '*.po' -exec sh -c 'file="{}"; msgfmt "${file%.*}.po" -o "${file%.*}.mo"' \;
RUN find /data -type f -name '*.po' -exec sh -c 'msgfmt "${1%.*}.po" -o"${1%.*}.mo"' shell {} \;
# Build the themes
FROM node:20-alpine AS themes
@ -26,6 +26,7 @@ COPY config/ /app/config
COPY db/ /app/db
COPY includes/ /app/includes
COPY public/ /app/public
COPY resources/api /app/resources/api
COPY resources/views /app/resources/views
COPY src/ /app/src
COPY storage/ /app/storage

View File

@ -2,7 +2,7 @@
use Engelsystem\Helpers\Carbon;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Engelsystem\Models\UserAngelType;
use Engelsystem\ShiftsFilter;
use Engelsystem\ShiftsFilterRenderer;
@ -17,7 +17,7 @@ use Illuminate\Support\Collection;
*/
function angeltypes_title()
{
return __('Angeltypes');
return __('angeltypes.angeltypes');
}
/**
@ -48,7 +48,7 @@ function angeltypes_controller()
function angeltype_link($angeltype_id, $params = [])
{
$params = array_merge(['action' => 'view', 'angeltype_id' => $angeltype_id], $params);
return page_link_to('angeltypes', $params);
return url('/angeltypes', $params);
}
/**
@ -59,7 +59,7 @@ function angeltype_link($angeltype_id, $params = [])
function angeltype_delete_controller()
{
if (!auth()->can('admin_angel_types')) {
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
$angeltype = AngelType::findOrFail(request()->input('angeltype_id'));
@ -68,11 +68,11 @@ function angeltype_delete_controller()
$angeltype->delete();
engelsystem_log('Deleted angeltype: ' . AngelType_name_render($angeltype, true));
success(sprintf(__('Angeltype %s deleted.'), $angeltype->name));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
return [
sprintf(__('Delete angeltype %s'), $angeltype->name),
sprintf(__('Delete angeltype %s'), htmlspecialchars($angeltype->name)),
AngelType_delete_view($angeltype),
];
}
@ -92,14 +92,14 @@ function angeltype_edit_controller()
// Edit existing angeltype
$angeltype = AngelType::findOrFail($request->input('angeltype_id'));
if (!auth()->user()->isAngelTypeSupporter($angeltype) && !auth()->can('admin_user_angeltypes')) {
throw_redirect(page_link_to('angeltypes'));
if (!auth()->user()?->isAngelTypeSupporter($angeltype) && !auth()->can('admin_user_angeltypes')) {
throw_redirect(url('/angeltypes'));
}
} else {
// New angeltype
if ($supporter_mode) {
// Supporters aren't allowed to create new angeltypes.
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
$angeltype = new AngelType();
}
@ -118,11 +118,13 @@ function angeltype_edit_controller()
}
$angeltype->restricted = $request->has('restricted');
$angeltype->no_self_signup = $request->has('no_self_signup');
$angeltype->shift_self_signup = $request->has('shift_self_signup');
$angeltype->show_on_dashboard = $request->has('show_on_dashboard');
$angeltype->hide_register = $request->has('hide_register');
$angeltype->hide_on_shift_view = $request->has('hide_on_shift_view');
$angeltype->requires_driver_license = $request->has('requires_driver_license');
$angeltype->requires_ifsg_certificate = $request->has('requires_ifsg_certificate');
}
$angeltype->description = strip_request_item_nl('description', $angeltype->description);
@ -137,20 +139,22 @@ function angeltype_edit_controller()
success('Angel type saved.');
engelsystem_log(
'Saved angeltype: ' . $angeltype->name . ($angeltype->restricted ? ', restricted' : '')
. ($angeltype->no_self_signup ? ', no_self_signup' : '')
. ($angeltype->shift_self_signup ? ', shift_self_signup' : '')
. ($angeltype->requires_driver_license ? ', requires driver license' : '') . ', '
. ($angeltype->requires_ifsg_certificate ? ', requires ifsg certificate' : '') . ', '
. $angeltype->contact_name . ', '
. $angeltype->contact_dect . ', '
. $angeltype->contact_email . ', '
. $angeltype->show_on_dashboard . ', '
. $angeltype->hide_register
. $angeltype->hide_register . ', '
. $angeltype->hide_on_shift_view
);
throw_redirect(angeltype_link($angeltype->id));
}
}
return [
sprintf(__('Edit %s'), $angeltype->name),
sprintf(__('Edit %s'), htmlspecialchars((string) $angeltype->name)),
AngelType_edit_view($angeltype, $supporter_mode),
];
}
@ -165,7 +169,7 @@ function angeltype_controller()
$user = auth()->user();
if (!auth()->can('angeltypes')) {
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
$angeltype = AngelType::findOrFail(request()->input('angeltype_id'));
@ -191,7 +195,7 @@ function angeltype_controller()
$isSupporter = !is_null($user_angeltype) && $user_angeltype->supporter;
return [
sprintf(__('Team %s'), $angeltype->name),
sprintf(__('Team %s'), htmlspecialchars($angeltype->name)),
AngelType_view(
$angeltype,
$members,
@ -220,9 +224,8 @@ function angeltype_controller_shiftsFilterDays(AngelType $angeltype)
$days = [];
foreach ($all_shifts as $shift) {
$day = Carbon::make($shift['start'])->format('Y-m-d');
$dayFormatted = Carbon::make($shift['start'])->format(__('Y-m-d'));
if (!isset($days[$day])) {
$days[$day] = $dayFormatted;
$days[$day] = dateWithEventDay($day);
}
}
ksort($days);
@ -239,13 +242,13 @@ function angeltype_controller_shiftsFilterDays(AngelType $angeltype)
function angeltype_controller_shiftsFilter(AngelType $angeltype, $days)
{
$request = request();
$roomIds = Room::query()
$locationIds = Location::query()
->select('id')
->pluck('id')
->toArray();
$shiftsFilter = new ShiftsFilter(
auth()->can('user_shifts_admin'),
$roomIds,
$locationIds,
[$angeltype->id]
);
$selected_day = date('Y-m-d');
@ -269,59 +272,70 @@ function angeltype_controller_shiftsFilter(AngelType $angeltype, $days)
function angeltypes_list_controller()
{
$user = auth()->user();
$admin_angeltypes = auth()->can('admin_angel_types');
if (!auth()->can('angeltypes')) {
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
$angeltypes = AngelTypes_with_user($user->id);
foreach ($angeltypes as $angeltype) {
$actions = [
button(
page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]),
icon('eye') . __('view'),
'btn-sm'
url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]),
icon('eye') . ($admin_angeltypes ? '' : __('View')),
'btn-sm btn-info',
'',
($admin_angeltypes ? __('View') : '')
),
];
if (auth()->can('admin_angel_types')) {
if ($admin_angeltypes) {
$actions[] = button(
page_link_to('angeltypes', ['action' => 'edit', 'angeltype_id' => $angeltype->id]),
icon('pencil') . __('edit'),
'btn-sm'
url('/angeltypes', ['action' => 'edit', 'angeltype_id' => $angeltype->id]),
icon('pencil'),
'btn-sm',
'',
__('form.edit')
);
$actions[] = button(
page_link_to('angeltypes', ['action' => 'delete', 'angeltype_id' => $angeltype->id]),
icon('trash') . __('delete'),
'btn-sm'
url('/angeltypes', ['action' => 'delete', 'angeltype_id' => $angeltype->id]),
icon('trash'),
'btn-sm btn-danger',
'',
__('form.delete')
);
}
$angeltype->membership = AngelType_render_membership($angeltype);
if (!empty($angeltype->user_angel_type_id)) {
$actions[] = button(
page_link_to(
'user_angeltypes',
url(
'/user-angeltypes',
['action' => 'delete', 'user_angeltype_id' => $angeltype->user_angel_type_id]
),
icon('box-arrow-right') . __('leave'),
'btn-sm'
icon('box-arrow-right') . ($admin_angeltypes ? '' : __('Leave')),
'btn-sm',
'',
($admin_angeltypes ? __('Leave') : '')
);
} else {
$actions[] = button(
page_link_to('user_angeltypes', ['action' => 'add', 'angeltype_id' => $angeltype->id]),
icon('box-arrow-in-right') . __('join'),
'btn-sm'
url('/user_angeltypes', ['action' => 'add', 'angeltype_id' => $angeltype->id]),
icon('box-arrow-in-right') . ($admin_angeltypes ? '' : __('Join')),
'btn-sm',
'',
($admin_angeltypes ? __('Join') : '')
);
}
$angeltype->is_restricted = $angeltype->restricted ? icon('mortarboard-fill') : '';
$angeltype->no_self_signup_allowed = $angeltype->no_self_signup ? '' : icon('pencil-square');
$angeltype->shift_self_signup_allowed = $angeltype->shift_self_signup ? icon('pencil-square') : '';
$angeltype->name = '<a href="'
. page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id])
. url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id])
. '">'
. $angeltype->name
. htmlspecialchars($angeltype->name)
. '</a>';
$angeltype->actions = table_buttons($actions);

View File

@ -17,7 +17,7 @@ function event_config_title()
function event_config_edit_controller()
{
if (!auth()->can('admin_event_config')) {
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
$request = request();
@ -117,8 +117,8 @@ function event_config_edit_controller()
$teardown_end_date ? $teardown_end_date->format('Y-m-d H:i') : ''
)
);
success(__('Settings saved.'));
throw_redirect(page_link_to('admin_event_config'));
success(__('settings.success'));
throw_redirect(url('/admin_event_config'));
}
}

View File

@ -1,40 +1,40 @@
<?php
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Engelsystem\ShiftsFilter;
use Engelsystem\ShiftsFilterRenderer;
/**
* Room controllers for managing everything room related.
* Location controllers for managing everything location related.
*/
/**
* View a room with its shifts.
* View a location with its shifts.
*
* @return array
*/
function room_controller(): array
function location_controller(): array
{
if (!auth()->can('view_rooms')) {
throw_redirect(page_link_to());
if (!auth()->can('view_locations')) {
throw_redirect(url('/'));
}
$request = request();
$room = load_room();
$location = load_location();
$all_shifts = $room->shifts->sortBy('start');
$all_shifts = $location->shifts->sortBy('start');
$days = [];
foreach ($all_shifts as $shift) {
$day = $shift->start->format('Y-m-d');
if (!isset($days[$day])) {
$days[$day] = $shift->start->format(__('Y-m-d'));
$days[$day] = dateWithEventDay($day);
}
}
$shiftsFilter = new ShiftsFilter(
true,
[$room->id],
[$location->id],
AngelType::query()->get('id')->pluck('id')->toArray()
);
$selected_day = date('Y-m-d');
@ -53,17 +53,17 @@ function room_controller(): array
$shiftCalendarRenderer = shiftCalendarRendererByShiftFilter($shiftsFilter);
return [
$room->name,
Room_view($room, $shiftsFilterRenderer, $shiftCalendarRenderer),
htmlspecialchars($location->name),
location_view($location, $shiftsFilterRenderer, $shiftCalendarRenderer),
];
}
/**
* Dispatch different room actions.
* Dispatch different location actions.
*
* @return array
*/
function rooms_controller(): array
function locations_controller(): array
{
$request = request();
$action = $request->input('action');
@ -72,36 +72,36 @@ function rooms_controller(): array
}
return match ($action) {
'view' => room_controller(),
'list' => throw_redirect(page_link_to('admin/rooms')),
default => throw_redirect(page_link_to('admin/rooms')),
'view' => location_controller(),
'list' => throw_redirect(url('/admin/locations')),
default => throw_redirect(url('/admin/locations')),
};
}
/**
* @param Room $room
* @param Location $location
* @return string
*/
function room_link(Room $room)
function location_link(Location $location)
{
return page_link_to('rooms', ['action' => 'view', 'room_id' => $room->id]);
return url('/locations', ['action' => 'view', 'location_id' => $location->id]);
}
/**
* Loads room by request param room_id
* Loads location by request param location_id
*
* @return Room
* @return Location
*/
function load_room()
function load_location()
{
if (!test_request_int('room_id')) {
throw_redirect(page_link_to());
if (!test_request_int('location_id')) {
throw_redirect(url('/'));
}
$room = Room::find(request()->input('room_id'));
if (!$room) {
throw_redirect(page_link_to());
$location = Location::find(request()->input('location_id'));
if (!$location) {
throw_redirect(url('/'));
}
return $room;
return $location;
}

View File

@ -1,8 +1,8 @@
<?php
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Location;
use Engelsystem\Models\News;
use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\ShiftsFilter;
@ -15,22 +15,22 @@ function public_dashboard_controller()
{
$filter = null;
if (request()->get('filtered')) {
$requestRooms = check_request_int_array('rooms');
$requestLocations = check_request_int_array('locations');
$requestAngelTypes = check_request_int_array('types');
if (!$requestRooms && !$requestAngelTypes) {
if (!$requestLocations && !$requestAngelTypes) {
$sessionFilter = collect(session()->get('shifts-filter', []));
$requestRooms = $sessionFilter->get('rooms', []);
$requestLocations = $sessionFilter->get('locations', []);
$requestAngelTypes = $sessionFilter->get('types', []);
}
$angelTypes = collect(unrestricted_angeltypes());
$rooms = $requestRooms ?: Room::orderBy('name')->get()->pluck('id')->toArray();
$locations = $requestLocations ?: Location::orderBy('name')->get()->pluck('id')->toArray();
$angelTypes = $requestAngelTypes ?: $angelTypes->pluck('id')->toArray();
$filterValues = [
'userShiftsAdmin' => false,
'filled' => [],
'rooms' => $rooms,
'locations' => $locations,
'types' => $angelTypes,
'startTime' => null,
'endTime' => null,
@ -57,14 +57,14 @@ function public_dashboard_controller()
}
}
$important_news = News::whereIsImportant(true)
$highlighted_news = News::whereIsHighlighted(true)
->orderBy('updated_at')
->limit(1)
->get();
return [
__('Public Dashboard'),
public_dashboard_view($stats, $free_shifts, $important_news),
public_dashboard_view($stats, $free_shifts, $highlighted_news),
];
}
@ -87,7 +87,7 @@ function public_dashboard_controller_free_shift(Shift $shift, ShiftsFilter $filt
'duration' => round(($shift->end->timestamp - $shift->start->timestamp) / 3600),
'shifttype_name' => $shift->shiftType->name,
'title' => $shift->title,
'room_name' => $shift->room->name,
'location_name' => $shift->location->name,
'needed_angels' => public_dashboard_needed_angels($shift->neededAngels, $filter),
];
@ -136,5 +136,5 @@ function public_dashboard_needed_angels($needed_angels, ShiftsFilter $filter = n
*/
function public_dashboard_link(array $parameters = []): string
{
return page_link_to('public-dashboard', $parameters);
return url('/public-dashboard', $parameters);
}

View File

@ -18,7 +18,7 @@ function shift_entries_controller(): array
{
$user = auth()->user();
if (!$user) {
throw_redirect(page_link_to('login'));
throw_redirect(url('/login'));
}
$action = strip_request_item('action');
@ -44,7 +44,7 @@ function shift_entry_create_controller(): array
$request = request();
if ($user->isFreeloader()) {
throw_redirect(page_link_to('user_myshifts'));
throw_redirect(url('/user_myshifts'));
}
$shift = Shift($request->input('shift_id'));
@ -113,17 +113,21 @@ function shift_entry_create_controller_admin(Shift $shift, ?AngelType $angeltype
}
/** @var User[]|Collection $users */
$users = User::query()->orderBy('name')->get();
$users = User::with('userAngelTypes')->orderBy('name')->get();
$users_select = [];
foreach ($users as $user) {
$users_select[$user->id] = $user->displayName;
$name = $user->displayName;
if ($user->userAngelTypes->where('id', $angeltype->id)->isEmpty()) {
$name = __('%s (not "%s")', [$name, $angeltype->name]);
}
$users_select[$user->id] = $name;
}
$angeltypes_select = $angeltypes->pluck('name', 'id')->toArray();
$room = $shift->room;
$location = $shift->location;
return [
ShiftEntry_create_title(),
ShiftEntry_create_view_admin($shift, $room, $angeltype, $angeltypes_select, $signup_user, $users_select),
ShiftEntry_create_view_admin($shift, $location, $angeltype, $angeltypes_select, $signup_user, $users_select),
];
}
@ -167,10 +171,10 @@ function shift_entry_create_controller_supporter(Shift $shift, AngelType $angelt
$users_select[$u->id] = $u->displayName;
}
$room = $shift->room;
$location = $shift->location;
return [
ShiftEntry_create_title(),
ShiftEntry_create_view_supporter($shift, $room, $angeltype, $signup_user, $users_select),
ShiftEntry_create_view_supporter($shift, $location, $angeltype, $signup_user, $users_select),
];
}
@ -250,10 +254,10 @@ function shift_entry_create_controller_user(Shift $shift, AngelType $angeltype):
throw_redirect(shift_link($shift));
}
$room = $shift->room;
$location = $shift->location;
return [
ShiftEntry_create_title(),
ShiftEntry_create_view_user($shift, $room, $angeltype, $comment),
ShiftEntry_create_view_user($shift, $location, $angeltype, $comment),
];
}
@ -272,7 +276,7 @@ function shift_entry_create_link(Shift $shift, AngelType $angeltype, $params = [
'shift_id' => $shift->id,
'angeltype_id' => $angeltype->id,
], $params);
return page_link_to('shift_entries', $params);
return url('/shift-entries', $params);
}
/**
@ -288,7 +292,7 @@ function shift_entry_create_link_admin(Shift $shift, $params = [])
'action' => 'create',
'shift_id' => $shift->id,
], $params);
return page_link_to('shift_entries', $params);
return url('/shift-entries', $params);
}
/**
@ -301,7 +305,7 @@ function shift_entry_load()
$request = request();
if (!$request->has('shift_entry_id') || !test_request_int('shift_entry_id')) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
$shiftEntry = ShiftEntry::findOrFail($request->input('shift_entry_id'));
@ -362,5 +366,5 @@ function shift_entry_delete_link($shiftEntry, $params = [])
'action' => 'delete',
'shift_entry_id' => $shiftEntry['shift_entry_id'] ?? $shiftEntry['id'],
], $params);
return page_link_to('shift_entries', $params);
return url('/shift-entries', $params);
}

View File

@ -1,8 +1,8 @@
<?php
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftType;
@ -20,7 +20,7 @@ function shift_link($shift)
$parameters['shift_id'] = $shift['shift_id'] ?? $shift['id'];
}
return page_link_to('shifts', $parameters);
return url('/shifts', $parameters);
}
/**
@ -29,7 +29,7 @@ function shift_link($shift)
*/
function shift_delete_link(Shift $shift)
{
return page_link_to('user_shifts', ['delete_shift' => $shift->id]);
return url('/user-shifts', ['delete_shift' => $shift->id]);
}
/**
@ -38,7 +38,7 @@ function shift_delete_link(Shift $shift)
*/
function shift_edit_link(Shift $shift)
{
return page_link_to('user_shifts', ['edit_shift' => $shift->id]);
return url('/user-shifts', ['edit_shift' => $shift->id]);
}
/**
@ -52,11 +52,11 @@ function shift_edit_controller()
$request = request();
if (!auth()->can('admin_shifts')) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
if (!$request->has('edit_shift') || !test_request_int('edit_shift')) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
$shift_id = $request->input('edit_shift');
@ -67,14 +67,14 @@ function shift_edit_controller()
));
}
$rooms = [];
foreach (Room::orderBy('name')->get() as $room) {
$rooms[$room->id] = $room->name;
$locations = [];
foreach (Location::orderBy('name')->get() as $location) {
$locations[$location->id] = $location->name;
}
$angeltypes = AngelType::all()->pluck('name', 'id')->toArray();
$shifttypes = ShiftType::all()->pluck('name', 'id')->toArray();
$needed_angel_types = collect(NeededAngelTypes_by_shift($shift_id))->pluck('count', 'angel_type_id')->toArray();
$needed_angel_types = collect(NeededAngelTypes_by_shift($shift))->pluck('count', 'angel_type_id')->toArray();
foreach (array_keys($angeltypes) as $angeltype_id) {
if (!isset($needed_angel_types[$angeltype_id])) {
$needed_angel_types[$angeltype_id] = 0;
@ -84,7 +84,7 @@ function shift_edit_controller()
$shifttype_id = $shift->shift_type_id;
$title = $shift->title;
$description = $shift->description;
$rid = $shift->room_id;
$rid = $shift->location_id;
$start = $shift->start;
$end = $shift->end;
@ -97,12 +97,12 @@ function shift_edit_controller()
if (
$request->has('rid')
&& preg_match('/^\d+$/', $request->input('rid'))
&& isset($rooms[$request->input('rid')])
&& isset($locations[$request->input('rid')])
) {
$rid = $request->input('rid');
} else {
$valid = false;
error(__('Please select a room.'));
error(__('Please select a location.'));
}
if ($request->has('shifttype_id') && isset($shifttypes[$request->input('shifttype_id')])) {
@ -154,13 +154,16 @@ function shift_edit_controller()
$shift->shift_type_id = $shifttype_id;
$shift->title = $title;
$shift->description = $description;
$shift->room_id = $rid;
$shift->location_id = $rid;
$shift->start = $start;
$shift->end = $end;
$shift->updatedBy()->associate(auth()->user());
$shift->save();
mail_shift_change($oldShift, $shift);
event('shift.updating', [
'shift' => $shift,
'oldShift' => $oldShift,
]);
NeededAngelType::whereShiftId($shift_id)->delete();
$needed_angel_types_info = [];
@ -194,13 +197,16 @@ function shift_edit_controller()
foreach ($angeltypes as $angeltype_id => $angeltype_name) {
$angel_types_spinner .= form_spinner(
'angeltype_count_' . $angeltype_id,
$angeltype_name,
$needed_angel_types[$angeltype_id]
htmlspecialchars($angeltype_name),
$needed_angel_types[$angeltype_id],
[],
ScheduleShift::whereShiftId($shift->id)->first() ? true : false,
);
}
$link = button(url('/shifts', ['action' => 'view', 'shift_id' => $shift_id]), icon('chevron-left'), 'btn-sm', '', __('general.back'));
return page_with_title(
shifts_title(),
$link . ' ' . shifts_title(),
[
msg(),
'<noscript>'
@ -208,15 +214,18 @@ function shift_edit_controller()
. '</noscript>',
form([
form_select('shifttype_id', __('Shifttype'), $shifttypes, $shifttype_id),
form_text('title', __('Title'), $title),
form_select('rid', __('Room:'), $rooms, $rid),
form_text('title', __('title.title'), $title),
form_select('rid', __('Location:'), $locations, $rid),
form_text('start', __('Start:'), $start->format('Y-m-d H:i')),
form_text('end', __('End:'), $end->format('Y-m-d H:i')),
form_textarea('description', __('Additional description'), $description),
form_info('', __('This description is for single shifts, otherwise please use the description in shift type.')),
form_info(
'',
__('This description is for single shifts, otherwise please use the description in shift type.')
),
'<h2>' . __('Needed angels') . '</h2>',
$angel_types_spinner,
form_submit('submit', __('Save')),
form_submit('submit', icon('save') . __('form.save')),
]),
]
);
@ -230,18 +239,18 @@ function shift_delete_controller()
$request = request();
if (!auth()->can('user_shifts_admin')) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
// Schicht komplett löschen (nur für admins/user mit user_shifts_admin privileg)
if (!$request->has('delete_shift') || !preg_match('/^\d+$/', $request->input('delete_shift'))) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
$shift_id = $request->input('delete_shift');
$shift = Shift($shift_id);
if (empty($shift)) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
// Schicht löschen bestätigt
@ -254,7 +263,7 @@ function shift_delete_controller()
'name' => $shift->shiftType->name,
'title' => $shift->title,
'type' => $entry->angelType->name,
'room' => $shift->room,
'location' => $shift->location,
'freeloaded' => $entry->freeloaded,
]);
}
@ -267,21 +276,25 @@ function shift_delete_controller()
. ' to ' . $shift->end->format('Y-m-d H:i')
);
success(__('Shift deleted.'));
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
return page_with_title(shifts_title(), [
error(sprintf(
__('Do you want to delete the shift %s from %s to %s?'),
$shift->shiftType->name,
$shift->start->format(__('Y-m-d H:i')),
$shift->end->format(__('H:i'))
), true),
form([
form_hidden('delete_shift', $shift->id),
form_submit('delete', __('delete')),
]),
]);
$link = button(url('/shifts', ['action' => 'view', 'shift_id' => $shift_id]), icon('chevron-left'), 'btn-sm', '', __('general.back'));
return page_with_title(
$link . ' ' . shifts_title(),
[
error(sprintf(
__('Do you want to delete the shift %s from %s to %s?'),
$shift->shiftType->name,
$shift->start->format(__('general.datetime')),
$shift->end->format(__('H:i'))
), true),
form([
form_hidden('delete_shift', $shift->id),
form_submit('delete', icon('trash') . __('form.delete'), '', true, 'danger'),
]),
]
);
}
/**
@ -293,21 +306,21 @@ function shift_controller()
$request = request();
if (!auth()->can('user_shifts')) {
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
if (!$request->has('shift_id')) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
$shift = Shift($request->input('shift_id'));
if (empty($shift)) {
error(__('Shift could not be found.'));
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
$shifttype = $shift->shiftType;
$room = $shift->room;
$location = $shift->location;
/** @var AngelType[] $angeltypes */
$angeltypes = AngelType::all();
$user_shifts = Shifts_by_user($user->id);
@ -338,8 +351,8 @@ function shift_controller()
}
return [
$shift->shiftType->name,
Shift_view($shift, $shifttype, $room, $angeltypes, $shift_signup_state),
htmlspecialchars($shift->shiftType->name),
Shift_view($shift, $shifttype, $location, $angeltypes, $shift_signup_state),
];
}
@ -350,13 +363,13 @@ function shifts_controller()
{
$request = request();
if (!$request->has('action')) {
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}
return match ($request->input('action')) {
'view' => shift_controller(),
'next' => shift_next_controller(), // throws redirect
default => throw_redirect(page_link_to('/')),
default => throw_redirect(url('/')),
};
}
@ -366,7 +379,7 @@ function shifts_controller()
function shift_next_controller()
{
if (!auth()->can('user_shifts')) {
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
$upcoming_shifts = ShiftEntries_upcoming_for_user(auth()->user());
@ -375,5 +388,5 @@ function shift_next_controller()
throw_redirect(shift_link($upcoming_shifts[0]->shift));
}
throw_redirect(page_link_to('user_shifts'));
throw_redirect(url('/user-shifts'));
}

View File

@ -1,163 +0,0 @@
<?php
use Engelsystem\Models\Shifts\ShiftType;
/**
* @param ShiftType $shifttype
* @return string
*/
function shifttype_link(ShiftType $shifttype)
{
return page_link_to('shifttypes', ['action' => 'view', 'shifttype_id' => $shifttype->id]);
}
/**
* Delete a shifttype.
*
* @return array
*/
function shifttype_delete_controller()
{
$request = request();
if (!$request->has('shifttype_id')) {
throw_redirect(page_link_to('shifttypes'));
}
$shifttype = ShiftType::findOrFail($request->input('shifttype_id'));
if ($request->hasPostData('delete')) {
engelsystem_log('Deleted shifttype ' . $shifttype->name);
success(sprintf(__('Shifttype %s deleted.'), $shifttype->name));
$shifttype->delete();
throw_redirect(page_link_to('shifttypes'));
}
return [
sprintf(__('Delete shifttype %s'), $shifttype->name),
ShiftType_delete_view($shifttype),
];
}
/**
* Edit or create shift type.
*
* @return array
*/
function shifttype_edit_controller()
{
$shifttype_id = null;
$name = '';
$description = '';
$request = request();
if ($request->has('shifttype_id')) {
$shifttype = ShiftType::findOrFail($request->input('shifttype_id'));
$shifttype_id = $shifttype->id;
$name = $shifttype->name;
$description = $shifttype->description;
}
if ($request->hasPostData('submit')) {
$valid = true;
if ($request->has('name') && $request->input('name') != '') {
$name = strip_request_item('name');
} else {
$valid = false;
error(__('Please enter a name.'));
}
if ($request->has('description')) {
$description = strip_request_item_nl('description');
}
if ($valid) {
$shiftType = ShiftType::findOrNew($shifttype_id);
$shiftType->name = $name;
$shiftType->description = $description;
$shiftType->save();
if ($shifttype_id) {
engelsystem_log('Updated shifttype ' . $name);
success(__('Updated shifttype.'));
} else {
$shifttype_id = $shiftType->id;
engelsystem_log('Created shifttype ' . $name);
success(__('Created shifttype.'));
}
throw_redirect(page_link_to('shifttypes', ['action' => 'view', 'shifttype_id' => $shifttype_id]));
}
}
return [
shifttypes_title(),
ShiftType_edit_view($name, $description, $shifttype_id),
];
}
/**
* @return array
*/
function shifttype_controller()
{
$request = request();
if (!$request->has('shifttype_id')) {
throw_redirect(page_link_to('shifttypes'));
}
$shifttype = ShiftType::findOrFail($request->input('shifttype_id'));
return [
$shifttype->name,
ShiftType_view($shifttype),
];
}
/**
* List all shift types.
*
* @return array
*/
function shifttypes_list_controller()
{
$shifttypes = ShiftType::all();
return [
shifttypes_title(),
ShiftTypes_list_view($shifttypes),
];
}
/**
* Text for shift type related links.
*
* @return string
*/
function shifttypes_title()
{
return __('Shifttypes');
}
/**
* Route shift type actions
*
* @return array
*/
function shifttypes_controller()
{
$request = request();
$action = 'list';
if ($request->has('action')) {
$action = $request->input('action');
}
return match ($action) {
'view' => shifttype_controller(),
'edit' => shifttype_edit_controller(),
'delete' => shifttype_delete_controller(),
'list' => shifttypes_list_controller(),
default => shifttypes_list_controller(),
};
}

View File

@ -5,8 +5,6 @@ use Engelsystem\Models\AngelType;
use Engelsystem\Models\User\User;
use Engelsystem\Models\UserAngelType;
use Illuminate\Database\Eloquent\Collection;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Exception\TransportException;
/**
* Display a hint for team/angeltype supporters if there are unconfirmed users for his angeltype.
@ -37,9 +35,9 @@ function user_angeltypes_unconfirmed_hint()
$unconfirmed_links = [];
foreach ($unconfirmed_user_angeltypes as $user_angeltype) {
$unconfirmed_links[] = '<a class="text-info" href="'
. page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $user_angeltype->angel_type_id])
. '">' . $user_angeltype->angelType->name
$unconfirmed_links[] = '<a href="'
. url('/angeltypes', ['action' => 'view', 'angeltype_id' => $user_angeltype->angel_type_id])
. '">' . htmlspecialchars($user_angeltype->angelType->name)
. ' (+' . $user_angeltype->count . ')'
. '</a>';
}
@ -67,13 +65,13 @@ function user_angeltypes_delete_all_controller(): array
if (!$request->has('angeltype_id')) {
error(__('Angeltype doesn\'t exist.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
$angeltype = AngelType::findOrFail($request->input('angeltype_id'));
if (!auth()->user()->isAngelTypeSupporter($angeltype) && !auth()->can('admin_user_angeltypes')) {
error(__('You are not allowed to delete all users for this angeltype.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
if ($request->hasPostData('deny_all')) {
@ -83,7 +81,7 @@ function user_angeltypes_delete_all_controller(): array
engelsystem_log(sprintf('Denied all users for angeltype %s', AngelType_name_render($angeltype, true)));
success(sprintf(__('Denied all users for angeltype %s.'), $angeltype->name));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
}
return [
@ -104,13 +102,13 @@ function user_angeltypes_confirm_all_controller(): array
if (!$request->has('angeltype_id')) {
error(__('Angeltype doesn\'t exist.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
$angeltype = AngelType::findOrFail($request->input('angeltype_id'));
if (!auth()->can('admin_user_angeltypes') && !$user->isAngelTypeSupporter($angeltype)) {
error(__('You are not allowed to confirm all users for this angeltype.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
if ($request->hasPostData('confirm_all')) {
@ -127,7 +125,7 @@ function user_angeltypes_confirm_all_controller(): array
user_angeltype_confirm_email($user, $angeltype);
}
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
}
return [
@ -148,7 +146,7 @@ function user_angeltype_confirm_controller(): array
if (!$request->has('user_angeltype_id')) {
error(__('User angeltype doesn\'t exist.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
/** @var UserAngelType $user_angeltype */
@ -156,7 +154,7 @@ function user_angeltype_confirm_controller(): array
$angeltype = $user_angeltype->angelType;
if (!$user->isAngelTypeSupporter($angeltype) && !auth()->can('admin_user_angeltypes')) {
error(__('You are not allowed to confirm this users angeltype.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
$user_source = $user_angeltype->user;
@ -173,7 +171,7 @@ function user_angeltype_confirm_controller(): array
user_angeltype_confirm_email($user_source, $angeltype);
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
}
return [
@ -188,23 +186,14 @@ function user_angeltype_confirm_email(User $user, AngelType $angeltype): void
return;
}
try {
/** @var EngelsystemMailer $mailer */
$mailer = app(EngelsystemMailer::class);
$mailer->sendViewTranslated(
$user,
'notification.angeltype.confirmed',
'emails/angeltype-confirmed',
['name' => $angeltype->name, 'angeltype' => $angeltype, 'username' => $user->displayName]
);
} catch (TransportException $e) {
/** @var LoggerInterface $logger */
$logger = app('logger');
$logger->error(
'Unable to send email "{title}" to user {user} with {exception}',
['title' => __('notification.angeltype.confirmed'), 'user' => $user->name, 'exception' => $e]
);
}
/** @var EngelsystemMailer $mailer */
$mailer = app(EngelsystemMailer::class);
$mailer->sendViewTranslated(
$user,
'notification.angeltype.confirmed',
'emails/angeltype-confirmed',
['name' => $angeltype->name, 'angeltype' => $angeltype, 'username' => $user->displayName]
);
}
function user_angeltype_add_email(User $user, AngelType $angeltype): void
@ -213,23 +202,14 @@ function user_angeltype_add_email(User $user, AngelType $angeltype): void
return;
}
try {
/** @var EngelsystemMailer $mailer */
$mailer = app(EngelsystemMailer::class);
$mailer->sendViewTranslated(
$user,
'notification.angeltype.added',
'emails/angeltype-added',
['name' => $angeltype->name, 'angeltype' => $angeltype, 'username' => $user->displayName]
);
} catch (TransportException $e) {
/** @var LoggerInterface $logger */
$logger = app('logger');
$logger->error(
'Unable to send email "{title}" to user {user} with {exception}',
['title' => __('notification.angeltype.added'), 'user' => $user->name, 'exception' => $e]
);
}
/** @var EngelsystemMailer $mailer */
$mailer = app(EngelsystemMailer::class);
$mailer->sendViewTranslated(
$user,
'notification.angeltype.added',
'emails/angeltype-added',
['name' => $angeltype->name, 'angeltype' => $angeltype, 'username' => $user->displayName]
);
}
/**
@ -244,7 +224,7 @@ function user_angeltype_delete_controller(): array
if (!$request->has('user_angeltype_id')) {
error(__('User angeltype doesn\'t exist.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
/** @var UserAngelType $user_angeltype */
@ -257,7 +237,7 @@ function user_angeltype_delete_controller(): array
&& !auth()->can('admin_user_angeltypes')
) {
error(__('You are not allowed to delete this users angeltype.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
if ($request->hasPostData('delete')) {
@ -266,7 +246,7 @@ function user_angeltype_delete_controller(): array
engelsystem_log(sprintf('User %s removed from %s.', User_Nick_render($user_source, true), $angeltype->name));
success(sprintf(__('User %s removed from %s.'), $user_source->displayName, $angeltype->name));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
}
return [
@ -287,19 +267,19 @@ function user_angeltype_update_controller(): array
if (!auth()->can('admin_angel_types')) {
error(__('You are not allowed to set supporter rights.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
if (!$request->has('user_angeltype_id')) {
error(__('User angeltype doesn\'t exist.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
if ($request->has('supporter') && preg_match('/^[01]$/', $request->input('supporter'))) {
$supporter = $request->input('supporter') == '1';
} else {
error(__('No supporter update given.'));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
/** @var UserAngelType $user_angeltype */
@ -321,7 +301,7 @@ function user_angeltype_update_controller(): array
));
success(sprintf($msg, $angeltype->name, $user_source->displayName));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
}
return [
@ -382,7 +362,7 @@ function user_angeltype_add_controller(): array
user_angeltype_add_email($user_source, $angeltype);
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
}
}
@ -406,7 +386,7 @@ function user_angeltype_join_controller(AngelType $angeltype)
$user_angeltype = UserAngelType::whereUserId($user->id)->where('angel_type_id', $angeltype->id)->first();
if (!empty($user_angeltype)) {
error(sprintf(__('You are already a %s.'), $angeltype->name));
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
$request = request();
@ -434,11 +414,11 @@ function user_angeltype_join_controller(AngelType $angeltype)
));
}
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
}
return [
sprintf(__('Become a %s'), $angeltype->name),
sprintf(__('Become a %s'), htmlspecialchars($angeltype->name)),
UserAngelType_join_view($user, $angeltype),
];
}
@ -452,7 +432,7 @@ function user_angeltypes_controller(): array
{
$request = request();
if (!$request->has('action')) {
throw_redirect(page_link_to('angeltypes'));
throw_redirect(url('/angeltypes'));
}
return match ($request->input('action')) {
@ -462,6 +442,6 @@ function user_angeltypes_controller(): array
'delete' => user_angeltype_delete_controller(),
'update' => user_angeltype_update_controller(),
'add' => user_angeltype_add_controller(),
default => throw_redirect(page_link_to('angeltyps')),
default => throw_redirect(url('/angeltyps')),
};
}

View File

@ -1,143 +0,0 @@
<?php
use Engelsystem\Models\User\User;
/**
* Generates a hint, if user joined angeltypes that require a driving license and the user has no driver license
* information provided.
*
* @return string|null
*/
function user_driver_license_required_hint()
{
$user = auth()->user();
// User has already entered data, no hint needed.
if ($user->license->wantsToDrive()) {
return null;
}
$angeltypes = $user->userAngelTypes;
foreach ($angeltypes as $angeltype) {
if ($angeltype->requires_driver_license) {
return sprintf(
__('You joined an angeltype which requires a driving license. Please edit your driving license information here: %s.'),
'<a href="' . user_driver_license_edit_link() . '" class="text-info">' . __('driving license information') . '</a>'
);
}
}
return null;
}
/**
* Route user driver licenses actions.
*
* @return array
*/
function user_driver_licenses_controller()
{
$user = auth()->user();
if (!$user) {
throw_redirect(page_link_to());
}
$action = strip_request_item('action', 'edit');
return match ($action) {
'edit' => user_driver_license_edit_controller(),
default => user_driver_license_edit_controller(),
};
}
/**
* Link to user driver license edit page for given user.
*
* @param User $user
* @return string
*/
function user_driver_license_edit_link($user = null)
{
if (!$user) {
return page_link_to('user_driver_licenses');
}
return page_link_to('user_driver_licenses', ['user_id' => $user->id]);
}
/**
* Loads the user for the driver license.
*
* @return User
*/
function user_driver_license_load_user()
{
$request = request();
$user_source = auth()->user();
if ($request->has('user_id')) {
$user_source = User::find($request->input('user_id'));
if (empty($user_source)) {
throw_redirect(user_driver_license_edit_link());
}
}
return $user_source;
}
/**
* Edit a users driver license information.
*
* @return array
*/
function user_driver_license_edit_controller()
{
$user = auth()->user();
$request = request();
$user_source = user_driver_license_load_user();
// only privilege admin_user can edit other users driver license information
if ($user->id != $user_source->id && !auth()->can('admin_user')) {
throw_redirect(user_driver_license_edit_link());
}
$driverLicense = $user_source->license;
if ($request->hasPostData('submit')) {
if ($request->has('wants_to_drive')) {
$driverLicense->has_car = $request->has('has_car');
$driverLicense->drive_car = $request->has('has_license_car');
$driverLicense->drive_3_5t = $request->has('has_license_3_5t_transporter');
$driverLicense->drive_7_5t = $request->has('has_license_7_5t_truck');
$driverLicense->drive_12t = $request->has('has_license_12t_truck');
$driverLicense->drive_forklift = $request->has('has_license_forklift');
if ($driverLicense->wantsToDrive()) {
$driverLicense->save();
engelsystem_log('Driver license information updated.');
success(__('Your driver license information has been saved.'));
throw_redirect(user_link($user_source->id));
} else {
error(__('Please select at least one driving license.'));
}
} else {
$driverLicense->has_car = false;
$driverLicense->drive_car = false;
$driverLicense->drive_3_5t = false;
$driverLicense->drive_7_5t = false;
$driverLicense->drive_12t = false;
$driverLicense->drive_forklift = false;
$driverLicense->save();
engelsystem_log('Driver license information removed.');
success(__('Your driver license information has been removed.'));
throw_redirect(user_link($user_source->id));
}
}
return [
sprintf(__('Edit %s driving license information'), $user_source->displayName),
UserDriverLicense_edit_view($user_source, $driverLicense),
];
}

View File

@ -20,7 +20,7 @@ function users_controller()
$request = request();
if (!$user) {
throw_redirect(page_link_to());
throw_redirect(url('/'));
}
$action = 'list';
@ -55,7 +55,7 @@ function user_delete_controller()
}
if (!auth()->can('admin_user')) {
throw_redirect(page_link_to());
throw_redirect(url('/'));
}
// You cannot delete yourself
@ -91,7 +91,7 @@ function user_delete_controller()
}
return [
sprintf(__('Delete %s'), $user_source->displayName),
sprintf(__('Delete %s'), htmlspecialchars($user_source->displayName)),
User_delete_view($user_source),
];
}
@ -101,7 +101,7 @@ function user_delete_controller()
*/
function users_link()
{
return page_link_to('users');
return url('/users');
}
/**
@ -110,7 +110,7 @@ function users_link()
*/
function user_edit_link($userId)
{
return page_link_to('admin_user', ['user_id' => $userId]);
return url('/admin-user', ['user_id' => $userId]);
}
/**
@ -119,7 +119,7 @@ function user_edit_link($userId)
*/
function user_delete_link($userId)
{
return page_link_to('users', ['action' => 'delete', 'user_id' => $userId]);
return url('/users', ['action' => 'delete', 'user_id' => $userId]);
}
/**
@ -128,7 +128,7 @@ function user_delete_link($userId)
*/
function user_link($userId)
{
return page_link_to('users', ['action' => 'view', 'user_id' => $userId]);
return url('/users', ['action' => 'view', 'user_id' => $userId]);
}
/**
@ -149,7 +149,7 @@ function user_edit_vouchers_controller()
(!auth()->can('admin_user') && !auth()->can('voucher.edit'))
|| !config('enable_voucher')
) {
throw_redirect(page_link_to());
throw_redirect(url('/'));
}
if ($request->hasPostData('submit')) {
@ -182,7 +182,7 @@ function user_edit_vouchers_controller()
}
return [
sprintf(__('%s\'s vouchers'), $user_source->displayName),
sprintf(__('%s\'s vouchers'), htmlspecialchars($user_source->displayName)),
User_edit_vouchers_view($user_source),
];
}
@ -200,7 +200,7 @@ function user_controller()
$user_source = User::find($request->input('user_id'));
if (!$user_source) {
error(__('User not found.'));
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
}
@ -234,7 +234,7 @@ function user_controller()
}
if (empty($user_source->api_key)) {
User_reset_api_key($user_source, false);
auth()->resetApiKey($user_source);
}
if ($user_source->state->force_active) {
@ -244,7 +244,7 @@ function user_controller()
}
return [
$user_source->displayName,
htmlspecialchars($user_source->displayName),
User_view(
$user_source,
auth()->can('admin_user'),
@ -271,7 +271,7 @@ function users_list_controller()
$request = request();
if (!auth()->can('admin_user')) {
throw_redirect(page_link_to());
throw_redirect(url('/'));
}
$order_by = 'name';
@ -343,13 +343,13 @@ function load_user()
{
$request = request();
if (!$request->has('user_id')) {
throw_redirect(page_link_to());
throw_redirect(url('/'));
}
$user = User::find($request->input('user_id'));
if (!$user) {
error(__('User doesn\'t exist.'));
throw_redirect(page_link_to());
throw_redirect(url('/'));
}
return $user;
@ -437,3 +437,57 @@ function shiftCalendarRendererByShiftFilter(ShiftsFilter $shiftsFilter)
return new ShiftCalendarRenderer($filtered_shifts, $needed_angeltypes, $shift_entries, $shiftsFilter);
}
/**
* Generates a hint, if user joined angeltypes that require a driving license and the user has no driver license
* information provided.
*
* @return string|null
*/
function user_driver_license_required_hint()
{
$user = auth()->user();
// User has already entered data, no hint needed.
if ($user->license->wantsToDrive()) {
return null;
}
$angeltypes = $user->userAngelTypes;
foreach ($angeltypes as $angeltype) {
if ($angeltype->requires_driver_license) {
return sprintf(
__('angeltype.driving_license.required.info.here'),
'<a href="' . url('/settings/certificates') . '">' . __('driving_license.info') . '</a>'
);
}
}
return null;
}
function user_ifsg_certificate_required_hint()
{
$user = auth()->user();
// User has already entered data, no hint needed.
if (!config('ifsg_enabled') || $user->license->ifsg_light || $user->license->ifsg) {
return null;
}
$angeltypes = $user->userAngelTypes;
foreach ($angeltypes as $angeltype) {
if (
$angeltype->requires_ifsg_certificate && !(
$user->license->ifsg_certificate || $user->license->ifsg_certificate_light
)
) {
return sprintf(
__('angeltype.ifsg.required.info.here'),
'<a href="' . url('/settings/certificates') . '">' . __('ifsg.info') . '</a>'
);
}
}
return null;
}

View File

@ -23,7 +23,7 @@ if ($app->get('config')->get('maintenance')) {
http_response_code(503);
$url = $app->get(UrlGeneratorInterface::class);
$maintenance = file_get_contents(__DIR__ . '/../resources/views/layouts/maintenance.html');
$maintenance = str_replace('%APP_NAME%', $app->get('config')->get('app_name'), $maintenance);
$maintenance = str_replace('%APP_NAME%', htmlspecialchars($app->get('config')->get('app_name')), $maintenance);
$maintenance = str_replace('%ASSETS_PATH%', $url->to(''), $maintenance);
echo $maintenance;
die();

View File

@ -1,9 +1,7 @@
<?php
use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Mail\EngelsystemMailer;
use Engelsystem\Models\User\User;
use Psr\Log\LogLevel;
/**
* @param User $recipientUser
@ -18,38 +16,18 @@ function engelsystem_email_to_user($recipientUser, $title, $message, $notIfItsMe
return true;
}
/** @var Translator $translator */
$translator = app()->get('translator');
$locale = $translator->getLocale();
$status = true;
try {
/** @var EngelsystemMailer $mailer */
$mailer = app('mailer');
$translator->setLocale($recipientUser->settings->language);
$mailer->sendView(
$recipientUser->contact->email ?: $recipientUser->email,
$title,
'emails/mail',
['username' => $recipientUser->displayName, 'message' => $message]
);
} catch (Exception $e) {
$status = false;
engelsystem_log(sprintf(
'An exception occurred while sending a mail to %s in %s:%u: %s',
$recipientUser->name,
$e->getFile(),
$e->getLine(),
$e->getMessage()
), LogLevel::CRITICAL);
}
$translator->setLocale($locale);
/** @var EngelsystemMailer $mailer */
$mailer = app('mailer');
$status = $mailer->sendViewTranslated(
$recipientUser,
$title,
'emails/mail',
['username' => $recipientUser->displayName, 'message' => $message]
);
if (!$status) {
error(sprintf(__('User %s could not be notified by email due to an error.'), $recipientUser->displayName));
engelsystem_log(sprintf('User %s could not be notified by email due to an error.', $recipientUser->name));
error(sprintf(__('User %s could not be notified by e-mail due to an error.'), $recipientUser->displayName));
engelsystem_log(sprintf('User %s could not be notified by e-mail due to an error.', $recipientUser->name));
}
return $status;

View File

@ -3,6 +3,8 @@
declare(strict_types=1);
use Engelsystem\Renderer\Twig\Extensions\Globals;
use Engelsystem\Helpers\Carbon;
use Engelsystem\Helpers\DayOfEvent;
function theme_id(): int
{
@ -25,3 +27,16 @@ function theme_type(): string
{
return theme()['type'];
}
function dateWithEventDay(string $day): string
{
$date = Carbon::createFromFormat('Y-m-d', $day);
$dayOfEvent = DayOfEvent::get($date);
$dateFormatted = $date->format(__('general.date'));
if (!config('enable_show_day_of_event') || is_null($dayOfEvent)) {
return $dateFormatted;
}
return $dateFormatted . ' (' . $dayOfEvent . ')';
}

View File

@ -17,11 +17,12 @@ function msg()
*
* @param string $msg
* @param bool $immediately
* @param bool $immediatelyRaw
* @return string
*/
function info($msg, $immediately = false)
function info($msg, $immediately = false, $immediatelyRaw = false)
{
return alert(NotificationType::INFORMATION, $msg, $immediately);
return alert(NotificationType::INFORMATION, $msg, $immediately, $immediatelyRaw);
}
/**
@ -29,11 +30,12 @@ function info($msg, $immediately = false)
*
* @param string $msg
* @param bool $immediately
* @param bool $immediatelyRaw
* @return string
*/
function warning($msg, $immediately = false)
function warning($msg, $immediately = false, $immediatelyRaw = false)
{
return alert(NotificationType::WARNING, $msg, $immediately);
return alert(NotificationType::WARNING, $msg, $immediately, $immediatelyRaw);
}
/**
@ -41,11 +43,12 @@ function warning($msg, $immediately = false)
*
* @param string $msg
* @param bool $immediately
* @param bool $immediatelyRaw
* @return string
*/
function error($msg, $immediately = false)
function error($msg, $immediately = false, $immediatelyRaw = false)
{
return alert(NotificationType::ERROR, $msg, $immediately);
return alert(NotificationType::ERROR, $msg, $immediately, $immediatelyRaw);
}
/**
@ -53,24 +56,27 @@ function error($msg, $immediately = false)
*
* @param string $msg
* @param bool $immediately
* @param bool $immediatelyRaw
* @return string
*/
function success($msg, $immediately = false)
function success($msg, $immediately = false, $immediatelyRaw = false)
{
return alert(NotificationType::MESSAGE, $msg, $immediately);
return alert(NotificationType::MESSAGE, $msg, $immediately, $immediatelyRaw);
}
/**
* Renders an alert message with the given alert-* class or sets it in session
*
* @see \Engelsystem\Controllers\HasUserNotifications
*
* @param NotificationType $type
* @param string $msg
* @param bool $immediately
* @param bool $immediatelyRaw
* @return string
*
* @see \Engelsystem\Controllers\HasUserNotifications
*
*/
function alert(NotificationType $type, $msg, $immediately = false)
function alert(NotificationType $type, $msg, $immediately = false, $immediatelyRaw = false)
{
if (empty($msg)) {
return '';
@ -87,6 +93,7 @@ function alert(NotificationType $type, $msg, $immediately = false)
['danger', 'warning', 'info', 'success'],
$type->value
);
$msg = $immediatelyRaw ? $msg : htmlspecialchars($msg);
return '<div class="alert alert-' . $type . '" role="alert">' . $msg . '</div>';
}

View File

@ -5,11 +5,13 @@ namespace Engelsystem\Events\Listener;
use Carbon\Carbon;
use Engelsystem\Helpers\Shifts;
use Engelsystem\Mail\EngelsystemMailer;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\Shift as ShiftModel;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Engelsystem\Models\Worklog;
use Illuminate\Database\Eloquent\Collection;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Exception\TransportException;
class Shift
{
@ -26,7 +28,7 @@ class Shift
string $name,
string $title,
string $type,
Room $room,
Location $location,
bool $freeloaded
): void {
if ($freeloaded || $start > Carbon::now()) {
@ -45,9 +47,9 @@ class Shift
$name,
$title,
$type,
$room->name,
$start->format(__('Y-m-d H:i')),
$end->format(__('Y-m-d H:i'))
$location->name,
$start->format(__('general.datetime')),
$end->format(__('general.datetime'))
);
$workLog->save();
@ -64,34 +66,70 @@ class Shift
string $name,
string $title,
string $type,
Room $room,
Location $location,
bool $freeloaded
): void {
if (!$user->settings->email_shiftinfo) {
return;
}
$subject = 'notification.shift.deleted';
try {
$this->mailer->sendViewTranslated(
$user,
'notification.shift.deleted',
'emails/worklog-from-shift',
[
'name' => $name,
'title' => $title,
'start' => $start,
'end' => $end,
'location' => $location,
'freeloaded' => $freeloaded,
'username' => $user->displayName,
]
);
}
public function updatedShiftSendEmail(
ShiftModel $shift,
ShiftModel $oldShift
): void {
// Only send e-mail on relevant changes
if (
$oldShift->shift_type_id == $shift->shift_type_id
&& $oldShift->title == $shift->title
&& $oldShift->start == $shift->start
&& $oldShift->end == $shift->end
&& $oldShift->location_id == $shift->location_id
) {
return;
}
$shift->load(['shiftType', 'location']);
$oldShift->load(['shiftType', 'location']);
/** @var ShiftEntry[]|Collection $shiftEntries */
$shiftEntries = $shift->shiftEntries()
->with(['angelType', 'user.settings'])
->get();
foreach ($shiftEntries as $shiftEntry) {
$user = $shiftEntry->user;
$angelType = $shiftEntry->angelType;
if (!$user->settings->email_shiftinfo || $shift->end < Carbon::now()) {
continue;
}
$this->mailer->sendViewTranslated(
$user,
$subject,
'emails/worklog-from-shift',
'notification.shift.updated',
'emails/updated-shift',
[
'name' => $name,
'title' => $title,
'start' => $start,
'end' => $end,
'room' => $room,
'freeloaded' => $freeloaded,
'username' => $user->displayName,
'shift' => $shift,
'oldShift' => $oldShift,
'angelType' => $angelType,
'username' => $user->displayName,
]
);
} catch (TransportException $e) {
$this->log->error(
'Unable to send email "{title}" to user {user} with {exception}',
['title' => $subject, 'user' => $user->name, 'exception' => $e]
);
}
}
}

View File

@ -24,29 +24,25 @@ $includeFiles = [
__DIR__ . '/../includes/view/AngelTypes_view.php',
__DIR__ . '/../includes/view/EventConfig_view.php',
__DIR__ . '/../includes/view/PublicDashboard_view.php',
__DIR__ . '/../includes/view/Rooms_view.php',
__DIR__ . '/../includes/view/Locations_view.php',
__DIR__ . '/../includes/view/ShiftCalendarLane.php',
__DIR__ . '/../includes/view/ShiftCalendarRenderer.php',
__DIR__ . '/../includes/view/ShiftCalendarShiftRenderer.php',
__DIR__ . '/../includes/view/ShiftsFilterRenderer.php',
__DIR__ . '/../includes/view/Shifts_view.php',
__DIR__ . '/../includes/view/ShiftEntry_view.php',
__DIR__ . '/../includes/view/ShiftTypes_view.php',
__DIR__ . '/../includes/view/UserAngelTypes_view.php',
__DIR__ . '/../includes/view/UserDriverLicenses_view.php',
__DIR__ . '/../includes/view/UserHintsRenderer.php',
__DIR__ . '/../includes/view/User_view.php',
__DIR__ . '/../includes/controller/angeltypes_controller.php',
__DIR__ . '/../includes/controller/event_config_controller.php',
__DIR__ . '/../includes/controller/public_dashboard_controller.php',
__DIR__ . '/../includes/controller/rooms_controller.php',
__DIR__ . '/../includes/controller/locations_controller.php',
__DIR__ . '/../includes/controller/shift_entries_controller.php',
__DIR__ . '/../includes/controller/shifts_controller.php',
__DIR__ . '/../includes/controller/shifttypes_controller.php',
__DIR__ . '/../includes/controller/users_controller.php',
__DIR__ . '/../includes/controller/user_angeltypes_controller.php',
__DIR__ . '/../includes/controller/user_driver_licenses_controller.php',
__DIR__ . '/../includes/helper/legacy_helper.php',
__DIR__ . '/../includes/helper/message_helper.php',
@ -62,7 +58,6 @@ $includeFiles = [
__DIR__ . '/../includes/pages/admin_groups.php',
__DIR__ . '/../includes/pages/admin_shifts.php',
__DIR__ . '/../includes/pages/admin_user.php',
__DIR__ . '/../includes/pages/guest_login.php',
__DIR__ . '/../includes/pages/user_myshifts.php',
__DIR__ . '/../includes/pages/user_shifts.php',

View File

@ -1,87 +1,7 @@
<?php
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Collection;
function mail_shift_change(Shift $old_shift, Shift $new_shift)
{
/** @var ShiftEntry[]|Collection $shiftEntries */
$shiftEntries = $old_shift->shiftEntries()
->with(['user', 'user.settings'])
->get();
$old_room = $old_shift->room;
$new_room = $new_shift->room;
$noticeable_changes = false;
$message = __('A Shift you are registered on has changed:');
$message .= "\n";
if ($old_shift->shift_type_id != $new_shift->shift_type_id) {
$message .= sprintf(
__('* Shift type changed from %s to %s'),
$old_shift->shiftType->name,
$new_shift->shiftType->name
) . "\n";
$noticeable_changes = true;
}
if ($old_shift->title != $new_shift->title) {
$message .= sprintf(__('* Shift title changed from %s to %s'), $old_shift->title, $new_shift->title) . "\n";
$noticeable_changes = true;
}
if ($old_shift->start->timestamp != $new_shift->start->timestamp) {
$message .= sprintf(
__('* Shift Start changed from %s to %s'),
$old_shift->start->format(__('Y-m-d H:i')),
$new_shift->start->format(__('Y-m-d H:i'))
) . "\n";
$noticeable_changes = true;
}
if ($old_shift->end->timestamp != $new_shift->end->timestamp) {
$message .= sprintf(
__('* Shift End changed from %s to %s'),
$old_shift->end->format(__('Y-m-d H:i')),
$new_shift->end->format(__('Y-m-d H:i'))
) . "\n";
$noticeable_changes = true;
}
if ($old_shift->room_id != $new_shift->room_id) {
$message .= sprintf(__('* Shift Location changed from %s to %s'), $old_room->name, $new_room->name) . "\n";
$noticeable_changes = true;
}
if (!$noticeable_changes) {
// There are no changes worth sending an E-Mail
return;
}
$message .= "\n";
$message .= __('The updated Shift:') . "\n";
$message .= $new_shift->shiftType->name . "\n";
$message .= $new_shift->title . "\n";
$message .= $new_shift->start->format(__('Y-m-d H:i')) . ' - ' . $new_shift->end->format(__('H:i')) . "\n";
$message .= $new_room->name . "\n\n";
$message .= url('/shifts', ['action' => 'view', 'shift_id' => $new_shift->id]) . "\n";
foreach ($shiftEntries as $shiftEntry) {
$user = $shiftEntry->user;
if ($user->settings->email_shiftinfo) {
engelsystem_email_to_user(
$user,
__('Your Shift has changed'),
$message,
true
);
}
}
}
function mail_shift_assign(User $user, Shift $shift)
{
@ -89,13 +9,11 @@ function mail_shift_assign(User $user, Shift $shift)
return;
}
$room = $shift->room;
$message = __('You have been assigned to a Shift:') . "\n";
$message .= $shift->shiftType->name . "\n";
$message .= $shift->title . "\n";
$message .= $shift->start->format(__('Y-m-d H:i')) . ' - ' . $shift->end->format(__('H:i')) . "\n";
$message .= $room->name . "\n\n";
$message .= $shift->start->format(__('general.datetime')) . ' - ' . $shift->end->format(__('H:i')) . "\n";
$message .= $shift->location->name . "\n\n";
$message .= url('/shifts', ['action' => 'view', 'shift_id' => $shift->id]) . "\n";
engelsystem_email_to_user($user, __('Assigned to Shift'), $message, true);
@ -107,13 +25,11 @@ function mail_shift_removed(User $user, Shift $shift)
return;
}
$room = $shift->room;
$message = __('You have been removed from a Shift:') . "\n";
$message .= $shift->shiftType->name . "\n";
$message .= $shift->title . "\n";
$message .= $shift->start->format(__('Y-m-d H:i')) . ' - ' . $shift->end->format(__('H:i')) . "\n";
$message .= $room->name . "\n";
$message .= $shift->start->format(__('general.datetime')) . ' - ' . $shift->end->format(__('H:i')) . "\n";
$message .= $shift->location->name . "\n";
engelsystem_email_to_user($user, __('Removed from Shift'), $message, true);
}

View File

@ -10,7 +10,7 @@ function mail_user_delete($user)
{
return engelsystem_email_to_user(
$user,
__('Your account has been deleted'),
__('Your account has been deleted.'),
__(
'Your %s account has been deleted. If you have any questions regarding your account deletion, please contact heaven.',
[config('app_name')]

View File

@ -1,47 +1,70 @@
<?php
use Engelsystem\Database\Db;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Illuminate\Database\Eloquent\Collection;
/**
* Returns all needed angeltypes and already taken needs.
*
* @param int $shiftId id of shift
* @param Shift $shift
* @return array
*/
function NeededAngelTypes_by_shift($shiftId)
function NeededAngelTypes_by_shift($shift)
{
$needed_angeltypes_source = Db::select(
'
$needed_angeltypes_source = [];
// Select from shift
if (!$shift->schedule) {
$needed_angeltypes_source = Db::select(
'
SELECT
`needed_angel_types`.*,
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`no_self_signup`
`angel_types`.`shift_self_signup`
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id`
WHERE `shift_id` = ?
ORDER BY `room_id` DESC',
[$shiftId]
);
WHERE `needed_angel_types`.`shift_id` = ?
ORDER BY `location_id` DESC
',
[$shift->id]
);
}
// Use settings from room
if (count($needed_angeltypes_source) == 0) {
// Get needed by shift type
if ($shift->schedule && $shift->schedule->needed_from_shift_type) {
$needed_angeltypes_source = Db::select('
SELECT `needed_angel_types`.*, `angel_types`.`name`, `angel_types`.`restricted`
SELECT
`needed_angel_types`.*,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id`
JOIN `shifts` ON `shifts`.`room_id` = `needed_angel_types`.`room_id`
WHERE `shifts`.`id` = ?
ORDER BY `room_id` DESC
', [$shiftId]);
WHERE `needed_angel_types`.`shift_type_id` = ?
ORDER BY `location_id` DESC
', [$shift->shift_type_id]);
}
// Load from room
if ($shift->schedule && !$shift->schedule->needed_from_shift_type) {
$needed_angeltypes_source = Db::select('
SELECT
`needed_angel_types`.*,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id`
WHERE `needed_angel_types`.`location_id` = ?
ORDER BY `location_id` DESC
', [$shift->location_id]);
}
/** @var ShiftEntry[]|Collection $shift_entries */
$shift_entries = ShiftEntry::with('user', 'angelType')
->where('shift_id', $shiftId)
->where('shift_id', $shift->id)
->get();
$needed_angeltypes = [];
foreach ($needed_angeltypes_source as $angeltype) {

View File

@ -15,7 +15,7 @@ function ShiftEntry_onCreate(ShiftEntry $shiftEntry): void
'User ' . User_Nick_render($shiftEntry->user, true)
. ' signed up for shift ' . $shiftEntry->shift->title
. ' (' . $shift->shiftType->name . ')'
. ' at ' . $shift->room->name
. ' at ' . $shift->location->name
. ' from ' . $shift->start->format('Y-m-d H:i')
. ' to ' . $shift->end->format('Y-m-d H:i')
. ' as ' . $shiftEntry->angelType->name
@ -33,14 +33,14 @@ function ShiftEntry_onDelete(ShiftEntry $shiftEntry)
$signout_user = $shiftEntry->user;
$shift = Shift($shiftEntry->shift);
$shifttype = $shift->shiftType;
$room = $shift->room;
$location = $shift->location;
$angeltype = $shiftEntry->angelType;
engelsystem_log(
'Shift signout: ' . User_Nick_render($signout_user, true)
. ' from shift ' . $shift->title
. ' (' . $shifttype->name . ')'
. ' at ' . $room->name
. ' at ' . $location->name
. ' from ' . $shift->start->format('Y-m-d H:i')
. ' to ' . $shift->end->format('Y-m-d H:i')
. ' as ' . $angeltype->name

View File

@ -44,10 +44,10 @@ class ShiftsFilter
* ShiftsFilter constructor.
*
* @param bool $user_shifts_admin
* @param int[] $rooms
* @param int[] $locations
* @param int[] $angelTypes
*/
public function __construct($user_shifts_admin = false, private $rooms = [], $angelTypes = [])
public function __construct($user_shifts_admin = false, private $locations = [], $angelTypes = [])
{
$this->types = $angelTypes;
@ -68,7 +68,7 @@ class ShiftsFilter
return [
'userShiftsAdmin' => $this->userShiftsAdmin,
'filled' => $this->filled,
'rooms' => $this->rooms,
'locations' => $this->locations,
'types' => $this->types,
'startTime' => $this->startTime,
'endTime' => $this->endTime,
@ -80,12 +80,12 @@ class ShiftsFilter
*/
public function sessionImport($data)
{
$this->userShiftsAdmin = $data['userShiftsAdmin'];
$this->filled = $data['filled'];
$this->rooms = $data['rooms'];
$this->types = $data['types'];
$this->startTime = $data['startTime'];
$this->endTime = $data['endTime'];
$this->userShiftsAdmin = $data['userShiftsAdmin'] ?? false;
$this->filled = $data['filled'] ?? [];
$this->locations = $data['locations'] ?? [];
$this->types = $data['types'] ?? [];
$this->startTime = $data['startTime'] ?? null;
$this->endTime = $data['endTime'] ?? null;
}
/**
@ -163,20 +163,20 @@ class ShiftsFilter
/**
* @return int[]
*/
public function getRooms()
public function getLocations()
{
if (count($this->rooms) == 0) {
if (count($this->locations) == 0) {
return [0];
}
return $this->rooms;
return $this->locations;
}
/**
* @param int[] $rooms
* @param int[] $locations
*/
public function setRooms($rooms)
public function setLocations($locations)
{
$this->rooms = $rooms;
$this->locations = $locations;
}
/**

View File

@ -28,12 +28,26 @@ function Shifts_by_angeltype(AngelType $angeltype)
UNION
/* By shift type */
SELECT DISTINCT `shifts`.* FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`room_id` = `shifts`.`room_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id` = `shifts`.`shift_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `needed_angel_types`.`angel_type_id` = ?
AND NOT s.shift_id IS NULL
', [$angeltype->id, $angeltype->id]);
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT DISTINCT `shifts`.* FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id` = `shifts`.`location_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `needed_angel_types`.`angel_type_id` = ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
', [$angeltype->id, $angeltype->id, $angeltype->id]);
}
/**
@ -53,25 +67,42 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
$shifts = Db::select('
SELECT *
FROM (
SELECT id, start
SELECT shifts.id, start
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`shift_id`=`shifts`.`id`' . ($filter ? ' AND needed_angel_types.angel_type_id 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()) . ')' : '') . '
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION
SELECT id, start
/* By shift type */
SELECT shifts.id, start
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`room_id`=`shifts`.`room_id`' . ($filter ? ' AND needed_angel_types.angel_type_id 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 (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`' . ($filter ? ' AND needed_angel_types.angel_type_id 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 NOT s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION
/* By location */
SELECT shifts.id, start
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`location_id`=`shifts`.`location_id`' . ($filter ? ' AND needed_angel_types.angel_type_id 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 NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp`
ORDER BY `tmp`.`start`
', [
@ -79,6 +110,8 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
$end,
$start,
$end,
$start,
$end,
]);
$shifts = collect($shifts);
@ -97,32 +130,51 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
{
$sql = '
SELECT * FROM (
SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `rooms`.`name` AS `room_name`
SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `locations`.`name` AS `location_name`
FROM `shifts`
JOIN `rooms` ON `shifts`.`room_id` = `rooms`.`id`
JOIN `locations` ON `shifts`.`location_id` = `locations`.`id`
JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_id` = `shifts`.`id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `shifts`.`room_id` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND `start` BETWEEN ? AND ?
AND `needed_angel_types`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ')
AND s.shift_id IS NULL
UNION
SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `rooms`.`name` AS `room_name`
/* By shift type */
SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `locations`.`name` AS `location_name`
FROM `shifts`
JOIN `rooms` ON `shifts`.`room_id` = `rooms`.`id`
JOIN `locations` ON `shifts`.`location_id` = `locations`.`id`
JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`room_id`=`shifts`.`room_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `shifts`.`room_id` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND `start` BETWEEN ? AND ?
AND `needed_angel_types`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ')
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `locations`.`name` AS `location_name`
FROM `shifts`
JOIN `locations` ON `shifts`.`location_id` = `locations`.`id`
JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND `start` BETWEEN ? AND ?
AND `needed_angel_types`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ')
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
) AS tmp_shifts
ORDER BY `room_name`, `start`
ORDER BY `location_name`, `start`
';
$shiftsData = Db::select(
@ -132,6 +184,8 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
$shiftsFilter->getEnd(),
$shiftsFilter->getStart(),
$shiftsFilter->getEnd(),
$shiftsFilter->getStart(),
$shiftsFilter->getEnd(),
]
);
@ -156,31 +210,54 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`no_self_signup`
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_id`=`shifts`.`id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `shifts`.`room_id` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND shifts.`start` BETWEEN ? AND ?
AND s.shift_id IS NULL
UNION
/* By shift type */
SELECT
`needed_angel_types`.*,
`shifts`.`id` AS shift_id,
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`no_self_signup`
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`room_id`=`shifts`.`room_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `shifts`.`room_id` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND shifts.`start` BETWEEN ? AND ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT
`needed_angel_types`.*,
`shifts`.`id` AS shift_id,
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND shifts.`start` BETWEEN ? AND ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
';
return Db::select(
@ -190,6 +267,8 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
$shiftsFilter->getEnd(),
$shiftsFilter->getStart(),
$shiftsFilter->getEnd(),
$shiftsFilter->getStart(),
$shiftsFilter->getEnd(),
]
);
}
@ -209,7 +288,7 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`no_self_signup`
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_id`=`shifts`.`id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
@ -220,26 +299,51 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty
UNION
/* By shift type */
SELECT
`needed_angel_types`.*,
`shifts`.`id` AS shift_id,
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`no_self_signup`
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`room_id`=`shifts`.`room_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`id`=?
AND `angel_types`.`id`=?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT
`needed_angel_types`.*,
`shifts`.`id` AS shift_id,
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`id`=?
AND `angel_types`.`id`=?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
',
[
$shift->id,
$angeltype->id,
$shift->id,
$angeltype->id,
$shift->id,
$angeltype->id,
]
);
}
@ -252,7 +356,7 @@ function ShiftEntries_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
{
return ShiftEntry::with('user')
->join('shifts', 'shifts.id', 'shift_entries.shift_id')
->whereIn('shifts.room_id', $shiftsFilter->getRooms())
->whereIn('shifts.location_id', $shiftsFilter->getLocations())
->whereBetween('start', [$shiftsFilter->getStart(), $shiftsFilter->getEnd()])
->get();
}
@ -368,12 +472,12 @@ function Shift_signup_allowed_angel(
if (
empty($user_angeltype)
|| $angeltype->no_self_signup == 1
|| ($angeltype->restricted == 1 && !isset($user_angeltype['confirm_user_id']))
|| !$angeltype->shift_self_signup
|| ($angeltype->restricted && !isset($user_angeltype['confirm_user_id']))
) {
// you cannot join if user is not of this angel type
// you cannot join if angeltype has shift self signup disabled
// you cannot join if you are not confirmed
// you cannot join if angeltype has no self signup
return new ShiftSignupState(ShiftSignupStatus::ANGELTYPE, $free_entries);
}
@ -510,8 +614,8 @@ function Shifts_by_user($userId, $include_freeloaded_comments = false)
$shiftsData = Db::select(
'
SELECT
`rooms`.*,
`rooms`.name AS Name,
`locations`.*,
`locations`.name AS Name,
`shift_types`.`id` AS `shifttype_id`,
`shift_types`.`name`,
`shift_entries`.`id` as shift_entry_id,
@ -525,7 +629,7 @@ function Shifts_by_user($userId, $include_freeloaded_comments = false)
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`)
JOIN `locations` ON (`shifts`.`location_id` = `locations`.`id`)
WHERE shift_entries.`user_id` = ?
ORDER BY `start`
',
@ -559,7 +663,7 @@ function Shift($shift)
}
$neededAngels = [];
$angelTypes = NeededAngelTypes_by_shift($shift->id);
$angelTypes = NeededAngelTypes_by_shift($shift);
foreach ($angelTypes as $type) {
$neededAngels[] = [
'angel_type_id' => $type['angel_type_id'],

View File

@ -24,7 +24,7 @@ function stats_currently_working(ShiftsFilter $filter = null)
)) AS `count`
FROM `shifts`
WHERE (`end` >= NOW() AND `start` <= NOW())
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '')
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '')
);
return $result['count'] ?: '-';
@ -47,20 +47,37 @@ function stats_hours_to_work(ShiftsFilter $filter = null)
* TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `shifts`.`end` >= NOW()
WHERE shifts.`end` >= NOW()
AND s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By shift type */
SELECT
(SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`room_id`=`shifts`.`room_id`' . ($filter ? ' AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
(SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`' . ($filter ? ' AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
* TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE shifts.`end` >= NOW()
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` >= NOW()
AND NOT s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By location */
SELECT
(SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`location_id`=`shifts`.`location_id`' . ($filter ? ' AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
* TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` >= NOW()
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp`
'
);
@ -90,7 +107,8 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
AND `needed_angel_types`.`shift_id`=`shifts`.`id`
' . ($filter ? 'AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
SELECT COUNT(*) FROM `shift_entries`
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 `shift_entries`.`shift_id`=`shifts`.`id`
@ -103,10 +121,11 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE shifts.`end` > NOW() AND shifts.`start` < ?
AND s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By shift type */
SELECT
GREATEST(0,
(
@ -114,7 +133,7 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id`=`needed_angel_types`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `needed_angel_types`.`room_id`=`shifts`.`room_id`
AND `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
' . ($filter ? 'AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
SELECT COUNT(*)
@ -129,12 +148,46 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `end` > NOW() AND `start` < ?
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > NOW() AND shifts.`start` < ?
AND NOT s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By location */
SELECT
GREATEST(0,
(
SELECT SUM(needed_angel_types.`count`)
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id`=`needed_angel_types`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `needed_angel_types`.`location_id`=`shifts`.`location_id`
' . ($filter ? 'AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
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 `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > NOW() AND shifts.`start` < ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp`', [
$in3hours,
$in3hours,
$in3hours,
]);
return $result['count'] ?: '-';
@ -185,10 +238,11 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null)
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE shifts.`end` > ? AND shifts.`start` < ?
AND s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By shift type */
SELECT
GREATEST(0,
(
@ -196,28 +250,62 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null)
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id`=`needed_angel_types`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `needed_angel_types`.`room_id`=`shifts`.`room_id`
AND `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
' . ($filter ? 'AND angel_types.id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
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 `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0
AND shift_entries.`freeloaded`=0
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `end` > ? AND `start` < ?
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > ? AND shifts.`start` < ?
AND NOT s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By location */
SELECT
GREATEST(0,
(
SELECT SUM(needed_angel_types.`count`)
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id`=`needed_angel_types`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `needed_angel_types`.`location_id`=`shifts`.`location_id`
' . ($filter ? 'AND angel_types.id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
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 `shift_entries`.`shift_id`=`shifts`.`id`
AND shift_entries.`freeloaded`=0
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > ? AND shifts.`start` < ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp`', [
$night_start,
$night_end,
$night_start,
$night_end,
$night_start,
$night_end,
]);
return $result['count'] ?: '-';

View File

@ -137,22 +137,6 @@ function User_validate_planned_departure_date($planned_arrival_date, $planned_de
return new ValidationResult(true, $planned_departure_date);
}
/**
* Generates a new api key for given user.
*
* @param User $user
* @param bool $log
*/
function User_reset_api_key($user, $log = true)
{
$user->api_key = bin2hex(random_bytes(32));
$user->save();
if ($log) {
engelsystem_log(sprintf('API key resetted (%s).', User_Nick_render($user, true)));
}
}
/**
* @param User $user
* @return float

View File

@ -47,11 +47,11 @@ function admin_active()
__('At least %s angels are forced to be active. The number has to be greater.'),
$forced_count
));
throw_redirect(page_link_to('admin_active'));
throw_redirect(url('/admin-active'));
}
} else {
$msg .= error(__('Please enter a number of angels to be marked as active.'));
throw_redirect(page_link_to('admin_active'));
throw_redirect(url('/admin-active'));
}
if ($request->hasPostData('ack')) {
@ -97,9 +97,9 @@ function admin_active()
$msg = success(__('Marked angels.'), true);
} else {
$set_active = form([
button(page_link_to('admin_active', ['search' => $search]), '&laquo; ' . __('back')),
form_submit('ack', '&raquo; ' . __('apply')),
], page_link_to('admin_active', ['search' => $search, 'count' => $count, 'set_active' => 1]));
button(url('/admin-active', ['search' => $search]), '&laquo; ' . __('general.back')),
form_submit('ack', '&raquo; ' . __('Apply')),
], url('/admin-active', ['search' => $search, 'count' => $count, 'set_active' => 1]));
}
}
@ -133,7 +133,7 @@ function admin_active()
$user_source->state->got_shirt = true;
$user_source->state->save();
engelsystem_log('User ' . User_Nick_render($user_source, true) . ' has tshirt now.');
$msg = success(($goodie_tshirt ? __('Angel has got a t-shirt.') : __('Angel has got a goodie.')), true);
$msg = success(($goodie_tshirt ? __('Angel has got a T-shirt.') : __('Angel has got a goodie.')), true);
} else {
$msg = error('Angel not found.', true);
}
@ -144,7 +144,7 @@ function admin_active()
$user_source->state->got_shirt = false;
$user_source->state->save();
engelsystem_log('User ' . User_Nick_render($user_source, true) . ' has NO tshirt.');
$msg = success(($goodie_tshirt ? __('Angel has got no t-shirt.') : __('Angel has got no goodie.')), true);
$msg = success(($goodie_tshirt ? __('Angel has got no T-shirt.') : __('Angel has got no goodie.')), true);
} else {
$msg = error(__('Angel not found.'), true);
}
@ -236,8 +236,8 @@ function admin_active()
$parameters['show_all_shifts'] = 1;
}
$actions[] = form(
[form_submit('submit', __('set active'), 'btn-sm', false, 'secondary')],
page_link_to('admin_active', $parameters),
[form_submit('submit', icon('plus-lg') . __('set active'), 'btn-sm', false, 'secondary')],
url('/admin-active', $parameters),
false,
true
);
@ -251,8 +251,8 @@ function admin_active()
$parametersRemove['show_all_shifts'] = 1;
}
$actions[] = form(
[form_submit('submit', __('remove active'), 'btn-sm', false, 'secondary')],
page_link_to('admin_active', $parametersRemove),
[form_submit('submit', icon('dash-lg') . __('Remove active'), 'btn-sm', false, 'secondary')],
url('/admin-active', $parametersRemove),
false,
true
);
@ -268,8 +268,8 @@ function admin_active()
if ($goodie_enabled) {
$actions[] = form(
[form_submit('submit', ($goodie_tshirt ? __('got t-shirt') : __('got goodie')), 'btn-sm', false, 'secondary')],
page_link_to('admin_active', $parametersShirt),
[form_submit('submit', icon('person') . ($goodie_tshirt ? __('Got T-shirt') : __('Got goodie')), 'btn-sm', false, 'secondary')],
url('/admin-active', $parametersShirt),
false,
true
);
@ -286,8 +286,8 @@ function admin_active()
if ($goodie_enabled) {
$actions[] = form(
[form_submit('submit', ($goodie_tshirt ? __('remove t-shirt') : __('remove goodie')), 'btn-sm', false, 'secondary')],
page_link_to('admin_active', $parameters),
[form_submit('submit', icon('person') . ($goodie_tshirt ? __('Remove T-shirt') : __('Remove goodie')), 'btn-sm', false, 'secondary')],
url('/admin-active', $parameters),
false,
true
);
@ -295,7 +295,7 @@ function admin_active()
}
if ($goodie_tshirt) {
$actions[] = button(url('/admin/user/' . $usr->id . '/goodie'), __('form.edit'), 'btn-secondary btn-sm');
$actions[] = button(url('/admin/user/' . $usr->id . '/goodie'), icon('pencil') . __('form.edit'), 'btn-secondary btn-sm');
}
$userData['actions'] = buttons($actions);
@ -328,18 +328,18 @@ function admin_active()
form([
form_text('search', __('Search angel:'), $search),
form_checkbox('show_all_shifts', __('Show all shifts'), $show_all_shifts),
form_submit('submit', __('Search')),
], page_link_to('admin_active')),
form_submit('submit', icon('search') . __('form.search')),
], url('/admin-active')),
$set_active == '' ? form([
form_text('count', __('How much angels should be active?'), $count ?: $forced_count),
form_submit('set_active', __('Preview')),
form_submit('set_active', icon('eye') . __('form.preview'), 'btn-info'),
]) : $set_active,
$msg . msg(),
table(
array_merge(
[
'no' => __('No.'),
'nick' => __('Name'),
'nick' => __('general.name'),
],
($goodie_tshirt ? ['shirt_size' => __('Size')] : []),
[
@ -350,15 +350,15 @@ function admin_active()
],
($goodie_enabled ? ['tshirt' => ($goodie_tshirt ? __('T-shirt?') : __('Goodie?'))] : []),
[
'actions' => '',
'actions' => __('general.actions'),
]
),
$matched_users
),
$goodie_enabled ? '<h2>' . ($goodie_tshirt ? __('Shirt statistic') : __('Goodie statistic')) . '</h2>' : '',
$goodie_enabled ? '<h2>' . ($goodie_tshirt ? __('T-shirt statistic') : __('Goodie statistic')) . '</h2>' : '',
$goodie_enabled ? table(array_merge(
($goodie_tshirt ? ['size' => __('Size')] : []),
['given' => $goodie_tshirt ? __('Given shirts') : __('Given goodies') ]
['given' => $goodie_tshirt ? __('Given T-shirts') : __('Given goodies') ]
), $goodie_statistics) : '',
]);
}

View File

@ -78,7 +78,13 @@ function admin_arrive()
foreach ($users as $usr) {
if (count($tokens) > 0) {
$match = false;
$index = join(' ', $usr->attributesToArray());
$data = collect($usr->toArray())->flatten()->filter(function ($value) {
// Remove empty values
return !empty($value) &&
// Skip datetime
!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}Z$/', (string) $value);
});
$index = join(' ', $data->toArray());
foreach ($tokens as $token) {
$token = trim($token);
if (!empty($token) && stristr($index, $token)) {
@ -92,50 +98,68 @@ function admin_arrive()
}
}
$usr->name = User_Nick_render($usr) . User_Pronoun_render($usr);
$usr->name = User_Nick_render($usr)
. User_Pronoun_render($usr)
. ($usr->state->user_info
? ' <small><span class="bi bi-info-circle-fill text-info"></span></small>'
: '');
$plannedDepartureDate = $usr->personalData->planned_departure_date;
$arrivalDate = $usr->state->arrival_date;
$plannedArrivalDate = $usr->personalData->planned_arrival_date;
$usr['rendered_planned_departure_date'] = $plannedDepartureDate
? $plannedDepartureDate->format(__('Y-m-d'))
? $plannedDepartureDate->format(__('general.date'))
: '-';
$usr['rendered_planned_arrival_date'] = $plannedArrivalDate ? $plannedArrivalDate->format(__('Y-m-d')) : '-';
$usr['rendered_arrival_date'] = $arrivalDate ? $arrivalDate->format(__('Y-m-d')) : '-';
$usr['rendered_planned_arrival_date'] = $plannedArrivalDate ? $plannedArrivalDate->format(__('general.date')) : '-';
$usr['rendered_arrival_date'] = $arrivalDate ? $arrivalDate->format(__('general.date')) : '-';
$usr['arrived'] = icon_bool($usr->state->arrived);
$usr['actions'] = form([
form_hidden('action', $usr->state->arrived ? 'reset' : 'arrived'),
form_hidden('user', $usr->id),
form_submit(
'submit',
$usr->state->arrived ? __('reset') : __('arrived'),
$usr->state->arrived
? icon('arrow-counterclockwise')
: icon('house'),
'btn-sm',
true,
$usr->state->arrived ? 'secondary' : 'primary'
$usr->state->arrived ? 'secondary' : 'primary',
$usr->state->arrived
? __('Reset')
: __('user.arrive')
),
]);
if ($usr->state->arrival_date) {
$day = $usr->state->arrival_date->format(__('Y-m-d'));
$day = $usr->state->arrival_date->format('Y-m-d');
if (!isset($arrival_count_at_day[$day])) {
$arrival_count_at_day[$day] = 0;
$arrival_count_at_day[$day] = [
'day' => $usr->state->arrival_date,
'count' => 0,
];
}
$arrival_count_at_day[$day]++;
$arrival_count_at_day[$day]['count']++;
}
if ($usr->personalData->planned_arrival_date) {
$day = $usr->personalData->planned_arrival_date->format(__('Y-m-d'));
$day = $usr->personalData->planned_arrival_date->format('Y-m-d');
if (!isset($planned_arrival_count_at_day[$day])) {
$planned_arrival_count_at_day[$day] = 0;
$planned_arrival_count_at_day[$day] = [
'day' => $usr->personalData->planned_arrival_date,
'count' => 0,
];
}
$planned_arrival_count_at_day[$day]++;
$planned_arrival_count_at_day[$day]['count']++;
}
if ($usr->personalData->planned_departure_date && $usr->state->arrived) {
$day = $usr->personalData->planned_departure_date->format(__('Y-m-d'));
$day = $usr->personalData->planned_departure_date->format('Y-m-d');
if (!isset($planned_departure_count_at_day[$day])) {
$planned_departure_count_at_day[$day] = 0;
$planned_departure_count_at_day[$day] = [
'day' => $usr->personalData->planned_departure_date,
'count' => 0,
];
}
$planned_departure_count_at_day[$day]++;
$planned_departure_count_at_day[$day]['count']++;
}
$users_matched[] = $usr;
@ -147,33 +171,33 @@ function admin_arrive()
$arrival_at_day = [];
$arrival_sum = 0;
foreach ($arrival_count_at_day as $day => $count) {
$arrival_sum += $count;
foreach ($arrival_count_at_day as $day => $entry) {
$arrival_sum += $entry['count'];
$arrival_at_day[$day] = [
'day' => $day,
'count' => $count,
'day' => $entry['day']->format(__('general.date')),
'count' => $entry['count'],
'sum' => $arrival_sum,
];
}
$planned_arrival_at_day = [];
$planned_arrival_sum = 0;
foreach ($planned_arrival_count_at_day as $day => $count) {
$planned_arrival_sum += $count;
foreach ($planned_arrival_count_at_day as $day => $entry) {
$planned_arrival_sum += $entry['count'];
$planned_arrival_at_day[$day] = [
'day' => $day,
'count' => $count,
'day' => $entry['day']->format(__('general.date')),
'count' => $entry['count'],
'sum' => $planned_arrival_sum,
];
}
$planned_departure_at_day = [];
$planned_departure_sum = 0;
foreach ($planned_departure_count_at_day as $day => $count) {
$planned_departure_sum += $count;
foreach ($planned_departure_count_at_day as $day => $entry) {
$planned_departure_sum += $entry['count'];
$planned_departure_at_day[$day] = [
'day' => $day,
'count' => $count,
'day' => $entry['day']->format(__('general.date')),
'count' => $entry['count'],
'sum' => $planned_departure_sum,
];
}
@ -181,60 +205,60 @@ function admin_arrive()
return page_with_title(admin_arrive_title(), [
$msg . msg(),
form([
form_text('search', __('Search'), $search),
form_submit('submit', __('Search')),
], page_link_to('admin_arrive')),
form_text('search', __('form.search'), $search),
form_submit('submit', icon('search') . __('form.search')),
], url('/admin-arrive')),
table([
'name' => __('Name'),
'name' => __('general.name'),
'rendered_planned_arrival_date' => __('Planned arrival'),
'arrived' => __('Arrived?'),
'rendered_arrival_date' => __('Arrival date'),
'rendered_planned_departure_date' => __('Planned departure'),
'actions' => '',
'actions' => __('general.actions'),
], $users_matched),
div('row', [
div('col-md-4', [
heading(__('Planned arrival statistics'), 3),
BarChart::render([
'count' => __('arrived'),
'count' => __('user.arrived'),
'sum' => __('arrived sum'),
], [
'count' => '#090',
'sum' => '#888',
], $planned_arrival_at_day),
table([
'day' => __('Date'),
'count' => __('Count'),
'day' => __('title.date'),
'count' => __('general.count'),
'sum' => __('Sum'),
], $planned_arrival_at_day),
]),
div('col-md-4', [
heading(__('Arrival statistics'), 3),
BarChart::render([
'count' => __('arrived'),
'count' => __('user.arrived'),
'sum' => __('arrived sum'),
], [
'count' => '#090',
'sum' => '#888',
], $arrival_at_day),
table([
'day' => __('Date'),
'count' => __('Count'),
'day' => __('title.date'),
'count' => __('general.count'),
'sum' => __('Sum'),
], $arrival_at_day),
]),
div('col-md-4', [
heading(__('Planned departure statistics'), 3),
BarChart::render([
'count' => __('arrived'),
'count' => __('user.arrived'),
'sum' => __('arrived sum'),
], [
'count' => '#090',
'sum' => '#888',
], $planned_departure_at_day),
table([
'day' => __('Date'),
'count' => __('Count'),
'day' => __('title.date'),
'count' => __('general.count'),
'sum' => __('Sum'),
], $planned_departure_at_day),
]),

View File

@ -97,16 +97,20 @@ function admin_free()
$email = $usr->contact->email ?: $usr->email;
$free_users_table[] = [
'name' => User_Nick_render($usr) . User_Pronoun_render($usr),
'name' => User_Nick_render($usr)
. User_Pronoun_render($usr)
. ($usr->state->user_info
? ' <small><span class="bi bi-info-circle-fill text-info"></span></small>'
: ''),
'shift_state' => User_shift_state_render($usr),
'last_shift' => User_last_shift_render($usr),
'dect' => sprintf('<a href="tel:%s">%1$s</a>', $usr->contact->dect),
'dect' => sprintf('<a href="tel:%s">%1$s</a>', htmlspecialchars((string) $usr->contact->dect)),
'email' => $usr->settings->email_human
? sprintf('<a href="mailto:%s">%1$s</a>', $email)
? sprintf('<a href="mailto:%s">%1$s</a>', htmlspecialchars((string) $email))
: icon('eye-slash'),
'actions' =>
auth()->can('admin_user')
? button(page_link_to('admin_user', ['id' => $usr->id]), icon('pencil') . __('edit'), 'btn-sm')
? button(url('/admin-user', ['id' => $usr->id]), icon('pencil'), 'btn-sm', '', __('form.edit'))
: '',
];
}
@ -115,19 +119,19 @@ function admin_free()
div('row', [
div('col-md-12 form-inline', [
div('row', [
form_text('search', __('Search'), $search, null, null, null, 'col'),
form_text('search', __('form.search'), $search, null, null, null, 'col'),
form_select('angeltype', __('Angeltype'), $angel_types, $angelType, '', 'col'),
form_submit('submit', __('Search')),
form_submit('submit', icon('search') . __('form.search')),
]),
]),
]),
]),
table([
'name' => __('Name'),
'shift_state' => __('Next shift'),
'name' => __('general.name'),
'shift_state' => __('shift.next'),
'last_shift' => __('Last shift'),
'dect' => __('DECT'),
'email' => __('E-Mail'),
'dect' => __('general.dect'),
'email' => __('general.email'),
'actions' => '',
], $free_users_table),
]);

View File

@ -31,18 +31,21 @@ function admin_groups()
$privileges_html = [];
foreach ($privileges as $privilege) {
$privileges_html[] = $privilege['name'];
$privileges_html[] = htmlspecialchars($privilege['name']);
}
$groups_table[] = [
'name' => $group->name,
'name' => htmlspecialchars($group->name),
'privileges' => join(', ', $privileges_html),
'actions' => button(
page_link_to(
'admin_groups',
url(
'/admin-groups',
['action' => 'edit', 'id' => $group->id]
),
icon('pencil') . __('edit'),
icon('pencil'),
'',
'',
__('form.edit'),
'btn-sm'
),
];
@ -50,7 +53,7 @@ function admin_groups()
return page_with_title(admin_groups_title(), [
table([
'name' => __('Name'),
'name' => __('general.name'),
'privileges' => __('Privileges'),
'actions' => '',
], $groups_table),
@ -72,18 +75,18 @@ function admin_groups()
foreach ($privileges as $privilege) {
$privileges_form[] = form_checkbox(
'privileges[]',
$privilege->description . ' (' . $privilege->name . ')',
htmlspecialchars($privilege->description . ' (' . $privilege->name . ')'),
$privilege->selected != '',
$privilege->id,
'privilege-' . $privilege->name
'privilege-' . htmlspecialchars($privilege->name)
);
}
$privileges_form[] = form_submit('submit', __('Save'));
$html .= page_with_title(__('Edit group') . ' ' . $group->name, [
$privileges_form[] = form_submit('submit', icon('save') . __('form.save'));
$html .= page_with_title(__('Edit group') . ' ' . htmlspecialchars($group->name), [
form(
$privileges_form,
page_link_to('admin_groups', ['action' => 'save', 'id' => $group->id])
url('/admin-groups', ['action' => 'save', 'id' => $group->id])
),
]);
} else {
@ -118,7 +121,7 @@ function admin_groups()
'Group privileges of group ' . $group->name
. ' edited: ' . join(', ', $privilege_names)
);
throw_redirect(page_link_to('admin_groups'));
throw_redirect(url('/admin-groups'));
} else {
return error('No Group found.', true);
}

View File

@ -1,15 +1,11 @@
<?php
use Engelsystem\Database\Db;
use Engelsystem\Helpers\Carbon;
use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Schedule;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
@ -44,8 +40,8 @@ function admin_shifts()
$shift_over_midnight = true;
// Locations laden
$rooms = Room::orderBy('name')->get();
$room_array = $rooms->pluck('name', 'id')->toArray();
$locations = Location::orderBy('name')->get();
$location_array = $locations->pluck('name', 'id')->toArray();
// Load angeltypes
/** @var AngelType[] $types */
@ -85,14 +81,14 @@ function admin_shifts()
// Auswahl der sichtbaren Locations für die Schichten
if (
$request->has('rid')
&& preg_match('/^\d+$/', $request->input('rid'))
&& isset($room_array[$request->input('rid')])
$request->has('lid')
&& preg_match('/^\d+$/', $request->input('lid'))
&& isset($location_array[$request->input('lid')])
) {
$rid = $request->input('rid');
$lid = $request->input('lid');
} else {
$valid = false;
$rid = $rooms->first()->id;
$lid = $locations->first()?->id ?? 0;
error(__('Please select a location.'));
}
@ -139,6 +135,29 @@ function admin_shifts()
'trim',
explode(',', $request->input('change_hours'))
);
// Fehlende Minutenangaben ergänzen, 24 Uhr -> 00 Uhr
array_walk($change_hours, function (&$value) use ($valid) {
// Add minutes
if (!preg_match('/^(\d{1,2}):\d{2}$/', $value)) {
$value .= ':00';
}
// Add 0 before low hours
if (preg_match('/^\d:\d{2}$/', $value)) {
$value = '0' . $value;
}
// Fix 24:00
if ($value == '24:00') {
$value = '00:00';
}
});
// Ensure valid time in change hours
foreach ($change_hours as $change_hour) {
if (!preg_match('/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/', $change_hour)) {
$valid = false;
error(sprintf(__('Please validate the change hour %s. It should be between 00:00 and 24:00.'), $change_hour));
}
}
$change_hours = array_unique($change_hours);
} else {
$valid = false;
error(__('Please split the shift-change hours by colons.'));
@ -152,7 +171,9 @@ function admin_shifts()
}
if ($request->has('angelmode')) {
if ($request->input('angelmode') == 'location') {
if ($request->input('angelmode') == 'shift_type') {
$angelmode = 'shift_type';
} elseif ($request->input('angelmode') == 'location') {
$angelmode = 'location';
} elseif ($request->input('angelmode') == 'manually') {
foreach ($types as $type) {
@ -184,8 +205,12 @@ function admin_shifts()
// Alle Eingaben in Ordnung
if ($valid) {
if ($angelmode == 'location') {
$needed_angel_types = NeededAngelType::whereRoomId($rid)
if ($angelmode == 'shift_type') {
$needed_angel_types = NeededAngelType::whereShiftTypeId($shifttype_id)
->pluck('count', 'angel_type_id')
->toArray() + $needed_angel_types;
} elseif ($angelmode == 'location') {
$needed_angel_types = NeededAngelType::whereLocationId($lid)
->pluck('count', 'angel_type_id')
->toArray() + $needed_angel_types;
}
@ -195,7 +220,7 @@ function admin_shifts()
$shifts[] = [
'start' => $start,
'end' => $end,
'room_id' => $rid,
'location_id' => $lid,
'title' => $title,
'shift_type_id' => $shifttype_id,
'description' => $description,
@ -215,7 +240,7 @@ function admin_shifts()
$shifts[] = [
'start' => $shift_start,
'end' => $shift_end,
'room_id' => $rid,
'location_id' => $lid,
'title' => $title,
'shift_type_id' => $shifttype_id,
'description' => $description,
@ -224,13 +249,6 @@ function admin_shifts()
$shift_start = $shift_end;
} while ($shift_end < $end);
} elseif ($mode == 'variable') {
// Fehlende Minutenangaben ergänzen
array_walk($change_hours, function (&$value) {
if (!preg_match('/^\d{1,2}:\d{2}$/', $value)) {
$value .= ':00';
}
});
// Alle Tage durchgehen
$end_day = Carbon::createFromDatetime($end->format('Y-m-d') . ' 00:00');
$day = Carbon::createFromDatetime($start->format('Y-m-d') . ' 00:00');
@ -238,10 +256,10 @@ function admin_shifts()
// Alle Schichtwechselstunden durchgehen
for ($i = 0; $i < count($change_hours); $i++) {
$start_hour = $change_hours[$i];
if ($i < count($change_hours) - 1) {
if (isset($change_hours[$i + 1])) {
// Normales Intervall zwischen zwei Schichtwechselstunden
$end_hour = $change_hours[$i + 1];
} elseif ($shift_over_midnight) {
} elseif ($shift_over_midnight && $day != $end_day) {
// Letzte Schichtwechselstunde: Wenn eine 24h Abdeckung gewünscht ist,
// hier die erste Schichtwechselstunde als Ende einsetzen
$end_hour = $change_hours[0];
@ -283,7 +301,7 @@ function admin_shifts()
$shifts[] = [
'start' => $interval_start,
'end' => $interval_end,
'room_id' => $rid,
'location_id' => $lid,
'title' => $title,
'shift_type_id' => $shifttype_id,
'description' => $description,
@ -300,17 +318,24 @@ function admin_shifts()
$shifts_table = [];
foreach ($shifts as $shift) {
/** @var Carbon $start */
$start = $shift['start'];
/** @var Carbon $end */
$end = $shift['end'];
$shifts_table_entry = [
'timeslot' =>
icon('clock-history') . ' '
. $shift['start']->format(__('Y-m-d H:i'))
. $start->format(__('general.datetime'))
. ' - '
. $shift['end']->format(__('H:i'))
. '<br />'
. Room_name_render(Room::find($shift['room_id'])),
. '<span title="' . $end->format(__('general.date')) . '">'
. $end->format(__('H:i'))
. '</span>'
. ', ' . round($end->copy()->diffInMinutes($start) / 60, 2) . 'h'
. '<br>'
. location_name_render(Location::find($shift['location_id'])),
'title' =>
ShiftType_name_render(ShiftType::find($shifttype_id))
. ($shift['title'] ? '<br />' . $shift['title'] : ''),
htmlspecialchars(ShiftType::find($shifttype_id)->name)
. ($shift['title'] ? '<br />' . htmlspecialchars($shift['title']) : ''),
'needed_angels' => '',
];
foreach ($types as $type) {
@ -326,9 +351,9 @@ function admin_shifts()
$session->set('admin_shifts_shifts', $shifts);
$session->set('admin_shifts_types', $needed_angel_types);
$hidden_types = '';
$previousEntries = [];
foreach ($needed_angel_types as $type_id => $count) {
$hidden_types .= form_hidden('angeltype_count_' . $type_id, $count);
$previousEntries['angeltype_count_' . $type_id] = $count;
}
// Number of Shifts that will be created (if over 100 its danger-red)
@ -338,28 +363,38 @@ function admin_shifts()
$shiftsCreationHint = '<span class="text-danger">' . $shiftsCreationHint . '</span>';
}
return page_with_title(__('Preview'), [
// Save as previous state to be able to reuse it
$previousEntries += [
'shifttype_id' => $shifttype_id,
'description' => $description,
'title' => $title,
'lid' => $lid,
'start' => $request->input('start'),
'end' => $request->input('end'),
'mode' => $mode,
'length' => $length,
'change_hours' => implode(', ', $change_hours),
'angelmode' => $angelmode,
'shift_over_midnight' => $shift_over_midnight ? 'true' : 'false',
];
$session->set('admin_shifts_previous', $previousEntries);
$hidden_types = '';
foreach ($previousEntries as $name => $value) {
$hidden_types .= form_hidden($name, $value);
}
return page_with_title(__('form.preview'), [
form([
$hidden_types,
form_hidden('shifttype_id', $shifttype_id),
form_hidden('description', $description),
form_hidden('title', $title),
form_hidden('rid', $rid),
form_hidden('start', $start->format('Y-m-d H:i')),
form_hidden('end', $end->format('Y-m-d H:i')),
form_hidden('mode', $mode),
form_hidden('length', $length),
form_hidden('change_hours', implode(', ', $change_hours)),
form_hidden('angelmode', $angelmode),
form_hidden('shift_over_midnight', $shift_over_midnight ? 'true' : 'false'),
form_submit('back', icon('chevron-left') . __('back')),
form_submit('back', icon('chevron-left') . __('general.back')),
$shiftsCreationHint,
table([
'timeslot' => __('Time and location'),
'title' => __('Type and title'),
'needed_angels' => __('Needed angels'),
], $shifts_table),
form_submit('submit', icon('save') . __('Save')),
form_submit('submit', icon('save') . __('form.save')),
]),
]);
}
@ -368,7 +403,7 @@ function admin_shifts()
!is_array($session->get('admin_shifts_shifts'))
|| !is_array($session->get('admin_shifts_types'))
) {
throw_redirect(page_link_to('admin_shifts'));
throw_redirect(url('/admin-shifts'));
}
$transactionId = Str::uuid();
@ -405,22 +440,22 @@ function admin_shifts()
}
success('Shifts created.');
throw_redirect(page_link_to('admin_shifts'));
throw_redirect(url('/admin-shifts'));
} else {
$session->remove('admin_shifts_shifts');
$session->remove('admin_shifts_types');
}
$rid = null;
if ($request->has('rid')) {
$rid = $request->input('rid');
$lid = null;
if ($request->has('lid')) {
$lid = $request->input('lid');
}
$angel_types = '';
foreach ($types as $type) {
$angel_types .= '<div class="col-sm-6 col-md-8 col-lg-6 col-xl-4 col-xxl-3">'
. form_spinner(
'angeltype_count_' . $type->id,
$type->name,
htmlspecialchars($type->name),
$needed_angel_types[$type->id],
[
'radio-name' => 'angelmode',
@ -430,20 +465,36 @@ function admin_shifts()
. '</div>';
}
$link = button(url('/user-shifts'), icon('chevron-left'), 'btn-sm', '', __('general.back'));
$reset = '';
if ($session->has('admin_shifts_previous')) {
$reset = form_submit(
'back',
icon('arrow-counterclockwise'),
'',
false,
'link',
__('Reset to previous state')
);
foreach ($session->get('admin_shifts_previous', []) as $name => $value) {
$reset .= form_hidden($name, $value);
}
}
return page_with_title(
admin_shifts_title() . ' ' . sprintf(
$link . ' ' . admin_shifts_title() . ' ' . sprintf(
'<a href="%s">%s</a>',
page_link_to('admin_shifts_history'),
url('/admin/shifts/history'),
icon('clock-history')
),
) . form([$reset], '', 'display:inline'),
[
msg(),
form([
div('row', [
div('col-md-6 col-xl-5', [
form_select('shifttype_id', __('Shifttype'), $shifttypes, $shifttype_id),
form_text('title', __('Title'), $title),
form_select('rid', __('Room'), $room_array, $rid),
form_text('title', __('title.title'), $title),
form_select('lid', __('Location'), $location_array, $lid),
]),
div('col-md-6 col-xl-7', [
form_textarea('description', __('Additional description'), $description),
@ -454,10 +505,22 @@ function admin_shifts()
div('col-md-6 col-xl-5', [
div('row', [
div('col-lg-6', [
form_datetime('start', __('Start'), $start),
form_datetime(
'start',
__('shifts.start'),
$request->has('start')
? Carbon::createFromDatetime($request->input('start'))
: $start
),
]),
div('col-lg-6', [
form_datetime('end', __('End'), $end),
form_datetime(
'end',
__('shifts.end'),
$request->has('end')
? Carbon::createFromDatetime($request->input('end'))
: $end
),
]),
]),
form_info(__('Mode')),
@ -488,7 +551,7 @@ function admin_shifts()
'change_hours',
__('Shift change hours'),
$request->has('change_hours')
? $request->input('change_hours')
? ($change_hours ? implode(', ', $change_hours) : $request->input('change_hours'))
: '00, 04, 08, 10, 12, 14, 16, 18, 20, 22',
false,
null,
@ -509,7 +572,13 @@ function admin_shifts()
form_info(__('Needed angels')),
form_radio(
'angelmode',
__('Take needed angels from room settings'),
__('Copy needed angels from shift type settings'),
$angelmode == 'shift_type',
'shift_type'
),
form_radio(
'angelmode',
__('Copy needed angels from location settings'),
$angelmode == 'location',
'location'
),
@ -524,104 +593,8 @@ function admin_shifts()
]),
]),
]),
form_submit('preview', icon('search') . __('Preview')),
form_submit('preview', icon('eye') . __('form.preview'), 'btn-info'),
]),
]
);
}
function admin_shifts_history_title(): string
{
return __('Shifts history');
}
/**
* Display shifts transaction history
*
* @return string
*/
function admin_shifts_history(): string
{
if (!auth()->can('admin_shifts')) {
throw new HttpForbidden();
}
$request = request();
$transactionId = $request->postData('transaction_id');
if ($request->hasPostData('delete') && $transactionId) {
$shifts = Shift::whereTransactionId($transactionId)->get();
engelsystem_log('Deleting ' . count($shifts) . ' shifts (transaction id ' . $transactionId . ')');
foreach ($shifts as $shift) {
$shift = Shift($shift);
foreach ($shift->shiftEntries as $entry) {
event('shift.entry.deleting', [
'user' => $entry->user,
'start' => $shift->start,
'end' => $shift->end,
'name' => $shift->shiftType->name,
'title' => $shift->title,
'type' => $entry->angelType->name,
'room' => $shift->room,
'freeloaded' => $entry->freeloaded,
]);
}
$shift->delete();
engelsystem_log(
'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')
);
}
success(sprintf(__('%s shifts deleted.'), count($shifts)));
throw_redirect(page_link_to('admin_shifts_history'));
}
$schedules = Schedule::all()->pluck('name', 'id')->toArray();
$shiftsData = Db::select('
SELECT
s.transaction_id,
s.title,
schedule_shift.schedule_id,
COUNT(s.id) AS count,
MIN(s.start) AS start,
MAX(s.end) AS end,
s.created_by AS user_id,
MAX(s.created_at) AS created_at
FROM shifts AS s
LEFT JOIN schedule_shift on schedule_shift.shift_id = s.id
WHERE s.transaction_id IS NOT NULL
GROUP BY s.transaction_id
ORDER BY created_at DESC
');
foreach ($shiftsData as &$shiftData) {
$shiftData['title'] = $shiftData['schedule_id'] ? __('shifts_history.schedule', [$schedules[$shiftData['schedule_id']]]) : $shiftData['title'];
$shiftData['user'] = User_Nick_render(User::find($shiftData['user_id']));
$shiftData['start'] = Carbon::make($shiftData['start'])->format(__('Y-m-d H:i'));
$shiftData['end'] = Carbon::make($shiftData['end'])->format(__('Y-m-d H:i'));
$shiftData['created_at'] = Carbon::make($shiftData['created_at'])->format(__('Y-m-d H:i'));
$shiftData['actions'] = form([
form_hidden('transaction_id', $shiftData['transaction_id']),
form_submit('delete', icon('trash') . __('delete all'), 'btn-sm', true, 'danger'),
]);
}
return page_with_title(admin_shifts_history_title(), [
msg(),
table([
'transaction_id' => __('ID'),
'title' => __('Title'),
'count' => __('Count'),
'start' => __('Start'),
'end' => __('End'),
'user' => __('User'),
'created_at' => __('Created'),
'actions' => '',
], $shiftsData),
], true);
}

View File

@ -27,6 +27,9 @@ function admin_user()
$goodie = GoodieType::from(config('goodie_type'));
$goodie_enabled = $goodie !== GoodieType::None;
$goodie_tshirt = $goodie === GoodieType::Tshirt;
$user_info_edit = auth()->can('user.info.edit');
$user_edit_shirt = auth()->can('user.edit.shirt');
$user_edit = auth()->can('user.edit');
if (!$request->has('id')) {
throw_redirect(users_link());
@ -41,38 +44,51 @@ function admin_user()
}
$html .= __('Here you can change the user entry. Under the item \'Arrived\' the angel is marked as present, a yes at Active means that the angel was active.');
if ($goodie_enabled) {
if ($goodie_enabled && $user_edit_shirt) {
if ($goodie_tshirt) {
$html .= ' ' . __('If the angel is active, it can claim a T-shirt. If T-shirt is set to \'Yes\', the angel already got their T-shirt.');
} else {
$html .= ' ' . __('If the angel is active, it can claim a goodie. If goodie is set to \'Yes\', the angel already got their goodie.');
}
}
$html .= '<br /><br />';
$html .= '<br><br>';
$html .= '<form action="'
. page_link_to('admin_user', ['action' => 'save', 'id' => $user_id])
. url('/admin-user', ['action' => 'save', 'id' => $user_id])
. '" method="post">' . "\n";
$html .= form_csrf();
$html .= '<table>' . "\n";
$html .= '<input type="hidden" name="Type" value="Normal">' . "\n";
$html .= '<tr><td>' . "\n";
$html .= '<table>' . "\n";
$html .= ' <tr><td>' . __('Nickname') . '</td><td>' . '<input size="40" name="eNick" value="' . $user_source->name . '" class="form-control" maxlength="24"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('general.nick') . '</td><td>'
. '<input size="40" name="eNick" value="' . htmlspecialchars($user_source->name)
. '" class="form-control" maxlength="24" ' . ($user_edit ? '' : 'disabled') . '>'
. '</td></tr>' . "\n";
$html .= ' <tr><td>' . __('Last login') . '</td><td><p class="help-block">'
. ($user_source->last_login_at ? $user_source->last_login_at->format(__('Y-m-d H:i')) : '-')
. ($user_source->last_login_at ? $user_source->last_login_at->format(__('general.datetime')) : '-')
. '</p></td></tr>' . "\n";
if (config('enable_user_name')) {
$html .= ' <tr><td>' . __('Prename') . '</td><td>' . '<input size="40" name="eName" value="' . $user_source->personalData->last_name . '" class="form-control" maxlength="64"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('Last name') . '</td><td>' . '<input size="40" name="eVorname" value="' . $user_source->personalData->first_name . '" class="form-control" maxlength="64"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('settings.profile.firstname') . '</td><td>'
. '<input size="40" name="eName" value="' . htmlspecialchars((string) $user_source->personalData->last_name) . '" class="form-control" maxlength="64">'
. '</td></tr>' . "\n";
$html .= ' <tr><td>' . __('settings.profile.lastname') . '</td><td>'
. '<input size="40" name="eVorname" value="' . htmlspecialchars((string) $user_source->personalData->first_name) . '" class="form-control" maxlength="64">'
. '</td></tr>' . "\n";
}
$html .= ' <tr><td>' . __('Mobile') . '</td><td>' . '<input type= "tel" size="40" name="eHandy" value="' . $user_source->contact->mobile . '" class="form-control" maxlength="40"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('settings.profile.mobile') . '</td><td>'
. '<input type= "tel" size="40" name="eHandy" value="' . htmlspecialchars((string) $user_source->contact->mobile) . '" class="form-control" maxlength="40">'
. '</td></tr>' . "\n";
if (config('enable_dect')) {
$html .= ' <tr><td>' . __('DECT') . '</td><td>' . '<input size="40" name="eDECT" value="' . $user_source->contact->dect . '" class="form-control" maxlength="40"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('general.dect') . '</td><td>'
. '<input size="40" name="eDECT" value="' . htmlspecialchars((string) $user_source->contact->dect) . '" class="form-control" maxlength="40">'
. '</td></tr>' . "\n";
}
if ($user_source->settings->email_human) {
$html .= ' <tr><td>' . __('settings.profile.email') . '</td><td>' . '<input type="email" size="40" name="eemail" value="' . $user_source->email . '" class="form-control" maxlength="254"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('general.email') . '</td><td>'
. '<input type="email" size="40" name="eemail" value="' . htmlspecialchars($user_source->email) . '" class="form-control" maxlength="254">'
. '</td></tr>' . "\n";
}
if ($goodie_tshirt) {
if ($goodie_tshirt && $user_edit_shirt) {
$html .= ' <tr><td>' . __('user.shirt_size') . '</td><td>'
. html_select_key(
'size',
@ -84,34 +100,50 @@ function admin_user()
. '</td></tr>' . "\n";
}
// User info
if ($user_info_edit) {
$html .= ' <tr><td>'
. __('user.info')
. ' <span class="bi bi-info-circle-fill text-info" data-bs-toggle="tooltip" title="'
. __('user.info.hint')
. '"></span>'
. '</td><td>'
. '<textarea cols="40" rows="" name="userInfo" class="form-control">'
. htmlspecialchars((string) $user_source->state->user_info)
. '</textarea>'
. '</td></tr>' . "\n";
}
$options = [
'1' => __('Yes'),
'0' => __('No'),
];
// Gekommen?
$html .= ' <tr><td>' . __('Arrived') . '</td><td>' . "\n";
if ($user_source->state->arrived) {
$html .= __('Yes');
} else {
$html .= __('No');
}
// Arrived?
$html .= ' <tr><td>' . __('user.arrived') . '</td><td>' . "\n";
$html .= ($user_source->state->arrived ? __('Yes') : __('No'));
$html .= '</td></tr>' . "\n";
// Aktiv?
$html .= ' <tr><td>' . __('user.active') . '</td><td>' . "\n";
$html .= html_options('eAktiv', $options, $user_source->state->active) . '</td></tr>' . "\n";
// Active?
if ($user_edit_shirt) {
$html .= ' <tr><td>' . __('user.active') . '</td><td>' . "\n";
$html .= html_options('eAktiv', $options, $user_source->state->active) . '</td></tr>' . "\n";
} else {
$html .= ' <tr><td>' . __('user.active') . '</td><td>' . "\n";
$html .= ($user_source->state->active ? __('Yes') : __('No'));
$html .= '</td></tr>' . "\n";
}
// Aktiv erzwingen
// Forced active?
if (auth()->can('admin_active')) {
$html .= ' <tr><td>' . __('Force active') . '</td><td>' . "\n";
$html .= html_options('force_active', $options, $user_source->state->force_active) . '</td></tr>' . "\n";
}
if ($goodie_enabled) {
if ($goodie_enabled && $user_edit_shirt) {
// T-Shirt bekommen?
if ($goodie_tshirt) {
$html .= ' <tr><td>' . __('T-Shirt') . '</td><td>' . "\n";
$html .= ' <tr><td>' . __('T-shirt') . '</td><td>' . "\n";
} else {
$html .= ' <tr><td>' . __('Goodie') . '</td><td>' . "\n";
}
@ -120,27 +152,36 @@ function admin_user()
$html .= '</table>' . "\n" . '</td><td></td></tr>';
$html .= '</td></tr>' . "\n";
$html .= '</table>' . "\n" . '<br />' . "\n";
$html .= '<button type="submit" class="btn btn-primary">' . __('form.save') . '</button>' . "\n";
$html .= '</table>' . "\n" . '<br>' . "\n";
$html .= '<button type="submit" class="btn btn-primary">'
. icon('save') . __('form.save') . '</button>' . "\n";
$html .= '</form>';
$html .= '<hr />';
$html .= '<hr>';
$html .= form_info('', __('Please visit the angeltypes page or the users profile to manage the users angeltypes.'));
$html .= __('Here you can reset the password of this angel:');
$html .= ' ' . __('Here you can reset the password of this angel:') . '<form action="'
. page_link_to('admin_user', ['action' => 'change_pw', 'id' => $user_id])
$html .= '<form action="'
. url('/admin-user', ['action' => 'change_pw', 'id' => $user_id])
. '" method="post">' . "\n";
$html .= form_csrf();
$html .= '<table>' . "\n";
$html .= ' <tr><td>' . __('Password') . '</td><td>' . '<input type="password" size="40" name="new_pw" value="" class="form-control" autocomplete="new-password"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('Confirm password') . '</td><td>' . '<input type="password" size="40" name="new_pw2" value="" class="form-control" autocomplete="new-password"></td></tr>' . "\n";
$html .= ' <tr><td>' . __('settings.password')
. ' <span class="bi bi-info-circle-fill text-info" data-bs-toggle="tooltip" title="'
. __('password.minimal_length', [config('min_password_length')]) . '"></span>'
. '</td><td>'
. '<input type="password" size="40" name="new_pw" value="" class="form-control" autocomplete="new-password">'
. '</td></tr>' . "\n";
$html .= ' <tr><td>' . __('password.reset.confirm') . '</td><td>'
. '<input type="password" size="40" name="new_pw2" value="" class="form-control" autocomplete="new-password">'
. '</td></tr>' . "\n";
$html .= '</table>' . "\n" . '<br />' . "\n";
$html .= '<button type="submit" class="btn btn-primary">' . __('form.save') . '</button>' . "\n";
$html .= '</table>' . "\n" . '<br>' . "\n";
$html .= '<button type="submit" class="btn btn-primary">'
. icon('save') . __('form.save') . '</button>' . "\n";
$html .= '</form>';
$html .= '<hr />';
$html .= '<hr>';
/** @var Group $my_highest_group */
$my_highest_group = $user->groups()->orderByDesc('id')->first();
@ -158,7 +199,7 @@ function admin_user()
&& ($my_highest_group >= $angel_highest_group || is_null($angel_highest_group))
) {
$html .= __('Here you can define the user groups of the angel:') . '<form action="'
. page_link_to('admin_user', ['action' => 'save_groups', 'id' => $user_id])
. url('/admin-user', ['action' => 'save_groups', 'id' => $user_id])
. '" method="post">' . "\n";
$html .= form_csrf();
$html .= '<div>';
@ -168,19 +209,22 @@ function admin_user()
$html .= '<div class="form-check">'
. '<input class="form-check-input" type="checkbox" id="' . $group->id . '" name="groups[]" value="' . $group->id . '" '
. ($group->selected ? ' checked="checked"' : '')
. ' /><label class="form-check-label" for="' . $group->id . '">' . $group->name . '</label></div>';
. ' /><label class="form-check-label" for="' . $group->id . '">'
. htmlspecialchars($group->name)
. '</label></div>';
}
$html .= '</div><br>';
$html .= '<button type="submit" class="btn btn-primary">' . __('form.save') . '</button>' . "\n";
$html .= '<button type="submit" class="btn btn-primary">'
. icon('save') . __('form.save') . '</button>' . "\n";
$html .= '</form>';
$html .= '<hr />';
$html .= '<hr>';
}
$html .= buttons([
button(user_delete_link($user_source->id), icon('trash') . __('delete'), 'btn-danger'),
button(user_delete_link($user_source->id), icon('trash') . __('form.delete'), 'btn-danger'),
]);
$html .= '<hr>';
@ -237,19 +281,21 @@ function admin_user()
break;
case 'save':
$force_active = $user->state->force_active;
$user_source = User::find($user_id);
if (auth()->can('admin_active')) {
$force_active = $request->input('force_active');
}
$changed_email = false;
if ($user_source->settings->email_human) {
$changed_email = $user_source->email !== $request->postData('eemail');
$user_source->email = $request->postData('eemail');
}
$nick = trim($request->get('eNick'));
$nickValid = (new Username())->validate($nick);
if ($nickValid) {
$changed_nick = false;
$old_nick = $user_source->name;
if ($nickValid && $user_edit) {
$changed_nick = $user_source->name !== $nick;
$user_source->name = $nick;
}
$user_source->save();
@ -258,30 +304,45 @@ function admin_user()
$user_source->personalData->first_name = $request->postData('eVorname');
$user_source->personalData->last_name = $request->postData('eName');
}
if ($goodie_tshirt) {
if ($goodie_tshirt && $user_edit_shirt) {
$user_source->personalData->shirt_size = $request->postData('eSize');
}
$user_source->personalData->save();
$user_source->contact->mobile = $request->postData('eHandy');
$user_source->contact->dect = $request->postData('eDECT');
if (config('enable_dect')) {
$user_source->contact->dect = $request->postData('eDECT');
}
$user_source->contact->save();
if ($goodie_enabled) {
if ($goodie_enabled && $user_edit_shirt) {
$user_source->state->got_shirt = $request->postData('eTshirt');
}
$user_source->state->active = $request->postData('eAktiv');
$user_source->state->force_active = $force_active;
if ($user_info_edit) {
$user_source->state->user_info = $request->postData('userInfo');
}
if ($user_edit_shirt) {
$user_source->state->active = $request->postData('eAktiv');
}
if (auth()->can('admin_active')) {
$user_source->state->force_active = $request->input('force_active');
}
$user_source->state->save();
engelsystem_log(
'Updated user: ' . $user_source->name . ' (' . $user_source->id . ')'
. ($goodie_tshirt ? ', t-shirt: ' : '' . $user_source->personalData->shirt_size)
'Updated user: ' . ($changed_nick
? ('nick modified form ' . $old_nick . ' to ' . $user_source->name)
: $user_source->name)
. ' (' . $user_source->id . ')'
. ($changed_email ? ', email modified' : '')
. ($goodie_tshirt ? ', t-shirt-size: ' . $user_source->personalData->shirt_size : '')
. ', active: ' . $user_source->state->active
. ', force-active: ' . $user_source->state->force_active
. ($goodie_tshirt ? ', tshirt: ' : ', goodie: ' . $user_source->state->got_shirt)
. ($goodie_tshirt ? ', t-shirt: ' : ', goodie: ' . $user_source->state->got_shirt)
. ($user_info_edit ? ', user-info: ' . $user_source->state->user_info : '')
);
$html .= success(__('Changes where saved.') . "\n", true);
$html .= success(__('Changes were saved.') . "\n", true);
break;
case 'change_pw':
@ -303,9 +364,13 @@ function admin_user()
}
}
return page_with_title(__('Edit user'), [
$link = button(url('/users', ['action' => 'view', 'user_id' => $user_id]), icon('chevron-left'), 'btn-sm', '', __('general.back'));
return page_with_title(
$link . ' ' . __('Edit user'),
[
$html,
]);
]
);
}
/**

View File

@ -1,543 +0,0 @@
<?php
use Carbon\Carbon;
use Engelsystem\Database\Database;
use Engelsystem\Events\Listener\OAuth2;
use Engelsystem\Config\GoodieType;
use Engelsystem\Http\Validation\Rules\Username;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Group;
use Engelsystem\Models\OAuth;
use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\PersonalData;
use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\State;
use Engelsystem\Models\User\User;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Collection;
/**
* @return string
*/
function register_title()
{
return __('Register');
}
/**
* Engel registrieren
*
* @return string
*/
function guest_register()
{
$authUser = auth()->user();
$tshirt_sizes = config('tshirt_sizes');
$goodie = GoodieType::from(config('goodie_type'));
$goodie_enabled = $goodie !== GoodieType::None;
$goodie_tshirt = $goodie === GoodieType::Tshirt;
$enable_user_name = config('enable_user_name');
$enable_dect = config('enable_dect');
$enable_planned_arrival = config('enable_planned_arrival');
$min_password_length = config('min_password_length');
$enable_password = config('enable_password');
$enable_pronoun = config('enable_pronoun');
$enable_mobile_show = config('enable_mobile_show');
$config = config();
$request = request();
$session = session();
/** @var Connection $db */
$db = app(Database::class)->getConnection();
$is_oauth = $session->has('oauth2_connect_provider');
$msg = '';
$nick = '';
$lastName = '';
$preName = '';
$dect = '';
$mobile = '';
$mobile_show = false;
$email = '';
$pronoun = '';
$email_shiftinfo = false;
$email_by_human_allowed = false;
$email_messages = false;
$email_news = false;
$email_goody = false;
$tshirt_size = '';
$password_hash = '';
$selected_angel_types = [];
$planned_arrival_date = null;
/** @var AngelType[]|Collection $angel_types_source */
$angel_types_source = AngelType::all();
$angel_types = [];
if (!empty($session->get('oauth2_groups'))) {
/** @var OAuth2 $oauth */
$oauth = app()->get(OAuth2::class);
$ssoTeams = $oauth->getSsoTeams($session->get('oauth2_connect_provider'));
foreach ($ssoTeams as $name => $team) {
if (in_array($name, $session->get('oauth2_groups'))) {
$selected_angel_types[] = $team['id'];
}
}
}
foreach ($angel_types_source as $angel_type) {
if ($angel_type->hide_register) {
continue;
}
$angel_types[$angel_type->id] = $angel_type->name
. ($angel_type->restricted ? ' (' . __('Requires introduction') . ')' : '');
if (!$angel_type->restricted) {
$selected_angel_types[] = $angel_type->id;
}
}
$oauth_enable_password = $session->get('oauth2_enable_password');
if (!is_null($oauth_enable_password)) {
$enable_password = $oauth_enable_password;
}
if (
!auth()->can('register') // No registration permission
// Not authenticated and
|| (!$authUser && !config('registration_enabled') && !$session->get('oauth2_allow_registration')) // Registration disabled
|| (!$authUser && !$enable_password && !$is_oauth) // Password disabled and not oauth
) {
error(__('Registration is disabled.'));
return page_with_title(register_title(), [
msg(),
]);
}
if ($request->hasPostData('submit')) {
$valid = true;
if ($request->has('username')) {
$nick = trim($request->get('username'));
$nickValid = (new Username())->validate($nick);
if (!$nickValid) {
$valid = false;
$msg .= error(sprintf(
__('Please enter a valid nick.') . ' ' . __('Use up to 24 letters, numbers or connecting punctuations for your nickname.'),
$nick
), true);
}
if (User::whereName($nick)->count() > 0) {
$valid = false;
$msg .= error(sprintf(__('Your nick "%s" already exists.'), htmlspecialchars($nick)), true);
}
} else {
$valid = false;
$msg .= error(__('Please enter a nickname.'), true);
}
if ($request->has('mobile_show') && $enable_mobile_show) {
$mobile_show = true;
}
if ($request->has('email') && strlen(strip_request_item('email')) > 0) {
$email = strip_request_item('email');
if (!check_email($email)) {
$valid = false;
$msg .= error(__('E-mail address is not correct.'), true);
}
if (User::whereEmail($email)->first()) {
$valid = false;
$msg .= error(__('E-mail address is already used by another user.'), true);
}
} else {
$valid = false;
$msg .= error(__('Please enter your e-mail.'), true);
}
if ($request->has('email_shiftinfo')) {
$email_shiftinfo = true;
}
if ($request->has('email_by_human_allowed')) {
$email_by_human_allowed = true;
}
if ($request->has('email_messages')) {
$email_messages = true;
}
if ($request->has('email_news')) {
$email_news = true;
}
if ($request->has('email_goody')) {
$email_goody = true;
}
if ($goodie_tshirt) {
if ($request->has('tshirt_size') && isset($tshirt_sizes[$request->input('tshirt_size')])) {
$tshirt_size = $request->input('tshirt_size');
} else {
$valid = false;
$msg .= error(__('Please select your shirt size.'), true);
}
}
if ($enable_password && $request->has('password') && strlen($request->postData('password')) >= $min_password_length) {
if ($request->postData('password') != $request->postData('password2')) {
$valid = false;
$msg .= error(__('Your passwords don\'t match.'), true);
}
} elseif ($enable_password) {
$valid = false;
$msg .= error(sprintf(
__('Your password is too short (please use at least %s characters).'),
$min_password_length
), true);
}
if ($request->has('planned_arrival_date') && $enable_planned_arrival) {
$tmp = parse_date('Y-m-d H:i', $request->input('planned_arrival_date') . ' 00:00');
$result = User_validate_planned_arrival_date($tmp);
$planned_arrival_date = $result->getValue();
if (!$result->isValid()) {
$valid = false;
error(__('Please enter your planned date of arrival. It should be after the buildup start date and before teardown end date.'));
}
} elseif ($enable_planned_arrival) {
$valid = false;
error(__('Please enter your planned date of arrival. It should be after the buildup start date and before teardown end date.'));
}
$selected_angel_types = [];
foreach (array_keys($angel_types) as $angel_type_id) {
if ($request->has('angel_types_' . $angel_type_id)) {
$selected_angel_types[] = $angel_type_id;
}
}
// Trivia
if ($enable_user_name && $request->has('lastname')) {
$lastName = strip_request_item('lastname');
}
if ($enable_user_name && $request->has('prename')) {
$preName = strip_request_item('prename');
}
if ($enable_pronoun && $request->has('pronoun')) {
$pronoun = strip_request_item('pronoun');
}
if ($enable_dect && $request->has('dect')) {
if (strlen(strip_request_item('dect')) <= 40) {
$dect = strip_request_item('dect');
} else {
$valid = false;
error(__('For dect numbers are only 40 digits allowed.'));
}
}
if ($request->has('mobile')) {
$mobile = strip_request_item('mobile');
}
if ($valid) {
// Safeguard against partially created user data
$db->beginTransaction();
$user = new User([
'name' => $nick,
'password' => $password_hash,
'email' => $email,
'api_key' => '',
'last_login_at' => null,
]);
$user->save();
$contact = new Contact([
'dect' => $dect,
'mobile' => $mobile,
]);
$contact->user()
->associate($user)
->save();
$personalData = new PersonalData([
'first_name' => $preName,
'last_name' => $lastName,
'pronoun' => $pronoun,
'shirt_size' => $tshirt_size,
'planned_arrival_date' => $enable_planned_arrival ? Carbon::createFromTimestamp($planned_arrival_date) : null,
]);
$personalData->user()
->associate($user)
->save();
$settings = new Settings([
'language' => $session->get('locale'),
'theme' => config('theme'),
'email_human' => $email_by_human_allowed,
'email_messages' => $email_messages,
'email_goody' => $email_goody,
'email_shiftinfo' => $email_shiftinfo,
'email_news' => $email_news,
'mobile_show' => $mobile_show,
]);
$settings->user()
->associate($user)
->save();
$state = new State([]);
if (config('autoarrive')) {
$state->arrived = true;
$state->arrival_date = new Carbon();
}
$state->user()
->associate($user)
->save();
if ($session->has('oauth2_connect_provider') && $session->has('oauth2_user_id')) {
$oauth = new OAuth([
'provider' => $session->get('oauth2_connect_provider'),
'identifier' => $session->get('oauth2_user_id'),
'access_token' => $session->get('oauth2_access_token'),
'refresh_token' => $session->get('oauth2_refresh_token'),
'expires_at' => $session->get('oauth2_expires_at'),
]);
$oauth->user()
->associate($user)
->save();
$session->remove('oauth2_connect_provider');
$session->remove('oauth2_user_id');
$session->remove('oauth2_access_token');
$session->remove('oauth2_refresh_token');
$session->remove('oauth2_expires_at');
}
// Assign user-group and set password
$defaultGroup = Group::find(auth()->getDefaultRole());
$user->groups()->attach($defaultGroup);
if ($enable_password) {
auth()->setPassword($user, $request->postData('password'));
}
// Assign angel-types
$user_angel_types_info = [];
foreach ($selected_angel_types as $selected_angel_type_id) {
$angelType = AngelType::findOrFail($selected_angel_type_id);
$user->userAngelTypes()->attach($angelType);
$user_angel_types_info[] = $angelType->name;
}
// Commit complete user data
$db->commit();
engelsystem_log(
'User ' . User_Nick_render($user, true)
. ' signed up as: ' . join(', ', $user_angel_types_info)
);
success(__('Angel registration successful!'));
// User is already logged in - that means a supporter has registered an angel. Return to register page.
if ($authUser) {
throw_redirect(page_link_to('register'));
}
// If a welcome message is present, display it on the next page
if ($config->get('welcome_msg')) {
$session->set('show_welcome', true);
}
// Login the user
if ($user->oauth->count()) {
/** @var OAuth $provider */
$provider = $user->oauth->first();
throw_redirect(url('/oauth/' . $provider->provider));
}
throw_redirect(page_link_to('/'));
}
}
$buildup_start_date = time();
$teardown_end_date = null;
if ($buildup = $config->get('buildup_start')) {
/** @var Carbon $buildup */
$buildup_start_date = $buildup->getTimestamp();
}
if ($teardown = $config->get('teardown_end')) {
/** @var Carbon $teardown */
$teardown_end_date = $teardown->getTimestamp();
}
$form_data = $session->get('form_data');
$session->remove('form_data');
if (!$nick && !empty($form_data['name'])) {
$nick = $form_data['name'];
}
if (!$email && !empty($form_data['email'])) {
$email = $form_data['email'];
}
if (!$preName && !empty($form_data['first_name'])) {
$preName = $form_data['first_name'];
}
if (!$lastName && !empty($form_data['last_name'])) {
$lastName = $form_data['last_name'];
}
return page_with_title(register_title(), [
__('By completing this form you\'re registering as a Chaos-Angel. This script will create you an account in the angel task scheduler.'),
form_info(entry_required() . ' = ' . __('Entry required!')),
$msg,
msg(),
form([
div('row', [
div('col', [
form_text(
'username',
__('Nick') . ' ' . entry_required(),
$nick,
false,
24,
'nickname'
),
form_info(
'',
__('Use up to 24 letters, numbers or connecting punctuations for your nickname.')
),
]),
$enable_pronoun ? div('col', [
form_text('pronoun', __('Pronoun'), $pronoun, false, 15),
]) : '',
]),
$enable_user_name ? div('row', [
div('col', [
form_text('prename', __('First name'), $preName, false, 64, 'given-name'),
]),
div('col', [
form_text('lastname', __('Last name'), $lastName, false, 64, 'family-name'),
]),
]) : '',
div('row', [
div('col', [
form_email(
'email',
__('E-Mail') . ' ' . entry_required(),
$email,
false,
'email',
254
),
form_checkbox(
'email_shiftinfo',
__(
'settings.profile.email_shiftinfo',
[config('app_name')]
),
$email_shiftinfo
),
form_checkbox(
'email_news',
__('Notify me of new news'),
$email_news
),
form_checkbox(
'email_messages',
__('settings.profile.email_messages'),
$email_messages
),
form_checkbox(
'email_by_human_allowed',
__('Allow heaven angels to contact you by e-mail.'),
$email_by_human_allowed
),
$goodie_enabled ?
form_checkbox(
'email_goody',
__('To receive vouchers, give consent that nick, email address, worked hours and shirt size will be stored until the next similar event.')
. (config('privacy_email') ? ' ' . __('To withdraw your approval, send an email to <a href="mailto:%s">%1$s</a>.', [config('privacy_email')]) : ''),
$email_goody
) : '',
]),
$enable_dect ? div('col', [
form_text('dect', __('DECT'), $dect, false, 40, 'tel-local'),
]) : '',
div('col', [
form_text('mobile', __('Mobile'), $mobile, false, 40, 'tel-national'),
$enable_mobile_show ? form_checkbox(
'mobile_show',
__('Show mobile number to other users to contact me'),
$mobile_show
) : '',
]),
]),
div('row', [
$enable_password ? div('col', [
form_password('password', __('Password') . ' ' . entry_required(), 'new-password'),
]) : '',
$enable_planned_arrival ? div('col', [
form_date(
'planned_arrival_date',
__('Planned date of arrival') . ' ' . entry_required(),
$planned_arrival_date,
$buildup_start_date,
$teardown_end_date
),
]) : '',
]),
div('row', [
$enable_password ? div('col', [
form_password('password2', __('Confirm password') . ' ' . entry_required(), 'new-password'),
]) : '',
div('col', [
$goodie_tshirt ? form_select(
'tshirt_size',
__('Shirt size') . ' ' . entry_required(),
$tshirt_sizes,
$tshirt_size,
__('form.select_placeholder')
) : '',
]),
]),
div('row', [
div('col', [
form_checkboxes(
'angel_types',
__('What do you want to do?') . sprintf(
' (<a href="%s">%s</a>)',
url('/angeltypes/about'),
__('Description of job types')
),
$angel_types,
$selected_angel_types
),
form_info(
'',
__('Some angel types have to be confirmed later by a supporter at an introduction meeting. You can change your selection in the options section.')
),
]),
]),
form_submit('submit', __('Register')),
]),
]);
}
/**
* @return string
*/
function entry_required()
{
return icon('exclamation-triangle', 'text-info');
}

View File

@ -16,7 +16,7 @@ use Engelsystem\Helpers\Schedule\XmlParser;
use Engelsystem\Helpers\Uuid;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Models\Room as RoomModel;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\Schedule as ScheduleUrl;
use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Models\Shifts\Shift;
@ -110,10 +110,15 @@ class ImportSchedule extends BaseController
/** @var ScheduleUrl $schedule */
$schedule = ScheduleUrl::findOrNew($scheduleId);
if ($request->request->has('delete')) {
return $this->delete($schedule);
}
$data = $this->validate($request, [
'name' => 'required',
'url' => 'required',
'shift_type' => 'required|int',
'needed_from_shift_type' => 'optional|checked',
'minutes_before' => 'int',
'minutes_after' => 'int',
]);
@ -125,17 +130,19 @@ class ImportSchedule extends BaseController
$schedule->name = $data['name'];
$schedule->url = $data['url'];
$schedule->shift_type = $data['shift_type'];
$schedule->needed_from_shift_type = (bool) $data['needed_from_shift_type'];
$schedule->minutes_before = $data['minutes_before'];
$schedule->minutes_after = $data['minutes_after'];
$schedule->save();
$this->log->info(
'Schedule {name}: Url {url}, Shift Type {shift_type}, minutes before/after {before}/{after}',
'Schedule {name}: Url {url}, Shift Type {shift_type}, ({need}), minutes before/after {before}/{after}',
[
'name' => $schedule->name,
'url' => $schedule->name,
'shift_type' => $schedule->shift_type,
'need' => $schedule->needed_from_shift_type ? 'from shift type' : 'from room',
'before' => $schedule->minutes_before,
'after' => $schedule->minutes_after,
]
@ -146,6 +153,33 @@ class ImportSchedule extends BaseController
return redirect('/admin/schedule/load/' . $schedule->id);
}
protected function delete(ScheduleUrl $schedule): Response
{
foreach ($schedule->scheduleShifts as $scheduleShift) {
// Only guid is needed here
$event = new Event(
$scheduleShift->guid,
0,
new Room(''),
'',
'',
'',
Carbon::now(),
'',
'',
'',
'',
''
);
$this->deleteEvent($event, $schedule);
}
$schedule->delete();
$this->addNotification('schedule.delete.success');
return redirect('/admin/schedule');
}
public function loadSchedule(Request $request): Response
{
try {
@ -179,7 +213,7 @@ class ImportSchedule extends BaseController
[
'schedule_id' => $scheduleUrl->id,
'schedule' => $schedule,
'rooms' => [
'locations' => [
'add' => $newRooms,
],
'shifts' => [
@ -218,15 +252,15 @@ class ImportSchedule extends BaseController
$this->log('Started schedule "{name}" import', ['name' => $scheduleUrl->name]);
foreach ($newRooms as $room) {
$this->createRoom($room);
$this->createLocation($room);
}
$rooms = $this->getAllRooms();
$locations = $this->getAllLocations();
foreach ($newEvents as $event) {
$this->createEvent(
$event,
$shiftType,
$rooms
$locations
->where('name', $event->getRoom()->getName())
->first(),
$scheduleUrl
@ -237,15 +271,15 @@ class ImportSchedule extends BaseController
$this->updateEvent(
$event,
$shiftType,
$rooms
$locations
->where('name', $event->getRoom()->getName())
->first()
->first(),
$scheduleUrl
);
}
foreach ($deleteEvents as $event) {
$this->fireDeleteShiftEntryEvents($event);
$this->deleteEvent($event);
$this->deleteEvent($event, $scheduleUrl);
}
$scheduleUrl->touch();
@ -255,29 +289,30 @@ class ImportSchedule extends BaseController
return redirect($this->url, 303);
}
protected function createRoom(Room $room): void
protected function createLocation(Room $room): void
{
$roomModel = new RoomModel();
$roomModel->name = $room->getName();
$roomModel->save();
$location = new Location();
$location->name = $room->getName();
$location->save();
$this->log('Created schedule room "{room}"', ['room' => $room->getName()]);
$this->log('Created schedule location "{location}"', ['location' => $room->getName()]);
}
protected function fireDeleteShiftEntryEvents(Event $event): void
protected function fireDeleteShiftEntryEvents(Event $event, ScheduleUrl $schedule): void
{
$shiftEntries = $this->db
->table('shift_entries')
->select([
'shift_types.name', 'shifts.title', 'angel_types.name AS type', 'rooms.id AS room_id',
'shift_types.name', 'shifts.title', 'angel_types.name AS type', 'locations.id AS location_id',
'shifts.start', 'shifts.end', 'shift_entries.user_id', 'shift_entries.freeloaded',
])
->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('locations', 'locations.id', 'shifts.location_id')
->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())
->where('schedule_shift.schedule_id', $schedule->id)
->get();
foreach ($shiftEntries as $shiftEntry) {
@ -288,13 +323,13 @@ class ImportSchedule extends BaseController
'name' => $shiftEntry->name,
'title' => $shiftEntry->title,
'type' => $shiftEntry->type,
'room' => RoomModel::find($shiftEntry->room_id),
'location' => Location::find($shiftEntry->location_id),
'freeloaded' => $shiftEntry->freeloaded,
]);
}
}
protected function createEvent(Event $event, int $shiftTypeId, RoomModel $room, ScheduleUrl $scheduleUrl): void
protected function createEvent(Event $event, int $shiftTypeId, Location $location, ScheduleUrl $scheduleUrl): void
{
$user = auth()->user();
$eventTimeZone = Carbon::now()->timezone;
@ -304,7 +339,7 @@ class ImportSchedule extends BaseController
$shift->shift_type_id = $shiftTypeId;
$shift->start = $event->getDate()->copy()->timezone($eventTimeZone);
$shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone);
$shift->room()->associate($room);
$shift->location()->associate($location);
$shift->url = $event->getUrl() ?? '';
$shift->transaction_id = Uuid::uuidBy($scheduleUrl->id, '5c4ed01e');
$shift->createdBy()->associate($user);
@ -316,68 +351,81 @@ class ImportSchedule extends BaseController
$scheduleShift->save();
$this->log(
'Created schedule shift "{shift}" in "{room}" ({from} {to}, {guid})',
'Created schedule shift "{shift}" in "{location}" ({from} {to}, {guid})',
[
'shift' => $shift->title,
'room' => $shift->room->name,
'from' => $shift->start->format(DateTimeInterface::RFC3339),
'to' => $shift->end->format(DateTimeInterface::RFC3339),
'guid' => $scheduleShift->guid,
'shift' => $shift->title,
'location' => $shift->location->name,
'from' => $shift->start->format(DateTimeInterface::RFC3339),
'to' => $shift->end->format(DateTimeInterface::RFC3339),
'guid' => $scheduleShift->guid,
]
);
}
protected function updateEvent(Event $event, int $shiftTypeId, RoomModel $room): void
protected function updateEvent(Event $event, int $shiftTypeId, Location $location, ScheduleUrl $schedule): void
{
$user = auth()->user();
$eventTimeZone = Carbon::now()->timezone;
/** @var ScheduleShift $scheduleShift */
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->first();
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first();
$shift = $scheduleShift->shift;
$oldShift = Shift::find($shift->id);
$shift->title = $event->getTitle();
$shift->shift_type_id = $shiftTypeId;
$shift->start = $event->getDate()->copy()->timezone($eventTimeZone);
$shift->end = $event->getEndDate()->copy()->timezone($eventTimeZone);
$shift->room()->associate($room);
$shift->location()->associate($location);
$shift->url = $event->getUrl() ?? '';
$shift->updatedBy()->associate($user);
$shift->save();
$this->fireUpdateShiftUpdateEvent($oldShift, $shift);
$this->log(
'Updated schedule shift "{shift}" in "{room}" ({from} {to}, {guid})',
'Updated schedule shift "{shift}" in "{location}" ({from} {to}, {guid})',
[
'shift' => $shift->title,
'room' => $shift->room->name,
'from' => $shift->start->format(DateTimeInterface::RFC3339),
'to' => $shift->end->format(DateTimeInterface::RFC3339),
'guid' => $scheduleShift->guid,
'shift' => $shift->title,
'location' => $shift->location->name,
'from' => $shift->start->format(DateTimeInterface::RFC3339),
'to' => $shift->end->format(DateTimeInterface::RFC3339),
'guid' => $scheduleShift->guid,
]
);
}
protected function deleteEvent(Event $event): void
protected function deleteEvent(Event $event, ScheduleUrl $schedule): void
{
/** @var ScheduleShift $scheduleShift */
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->first();
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first();
$shift = $scheduleShift->shift;
$shift->delete();
$this->fireDeleteShiftEntryEvents($event, $schedule);
$this->log(
'Deleted schedule shift "{shift}" in {room} ({from} {to}, {guid})',
'Deleted schedule shift "{shift}" in {location} ({from} {to}, {guid})',
[
'shift' => $shift->title,
'room' => $shift->room->name,
'from' => $shift->start->format(DateTimeInterface::RFC3339),
'to' => $shift->end->format(DateTimeInterface::RFC3339),
'guid' => $scheduleShift->guid,
'shift' => $shift->title,
'location' => $shift->location->name,
'from' => $shift->start->format(DateTimeInterface::RFC3339),
'to' => $shift->end->format(DateTimeInterface::RFC3339),
'guid' => $scheduleShift->guid,
]
);
}
protected function fireUpdateShiftUpdateEvent(Shift $oldShift, Shift $newShift): void
{
event('shift.updating', [
'shift' => $newShift,
'oldShift' => $oldShift,
]);
}
/**
* @param Request $request
* @return Event[]|Room[]|RoomModel[]
* @return Event[]|Room[]|Location[]
* @throws ErrorException
*/
protected function getScheduleData(Request $request)
@ -420,10 +468,10 @@ class ImportSchedule extends BaseController
protected function newRooms(array $scheduleRooms): array
{
$newRooms = [];
$allRooms = $this->getAllRooms();
$allLocations = $this->getAllLocations();
foreach ($scheduleRooms as $room) {
if ($allRooms->where('name', $room->getName())->count()) {
if ($allLocations->where('name', $room->getName())->count()) {
continue;
}
@ -456,7 +504,7 @@ class ImportSchedule extends BaseController
$scheduleEvents = [];
/** @var Event[] $deleteEvents */
$deleteEvents = [];
$rooms = $this->getAllRooms();
$locations = $this->getAllLocations();
$eventTimeZone = Carbon::now()->timezone;
foreach ($schedule->getDay() as $day) {
@ -477,19 +525,19 @@ class ImportSchedule extends BaseController
$scheduleEventsGuidList = array_keys($scheduleEvents);
$existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList);
foreach ($existingShifts as $shift) {
$guid = $shift->guid;
foreach ($existingShifts as $scheduleShift) {
$guid = $scheduleShift->guid;
/** @var Shift $shift */
$shift = Shift::with('room')->find($shift->shift_id);
$shift = Shift::with('location')->find($scheduleShift->shift_id);
$event = $scheduleEvents[$guid];
$room = $rooms->where('name', $event->getRoom()->getName())->first();
$location = $locations->where('name', $event->getRoom()->getName())->first();
if (
$shift->title != $event->getTitle()
|| $shift->shift_type_id != $shiftType
|| $shift->start != $event->getDate()
|| $shift->end != $event->getEndDate()
|| $shift->room_id != ($room->id ?? '')
|| $shift->location_id != ($location->id ?? '')
|| $shift->url != ($event->getUrl() ?? '')
) {
$changeEvents[$guid] = $event;
@ -503,8 +551,8 @@ class ImportSchedule extends BaseController
}
$scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleUrl, $scheduleEventsGuidList);
foreach ($scheduleShifts as $shift) {
$event = $this->eventFromScheduleShift($shift);
foreach ($scheduleShifts as $scheduleShift) {
$event = $this->eventFromScheduleShift($scheduleShift);
$deleteEvents[$event->getGuid()] = $event;
}
@ -513,14 +561,13 @@ class ImportSchedule extends BaseController
protected function eventFromScheduleShift(ScheduleShift $scheduleShift): Event
{
/** @var Shift $shift */
$shift = Shift::with('room')->find($scheduleShift->shift_id);
$shift = $scheduleShift->shift;
$duration = $shift->start->diff($shift->end);
return new Event(
$scheduleShift->guid,
0,
new Room($shift->room->name),
new Room($shift->location->name),
$shift->title,
'',
'n/a',
@ -534,11 +581,11 @@ class ImportSchedule extends BaseController
}
/**
* @return RoomModel[]|Collection
* @return Location[]|Collection
*/
protected function getAllRooms(): Collection
protected function getAllLocations(): Collection
{
return RoomModel::all();
return Location::all();
}
/**

View File

@ -8,7 +8,7 @@ use Engelsystem\Models\User\User;
*/
function myshifts_title()
{
return __('My shifts');
return __('profile.my-shifts');
}
/**
@ -35,23 +35,24 @@ function user_myshifts()
$shifts_user = User::find($shift_entry_id);
if ($request->has('reset')) {
if ($request->input('reset') == 'ack') {
User_reset_api_key($user);
auth()->resetApiKey($user);
engelsystem_log(sprintf('API key resetted (%s).', User_Nick_render($user, true)));
success(__('Key changed.'));
throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
throw_redirect(url('/users', ['action' => 'view', 'user_id' => $shifts_user->id]));
}
return page_with_title(__('Reset API key'), [
error(
__('If you reset the key, the url to your iCal- and JSON-export and your atom/rss feed changes! You have to update it in every application using one of these exports.'),
true
),
button(page_link_to('user_myshifts', ['reset' => 'ack']), __('Continue'), 'btn-danger'),
button(url('/user-myshifts', ['reset' => 'ack']), __('Continue'), 'btn-danger'),
]);
} elseif ($request->has('edit') && preg_match('/^\d+$/', $request->input('edit'))) {
$shift_entry_id = $request->input('edit');
/** @var ShiftEntry $shiftEntry */
$shiftEntry = ShiftEntry::where('id', $shift_entry_id)
->where('user_id', $shifts_user->id)
->with(['shift', 'shift.shiftType', 'shift.room', 'user'])
->with(['shift', 'shift.shiftType', 'shift.location', 'user'])
->first();
if (!empty($shiftEntry)) {
$shift = $shiftEntry->shift;
@ -90,14 +91,14 @@ function user_myshifts()
. '. Freeloaded: ' . ($freeloaded ? 'YES Comment: ' . $freeloaded_comment : 'NO')
);
success(__('Shift saved.'));
throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
throw_redirect(url('/users', ['action' => 'view', 'user_id' => $shifts_user->id]));
}
}
return ShiftEntry_edit_view(
$shifts_user,
$shift->start->format(__('Y-m-d H:i')) . ', ' . shift_length($shift),
$shift->room->name,
$shift->start->format(__('general.datetime')) . ', ' . shift_length($shift),
$shift->location->name,
$shift->shiftType->name,
$shiftEntry->angelType->name,
$shiftEntry->user_comment,
@ -106,10 +107,10 @@ function user_myshifts()
auth()->can('user_shifts_admin')
);
} else {
throw_redirect(page_link_to('user_myshifts'));
throw_redirect(url('/user-myshifts'));
}
}
throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
throw_redirect(url('/users', ['action' => 'view', 'user_id' => $shifts_user->id]));
return '';
}

View File

@ -3,7 +3,7 @@
use Engelsystem\Database\Db;
use Engelsystem\Helpers\Carbon;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Room;
use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\UserAngelType;
@ -34,7 +34,7 @@ function user_shifts()
$request = request();
if (auth()->user()->isFreeloader()) {
throw_redirect(page_link_to('user_myshifts'));
throw_redirect(url('/user-myshifts'));
}
if ($request->has('edit_shift')) {
@ -107,40 +107,42 @@ function update_ShiftsFilter(ShiftsFilter $shiftsFilter, $user_shifts_admin, $da
{
$shiftsFilter->setUserShiftsAdmin($user_shifts_admin);
$shiftsFilter->setFilled(check_request_int_array('filled', $shiftsFilter->getFilled()));
$shiftsFilter->setRooms(check_request_int_array('rooms', $shiftsFilter->getRooms()));
$shiftsFilter->setLocations(check_request_int_array('locations', $shiftsFilter->getLocations()));
$shiftsFilter->setTypes(check_request_int_array('types', $shiftsFilter->getTypes()));
update_ShiftsFilter_timerange($shiftsFilter, $days);
}
/**
* @return Room[]|Collection
* @return Location[]|Collection
*/
function load_rooms(bool $onlyWithActiveShifts = false)
function load_locations(bool $onlyWithActiveShifts = false)
{
$rooms = Room::orderBy('name');
$locations = Location::orderBy('name');
if ($onlyWithActiveShifts) {
$roomIdsFromAngelType = NeededAngelType::query()
->whereNotNull('room_id')
->select('room_id');
$locationIdsFromAngelType = NeededAngelType::query()
->whereNotNull('location_id')
->select('location_id');
$roomIdsFromShift = Shift::query()
$locationIdsFromShift = Shift::query()
->leftJoin('needed_angel_types', 'shifts.id', 'needed_angel_types.shift_id')
->whereNotNull('needed_angel_types.shift_id')
->select('shifts.room_id');
->leftJoin('needed_angel_types AS nast', 'shifts.shift_type_id', 'nast.shift_type_id')
->whereNotNull('needed_angel_types.id')
->orWhereNotNull('nast.id')
->select('shifts.location_id');
$rooms->whereIn('id', $roomIdsFromAngelType)
->orWhereIn('id', $roomIdsFromShift);
$locations->whereIn('id', $locationIdsFromAngelType)
->orWhereIn('id', $locationIdsFromShift);
}
$rooms = $rooms->get();
$locations = $locations->get();
if ($rooms->isEmpty()) {
error(__('The administration has not configured any rooms yet.'));
throw_redirect(page_link_to('/'));
if ($locations->isEmpty()) {
error(__('The administration has not configured any locations yet.'));
throw_redirect(url('/'));
}
return $rooms;
return $locations;
}
/**
@ -162,7 +164,7 @@ function load_days()
error(__('The administration has not configured any shifts yet.'));
// Do not try to redirect to the current page
if (config('home_site') != 'user_shifts') {
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
}
return $days;
@ -174,10 +176,11 @@ function load_days()
function load_types()
{
$user = auth()->user();
$isShico = auth()->can('admin_shifts');
if (!AngelType::count()) {
error(__('The administration has not configured any angeltypes yet - or you are not subscribed to any angeltype.'));
throw_redirect(page_link_to('/'));
throw_redirect(url('/'));
}
$types = Db::select(
@ -197,8 +200,11 @@ function load_types()
ON (
`user_angel_type`.`angel_type_id`=`angel_types`.`id`
AND `user_angel_type`.`user_id`=?
)
ORDER BY `angel_types`.`name`
)'
. ($isShico ? '' :
'WHERE angel_types.hide_on_shift_view = 0
OR user_angel_type.user_id IS NOT NULL ') .
'ORDER BY `angel_types`.`name`
',
[
$user->id,
@ -229,7 +235,7 @@ function view_user_shifts()
$session = session();
$days = load_days();
$rooms = load_rooms(true);
$locations = load_locations(true);
$types = load_types();
$ownAngelTypes = [];
@ -246,8 +252,8 @@ function view_user_shifts()
}
if (!$session->has('shifts-filter')) {
$room_ids = $rooms->pluck('id')->toArray();
$shiftsFilter = new ShiftsFilter(auth()->can('user_shifts_admin'), $room_ids, $ownAngelTypes);
$location_ids = $locations->pluck('id')->toArray();
$shiftsFilter = new ShiftsFilter(auth()->can('user_shifts_admin'), $location_ids, $ownAngelTypes);
$session->set('shifts-filter', $shiftsFilter->sessionExport());
}
@ -259,7 +265,7 @@ function view_user_shifts()
$shiftCalendarRenderer = shiftCalendarRendererByShiftFilter($shiftsFilter);
if (empty($user->api_key)) {
User_reset_api_key($user, false);
auth()->resetApiKey($user);
}
$filled = [
@ -277,24 +283,29 @@ function view_user_shifts()
$end_day = $shiftsFilter->getEnd()->format('Y-m-d');
$end_time = $shiftsFilter->getEnd()->format('H:i');
$canSignUpForShifts = true;
if (config('signup_requires_arrival') && !$user->state->arrived) {
$canSignUpForShifts = false;
info(render_user_arrived_hint());
}
$formattedDays = collect($days)->map(function ($value) {
return Carbon::make($value)->format(__('Y-m-d'));
return dateWithEventDay(Carbon::make($value)->format('Y-m-d'));
})->toArray();
$link = button(url('/admin-shifts'), icon('plus-lg'), 'add');
return page([
div('col-md-12', [
msg(),
view(__DIR__ . '/../../resources/views/pages/user-shifts.html', [
'title' => shifts_title(),
'room_select' => make_select(
$rooms,
$shiftsFilter->getRooms(),
'rooms',
icon('pin-map-fill') . __('Rooms')
'add_link' => auth()->can('admin_shifts') ? $link : '',
'location_select' => make_select(
$locations,
$shiftsFilter->getLocations(),
'locations',
icon('pin-map-fill') . __('Locations')
),
'start_select' => html_select_key(
'start_day',
@ -314,7 +325,10 @@ function view_user_shifts()
$types,
$shiftsFilter->getTypes(),
'types',
icon('person-lines-fill') . __('Angeltypes') . '<sup>1</sup>',
icon('person-lines-fill') . __('angeltypes.angeltypes')
. ' <small><span class="bi bi-info-circle-fill text-info" data-bs-toggle="tooltip" title="'
. __('The tasks shown here are influenced by the angeltypes you joined already!')
. '"></span></small>',
$ownAngelTypes
),
'filled_select' => make_select(
@ -323,12 +337,6 @@ function view_user_shifts()
'filled',
icon('person-fill-slash') . __('Occupancy')
),
'task_notice' =>
'<sup>1</sup>'
. __('The tasks shown here are influenced by the angeltypes you joined already!')
. ' <a href="' . url('/angeltypes/about') . '">'
. __('Description of the jobs.')
. '</a>',
'shifts_table' => msg() . $shiftCalendarRenderer->render(),
'ical_text' => div('mt-3', ical_hint()),
'filter' => __('Filter'),
@ -340,7 +348,11 @@ function view_user_shifts()
'set_last_4h' => __('last 4h'),
'set_next_4h' => __('next 4h'),
'set_next_8h' => __('next 8h'),
'buttons' => button(
'random' => auth()->can('user_shifts') && $canSignUpForShifts ? button(
url('/shifts/random'),
icon('dice-4-fill') . __('shifts.random')
) : '',
'dashboard' => button(
public_dashboard_link(),
icon('speedometer2') . __('Public Dashboard')
),
@ -364,9 +376,9 @@ function ical_hint()
return heading(__('iCal export and API') . ' ' . button_help('user/ical'), 2)
. '<p>' . sprintf(
__('Export your own shifts. <a href="%s">iCal format</a> or <a href="%s">JSON format</a> available (please keep secret, otherwise <a href="%s">reset the api key</a>).'),
page_link_to('ical', ['key' => $user->api_key]),
page_link_to('shifts_json_export', ['key' => $user->api_key]),
page_link_to('user_myshifts', ['reset' => 1])
url('/ical', ['key' => $user->api_key]),
url('/shifts-json-export', ['key' => $user->api_key]),
url('/user-myshifts', ['reset' => 1])
)
. ' <button class="btn btn-sm btn-danger" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseApiKey"
@ -377,15 +389,6 @@ function ical_hint()
. '<p id="collapseApiKey" class="collapse"><code>' . $user->api_key . '</code></p>';
}
/**
* @param array $array
* @return array
*/
function get_ids_from_array($array)
{
return $array['id'];
}
/**
* @param array $items
* @param array $selected
@ -418,7 +421,7 @@ function make_select($items, $selected, $name, $title = null, $ownSelect = [])
$htmlItems[] = '<div class="form-check">'
. '<input class="form-check-input" type="checkbox" id="' . $id . '" name="' . $name . '[]" value="' . $i['id'] . '" '
. (in_array($i['id'], $selected) ? ' checked="checked"' : '')
. '><label class="form-check-label" for="' . $id . '">' . $i['name'] . '</label>'
. '><label class="form-check-label" for="' . $id . '">' . htmlspecialchars($i['name']) . '</label>'
. (!isset($i['enabled']) || $i['enabled'] ? '' : icon('mortarboard-fill'))
. '</div>';
}

View File

@ -24,52 +24,28 @@ function form_hidden($name, $value)
* @param array $data_attributes
* @return string
*/
function form_spinner(string $name, string $label, int $value, array $data_attributes = [])
function form_spinner(string $name, string $label, int $value, array $data_attributes = [], bool $isDisabled = false)
{
$id = 'spinner-' . $name;
$attr = '';
foreach ($data_attributes as $attr_key => $attr_value) {
$attr .= ' data-' . $attr_key . '="' . $attr_value . '"';
}
$disabled = $isDisabled ? ' disabled' : '';
return form_element($label, '
<div class="input-group">
<input id="' . $id . '" class="form-control" type="number" min="0" step="1" name="' . $name . '" value="' . $value . '"' . $attr . ' />
<button class="btn btn-secondary spinner-down" type="button" data-input-id="' . $id . '"' . $attr . '>
<input id="' . $id . '" class="form-control" type="number" min="0" step="1" name="' . $name . '" value="' . $value . '"' . $attr . $disabled . '/>
<button class="btn btn-secondary spinner-down' . $disabled . '" type="button" data-input-id="' . $id . '"' . $attr . '>
' . icon('dash-lg') . '
</button>
<button class="btn btn-secondary spinner-up" type="button" data-input-id="' . $id . '"' . $attr . '>
<button class="btn btn-secondary spinner-up' . $disabled . '" type="button" data-input-id="' . $id . '"' . $attr . '>
' . icon('plus-lg') . '
</button>
</div>
', $id);
}
/**
* Render a bootstrap datepicker
*
* @param string $name Name of the parameter
* @param string $label Label
* @param int|Carbon $value Unix Timestamp
* @param string $start_date Earliest possible date
* @param string $end_date
* @return string HTML
*/
function form_date($name, $label, $value, $start_date = '', $end_date = '')
{
$dom_id = $name . '-date';
$value = ($value instanceof Carbon) ? $value->getTimestamp() : $value;
$value = is_numeric($value) ? date('Y-m-d', $value) : '';
$start_date = is_numeric($start_date) ? date('Y-m-d', $start_date) : '';
$end_date = is_numeric($end_date) ? date('Y-m-d', $end_date) : '';
return form_element(
$label,
'<input class="form-control" id="' . $dom_id . '" type="date" placeholder="YYYY-MM-DD" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}" min="' . $start_date . '" max="' . $end_date . '" name="' . $name . '" value="' . htmlspecialchars((string) $value) . '" autocomplete="off">',
$dom_id
);
}
/**
* Render a bootstrap datepicker
*
@ -93,24 +69,6 @@ function form_datetime(string $name, string $label, $value)
', $dom_id, $name, htmlspecialchars($value ? $value->format('Y-m-d H:i') : '')), $dom_id);
}
/**
* Rendert eine Liste von Checkboxen für ein Formular
*
* @param string $name Die Namen der Checkboxen werden aus name_key gebildet
* @param string $label Die Beschriftung der Liste
* @param array $items Array mit den einzelnen Checkboxen
* @param array $selected Array mit den Keys, die ausgewählt sind
* @return string
*/
function form_checkboxes($name, $label, $items, $selected)
{
$html = form_element($label, '');
foreach ($items as $key => $item) {
$html .= form_checkbox($name . '_' . $key, $item, in_array($key, $selected));
}
return $html;
}
/**
* Rendert eine Checkbox
*
@ -128,14 +86,15 @@ function form_checkbox($name, $label, $selected, $value = 'checked', $html_id =
}
return '<div class="form-check">'
. '<input class="form-check-input" type="checkbox" id="' . $html_id . '" name="' . $name . '" value="' . htmlspecialchars((string) $value) . '" '
. '<input class="form-check-input" type="checkbox" id="' . $html_id . '" '
. 'name="' . htmlspecialchars($name) . '" value="' . $value . '" '
. ($selected ? ' checked="checked"' : '') . ' /><label class="form-check-label" for="' . $html_id . '">'
. $label
. '</label></div>';
}
/**
* Rendert einen Radio
* Renders a radio button
*
* @param string $name
* @param string $label
@ -184,9 +143,9 @@ function form_info($label, $text = '')
* @param string $buttonType
* @return string
*/
function form_submit($name, $label, $class = '', $wrapForm = true, $buttonType = 'primary')
function form_submit($name, $label, $class = '', $wrapForm = true, $buttonType = 'primary', $title = '')
{
$button = '<button class="btn btn-' . $buttonType . ($class ? ' ' . $class : '') . '" type="submit" name="' . $name . '">'
$button = '<button class="btn btn-' . $buttonType . ($class ? ' ' . $class : '') . '" type="submit" name="' . $name . '" title="' . $title . '">'
. $label
. '</button>';
@ -232,67 +191,6 @@ function form_text($name, $label, $value, $disabled = false, $maxlength = null,
);
}
/**
* Renders a text input with placeholder instead of label.
*
* @param string $name Input name
* @param string $placeholder Placeholder
* @param string $value The value
* @param boolean $disabled Is the field enabled?
* @return string
*/
function form_text_placeholder($name, $placeholder, $value, $disabled = false)
{
$disabled = $disabled ? ' disabled="disabled"' : '';
return form_element(
'',
'<input class="form-control" id="form_' . $name . '" type="text" name="' . $name
. '" value="' . htmlspecialchars((string) $value) . '" placeholder="' . $placeholder
. '" ' . $disabled . '/>'
);
}
/**
* Rendert ein Formular-Emailfeld
*
* @param string $name
* @param string $label
* @param string $value
* @param bool $disabled
* @param string|null $autocomplete
* @param int|null $maxlength
*
* @return string
*/
function form_email($name, $label, $value, $disabled = false, $autocomplete = null, $maxlength = null)
{
$disabled = $disabled ? ' disabled="disabled"' : '';
$autocomplete = $autocomplete ? ' autocomplete="' . $autocomplete . '"' : '';
$maxlength = $maxlength ? ' maxlength=' . (int) $maxlength : '';
return form_element(
$label,
'<input class="form-control" id="form_' . $name . '" type="email" name="' . $name . '" value="'
. htmlspecialchars((string) $value) . '" ' . $disabled . $autocomplete . $maxlength . '/>',
'form_' . $name
);
}
/**
* Rendert ein Formular-Dateifeld
*
* @param string $name
* @param string $label
* @return string
*/
function form_file($name, $label)
{
return form_element(
$label,
sprintf('<input id="form_%1$s" type="file" name="%1$s" />', $name),
'form_' . $name
);
}
/**
* Rendert ein Formular-Passwortfeld
*
@ -308,7 +206,7 @@ function form_password($name, $label, $autocomplete, $disabled = false)
return form_element(
$label,
sprintf(
'<input class="form-control" id="form_%1$s" type="password" name="%1$s" minlength="%2$s" value="" autocomplete="%3$s"%4$s/>',
'<input class="form-control" id="form_%1$s" type="password" name="%1$s" minlength="%2$s" value="" autocomplete="%3$s" %4$s>',
$name,
config('min_password_length'),
$autocomplete,
@ -318,25 +216,6 @@ function form_password($name, $label, $autocomplete, $disabled = false)
);
}
/**
* Renders a password input with placeholder instead of label.
*
* @param string $name
* @param string $placeholder
* @param bool $disabled
* @return string
*/
function form_password_placeholder($name, $placeholder, $disabled = false)
{
$disabled = $disabled ? ' disabled="disabled"' : '';
return form_element(
'',
'<input class="form-control" id="form_' . $name . '" type="password" name="'
. $name . '" value="" placeholder="' . $placeholder . '" ' . $disabled . '/>',
'form_' . $name
);
}
/**
* Rendert ein Formular-Textfeld
*
@ -406,14 +285,14 @@ function form_element($label, $input, $for = '', $class = '')
*
* @param string[] $elements
* @param string $action
* @param bool $inline
* @param string $style
* @return string
*/
function form($elements, $action = '', $inline = false, $btnGroup = false)
function form($elements, $action = '', $style = '', $btnGroup = false)
{
return '<form action="' . $action . '" enctype="multipart/form-data" method="post"'
. ($btnGroup ? ' class="btn-group"' : '')
. ($inline ? ' style="float:left"' : '') . '>'
. ($style ? ' style="' . $style . '"' : '') . '>'
. join($elements)
. form_csrf()
. '</form>';
@ -463,9 +342,13 @@ function html_select_key($dom_id, $name, $rows, $selected, $selectText = '')
}
foreach ($rows as $key => $row) {
if (($key == $selected) || ($row === $selected)) {
$html .= '<option value="' . $key . '" selected="selected">' . $row . '</option>';
$html .= '<option value="' . htmlspecialchars($key) . '" selected="selected">'
. htmlspecialchars($row)
. '</option>';
} else {
$html .= '<option value="' . $key . '">' . $row . '</option>';
$html .= '<option value="' . htmlspecialchars($key) . '">'
. htmlspecialchars($row)
. '</option>';
}
}
$html .= '</select>';

View File

@ -1,20 +1,9 @@
<?php
use Engelsystem\Models\Location;
use Engelsystem\Models\Question;
use Engelsystem\Models\Room;
use Engelsystem\UserHintsRenderer;
/**
* @param string $page
* @param array $parameters get parameters
* @return string
*/
function page_link_to($page = '', $parameters = [])
{
$page = str_replace('_', '-', $page);
return url($page, $parameters);
}
/**
* Render the user hints
*
@ -31,12 +20,17 @@ function header_render_hints()
$hints_renderer->addHint(user_angeltypes_unconfirmed_hint());
$hints_renderer->addHint(render_user_departure_date_hint());
$hints_renderer->addHint(user_driver_license_required_hint());
$hints_renderer->addHint(user_ifsg_certificate_required_hint());
// Important hints:
$hints_renderer->addHint(render_user_freeloader_hint(), true);
$hints_renderer->addHint(render_user_arrived_hint(), true);
$hints_renderer->addHint(render_user_arrived_hint(true), true);
$hints_renderer->addHint(render_user_pronoun_hint(), true);
$hints_renderer->addHint(render_user_firstname_hint(), true);
$hints_renderer->addHint(render_user_lastname_hint(), true);
$hints_renderer->addHint(render_user_tshirt_hint(), true);
$hints_renderer->addHint(render_user_dect_hint(), true);
$hints_renderer->addHint(render_user_mobile_hint(), true);
return $hints_renderer->render();
}
@ -62,10 +56,10 @@ function make_navigation()
$page = current_page();
$menu = [];
$pages = [
'news' => __('News'),
'meetings' => [__('Meetings'), 'user_meetings'],
'news' => __('news.title'),
'meetings' => [__('news.title.meetings'), 'user_meetings'],
'user_shifts' => __('Shifts'),
'angeltypes' => __('Angeltypes'),
'angeltypes' => __('angeltypes.angeltypes'),
'questions' => [__('Ask the Heaven'), 'question.add'],
];
@ -75,23 +69,30 @@ function make_navigation()
}
$title = ((array) $options)[0];
$menu[] = toolbar_item_link(page_link_to($menu_page), '', $title, $menu_page == $page);
$menu[] = toolbar_item_link(
url(str_replace('_', '-', $menu_page)),
'',
$title,
$menu_page == $page
);
}
$menu = make_room_navigation($menu);
$menu = make_location_navigation($menu);
$admin_menu = [];
$admin_pages = [
// path => name
// path => [name, permission]
// Examples:
// path => name,
// path => [name, permission],
'admin_arrive' => 'Arrive angels',
'admin_active' => 'Active angels',
'users' => ['All Angels', 'admin_user'],
'admin_free' => 'Free angels',
'admin/questions' => ['Answer questions', 'question.edit'],
'shifttypes' => 'Shifttypes',
'admin/shifttypes' => ['shifttype.shifttypes', 'shifttypes'],
'admin_shifts' => 'Create shifts',
'admin/rooms' => ['room.rooms', 'admin_rooms'],
'admin/locations' => ['location.locations', 'admin_locations'],
'admin_groups' => 'Grouprights',
'admin/schedule' => ['schedule.import', 'schedule.import'],
'admin/logs' => ['log.log', 'admin_log'],
@ -109,8 +110,8 @@ function make_navigation()
$title = ((array) $options)[0];
$admin_menu[] = toolbar_dropdown_item(
page_link_to($menu_page),
__($title),
url(str_replace('_', '-', $menu_page)),
htmlspecialchars(__($title)),
$menu_page == $page
);
}
@ -141,31 +142,36 @@ function menu_is_allowed(string $page, $options)
}
/**
* Adds room navigation to the given menu.
* Adds location navigation to the given menu.
*
* @param string[] $menu Rendered menu
* @return string[]
*/
function make_room_navigation($menu)
function make_location_navigation($menu)
{
if (!auth()->can('view_rooms')) {
if (!auth()->can('view_locations')) {
return $menu;
}
// Get a list of all rooms
$rooms = Room::orderBy('name')->get();
$room_menu = [];
if (auth()->can('admin_rooms')) {
$room_menu[] = toolbar_dropdown_item(page_link_to('admin/rooms'), __('Manage rooms'), false, 'list');
// Get a list of all locations
$locations = Location::orderBy('name')->get();
$location_menu = [];
if (auth()->can('admin_locations')) {
$location_menu[] = toolbar_dropdown_item(
url('/admin/locations'),
__('Manage locations'),
false,
'list'
);
}
if (count($room_menu) > 0) {
$room_menu[] = toolbar_dropdown_item_divider();
if (count($location_menu) > 0) {
$location_menu[] = toolbar_dropdown_item_divider();
}
foreach ($rooms as $room) {
$room_menu[] = toolbar_dropdown_item(room_link($room), $room->name, false, 'pin-map-fill');
foreach ($locations as $location) {
$location_menu[] = toolbar_dropdown_item(location_link($location), $location->name, false, 'pin-map-fill');
}
if (count($room_menu) > 0) {
$menu[] = toolbar_dropdown(__('Rooms'), $room_menu);
if (count($location_menu) > 0) {
$menu[] = toolbar_dropdown(__('Locations'), $location_menu);
}
return $menu;
}
@ -209,7 +215,7 @@ function admin_new_questions()
return null;
}
return '<a href="' . page_link_to('/admin/questions') . '">'
return '<a href="' . url('/admin/questions') . '">'
. __('There are unanswered questions!')
. '</a>';
}

View File

@ -2,9 +2,7 @@
use Engelsystem\Helpers\Carbon;
use Engelsystem\Http\Exceptions\HttpTemporaryRedirect;
use Engelsystem\Models\BaseModel;
use Engelsystem\ValidationResult;
use Illuminate\Support\Collection;
/**
* Provide page/request helper functions
@ -63,41 +61,6 @@ function throw_redirect($url)
throw new HttpTemporaryRedirect($url);
}
/**
* Echoes given output and dies.
*
* @param string $output String to display
*/
function raw_output($output = '')
{
echo $output;
die();
}
/**
* Helper function for transforming list of entities into array for select boxes.
*
* @param array|Collection $data The data array
* @param string $key_name name of the column to use as id/key
* @param string $value_name name of the column to use as displayed value
*
* @return array|Collection
*/
function select_array($data, $key_name, $value_name)
{
if ($data instanceof Collection) {
return $data->mapWithKeys(function (BaseModel $model) use ($key_name, $value_name) {
return [$model->{$key_name} => $model->{$value_name}];
});
}
$return = [];
foreach ($data as $value) {
$return[$value[$key_name]] = $value[$value_name];
}
return $return;
}
/**
* Returns an int[] from given request param name.
*
@ -185,23 +148,6 @@ function strip_request_item($name, $default_value = null)
return $default_value;
}
/**
* Returns REQUEST value or default value (null) if not set.
*
* @param string $name
* @param string|null $default_value
* @return mixed|null
*/
function strip_request_tags($name, $default_value = null)
{
$request = request();
if ($request->has($name)) {
return strip_tags($request->input($name));
}
return $default_value;
}
/**
* Testet, ob der angegebene REQUEST Wert ein Integer ist, bzw.
* eine ID sein könnte.

View File

@ -72,17 +72,6 @@ function tabs($tabs, $selected = 0)
]);
}
/**
* Display muted (grey) text.
*
* @param string $text
* @return string
*/
function mute($text)
{
return '<span class="text-muted">' . $text . '</span>';
}
/**
* Renders a bootstrap label with given content and class.
*
@ -188,7 +177,7 @@ function toolbar_item_link($href, $icon, $label, $active = false)
return '<li class="nav-item">'
. '<a class="nav-link ' . ($active ? 'active" aria-current="page"' : '"') . ' href="' . $href . '">'
. ($icon != '' ? '<span class="bi bi-' . $icon . '"></span> ' : '')
. $label
. htmlspecialchars($label)
. '</a>'
. '</li>';
}
@ -196,11 +185,11 @@ function toolbar_item_link($href, $icon, $label, $active = false)
function toolbar_dropdown_item(string $href, string $label, bool $active, string $icon = null): string
{
return strtr(
'<li><a class="dropdown-item{active}"{aria} href="{href}">{icon} {label}</a></li>',
'<li><a class="dropdown-item{active}" {aria} href="{href}">{icon} {label}</a></li>',
[
'{href}' => $href,
'{icon}' => $icon === null ? '' : '<i class="bi bi-' . $icon . '"></i>',
'{label}' => $label,
'{label}' => htmlspecialchars($label),
'{active}' => $active ? ' active' : '',
'{aria}' => $active ? ' aria-current="page"' : '',
]
@ -235,7 +224,7 @@ EOT;
$template,
[
'{class}' => $active ? ' active' : '',
'{label}' => $label,
'{label}' => htmlspecialchars($label),
'{submenu}' => join("\n", $submenu),
]
);
@ -338,7 +327,7 @@ function render_table($columns, $rows, $data = true)
$html .= '</tr></thead>';
$html .= '<tbody>';
foreach ($rows as $row) {
$html .= '<tr>';
$html .= '<tr' . (isset($row['row-class']) ? ' class="' . $row['row-class'] . '"' : '') . '>';
foreach ($columns as $key => $column) {
$value = '&nbsp;';
if (isset($row[$key])) {
@ -362,7 +351,7 @@ function render_table($columns, $rows, $data = true)
* @param string $id
* @return string
*/
function button($href, $label, $class = '', $id = '')
function button($href, $label, $class = '', $id = '', $title = '')
{
if (!Str::contains(str_replace(['btn-sm', 'btn-xl'], '', $class), 'btn-')) {
$class = 'btn-secondary' . ($class ? ' ' . $class : '');
@ -370,7 +359,8 @@ function button($href, $label, $class = '', $id = '')
$idAttribute = $id ? 'id="' . $id . '"' : '';
return '<a ' . $idAttribute . ' href="' . $href . '" class="btn ' . $class . '">' . $label . '</a>';
return '<a ' . $idAttribute . ' href="' . $href
. '" class="btn ' . $class . '" title="' . $title . '">' . $label . '</a>';
}
/**
@ -396,9 +386,9 @@ function button_checkbox_selection($name, $label, $value)
*
* @return string
*/
function button_icon($href, $icon, $class = '')
function button_icon($href, $icon, $class = '', $title = '')
{
return button($href, icon($icon), $class);
return button($href, icon($icon), $class, '', $title);
}
/**

View File

@ -26,7 +26,7 @@ function AngelType_name_render(AngelType $angeltype, $plain = false)
}
return '<a href="' . angeltype_link($angeltype->id) . '">'
. ($angeltype->restricted ? icon('mortarboard-fill') : '') . $angeltype->name
. ($angeltype->restricted ? icon('mortarboard-fill') : '') . htmlspecialchars($angeltype->name)
. '</a>';
}
@ -60,12 +60,15 @@ function AngelType_render_membership(AngelType $user_angeltype)
*/
function AngelType_delete_view(AngelType $angeltype)
{
return page_with_title(sprintf(__('Delete angeltype %s'), $angeltype->name), [
$link = button($angeltype->id
? url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id])
: url('/angeltypes'), icon('chevron-left'), 'btn-sm', '', __('general.back'));
return page_with_title($link . ' ' . sprintf(__('Delete angeltype %s'), htmlspecialchars($angeltype->name)), [
info(sprintf(__('Do you want to delete angeltype %s?'), $angeltype->name), true),
form([
buttons([
button(page_link_to('angeltypes'), icon('x-lg') . __('cancel')),
form_submit('delete', icon('trash') . __('delete'), 'btn-danger', false),
button(url('/angeltypes'), icon('x-lg') . __('form.cancel')),
form_submit('delete', icon('trash'), 'btn-danger', false, 'primary', __('form.delete')),
]),
]),
], true);
@ -80,56 +83,96 @@ function AngelType_delete_view(AngelType $angeltype)
*/
function AngelType_edit_view(AngelType $angeltype, bool $supporter_mode)
{
return page_with_title(sprintf(__('Edit %s'), $angeltype->name), [
buttons([
button(page_link_to('angeltypes'), icon('person-lines-fill') . __('Angeltypes'), 'back'),
]),
msg(),
form([
$supporter_mode
? form_info(__('Name'), $angeltype->name)
: form_text('name', __('Name'), $angeltype->name),
$supporter_mode
? form_info(__('Requires introduction'), $angeltype->restricted ? __('Yes') : __('No'))
: form_checkbox('restricted', __('Requires introduction'), $angeltype->restricted),
form_info(
'',
__('Angel types which require introduction can only be used by an angel if enabled by a supporter (double opt-in).')
),
$supporter_mode
? form_info(__('No Self Sign Up allowed'), $angeltype->no_self_signup ? __('Yes') : __('No'))
: form_checkbox('no_self_signup', __('No Self Sign Up allowed'), $angeltype->no_self_signup),
$supporter_mode ?
$link = button($angeltype->id
? url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id])
: url('/angeltypes'), icon('chevron-left'), 'btn-sm', '', __('general.back'));
return page_with_title(
$link . ' ' . (
$angeltype->id ?
sprintf(__('Edit %s'), htmlspecialchars((string) $angeltype->name)) :
__('Create angeltype')
),
[
$angeltype->id ?
buttons([
button(url('/angeltypes'), icon('person-lines-fill') . __('angeltypes.angeltypes'), 'back'),
]) : '',
msg(),
form([
$supporter_mode
? form_info(__('general.name'), htmlspecialchars($angeltype->name))
: form_text('name', __('general.name'), $angeltype->name),
$supporter_mode
? form_info(__('angeltypes.restricted'), $angeltype->restricted ? __('Yes') : __('No'))
: form_checkbox(
'restricted',
__('angeltypes.restricted') .
' <span class="bi bi-info-circle-fill text-info" data-bs-toggle="tooltip" title="' .
__('angeltypes.restricted.info') . '"></span>',
$angeltype->restricted
),
$supporter_mode
? form_info(__('shift.self_signup'), $angeltype->shift_self_signup ? __('Yes') : __('No'))
: form_checkbox(
'shift_self_signup',
__('shift.self_signup') .
' <span class="bi bi-info-circle-fill text-info" data-bs-toggle="tooltip" title="' .
__('angeltypes.shift.self_signup.info') . '"></span>',
$angeltype->shift_self_signup
),
$supporter_mode ?
form_info(
__('Requires driver license'),
$angeltype->requires_driver_license
? __('Yes')
: __('No')
) :
form_checkbox(
'requires_driver_license',
__('Requires driver license'),
$angeltype->requires_driver_license
),
$supporter_mode && config('ifsg_enabled') ?
form_info(
__('angeltype.ifsg.required'),
$angeltype->requires_ifsg_certificate
? __('Yes')
: __('No')
) :
form_checkbox(
'requires_ifsg_certificate',
__('angeltype.ifsg.required'),
$angeltype->requires_ifsg_certificate
),
$supporter_mode
? form_info(__('Show on dashboard'), $angeltype->show_on_dashboard ? __('Yes') : __('No'))
: form_checkbox('show_on_dashboard', __('Show on dashboard'), $angeltype->show_on_dashboard),
$supporter_mode
? form_info(__('Hide at Registration'), $angeltype->hide_register ? __('Yes') : __('No'))
: form_checkbox('hide_register', __('Hide at Registration'), $angeltype->hide_register),
$supporter_mode
? form_info(__('angeltypes.hide_on_shift_view'), $angeltype->hide_on_shift_view ? __('Yes') : __('No'))
: form_checkbox(
'hide_on_shift_view',
__('angeltypes.hide_on_shift_view') .
' <span class="bi bi-info-circle-fill text-info" data-bs-toggle="tooltip" title="' .
__('angeltypes.hide_on_shift_view.info') . '"></span>',
$angeltype->hide_on_shift_view
),
form_textarea('description', __('general.description'), $angeltype->description),
form_info('', __('Please use markdown for the description.')),
heading(__('Contact'), 3),
form_info(
__('Requires driver license'),
$angeltype->requires_driver_license
? __('Yes')
: __('No')
) :
form_checkbox(
'requires_driver_license',
__('Requires driver license'),
$angeltype->requires_driver_license
'',
__('Primary contact person/desk for user questions.')
),
$supporter_mode
? form_info(__('Show on dashboard'), $angeltype->show_on_dashboard ? __('Yes') : __('No'))
: form_checkbox('show_on_dashboard', __('Show on dashboard'), $angeltype->show_on_dashboard),
$supporter_mode
? form_info(__('Hide at Registration'), $angeltype->hide_register ? __('Yes') : __('No'))
: form_checkbox('hide_register', __('Hide at Registration'), $angeltype->hide_register),
form_textarea('description', __('Description'), $angeltype->description),
form_info('', __('Please use markdown for the description.')),
heading(__('Contact'), 3),
form_info(
'',
__('Primary contact person/desk for user questions.')
),
form_text('contact_name', __('Name'), $angeltype->contact_name),
config('enable_dect') ? form_text('contact_dect', __('DECT'), $angeltype->contact_dect) : '',
form_text('contact_email', __('E-Mail'), $angeltype->contact_email),
form_submit('submit', __('Save')),
]),
]);
form_text('contact_name', __('general.name'), $angeltype->contact_name),
config('enable_dect') ? form_text('contact_dect', __('general.dect'), $angeltype->contact_dect) : '',
form_text('contact_email', __('general.email'), $angeltype->contact_email),
form_submit('submit', icon('save') . __('form.save')),
]),
]
);
}
/**
@ -151,28 +194,40 @@ function AngelType_view_buttons(
$user_driver_license,
$user
) {
$buttons = [
button(page_link_to('angeltypes'), icon('person-lines-fill') . __('Angeltypes'), 'back'),
];
if ($angeltype->requires_driver_license) {
$buttons[] = button(
user_driver_license_edit_link($user),
url('/settings/certificates'),
icon('person-vcard') . __('my driving license')
);
}
if (config('isfg_enabled') && $angeltype->requires_ifsg_certificate) {
$buttons[] = button(
url('/settings/certificates'),
icon('card-checklist') . __('angeltype.ifsg.own')
);
}
if (is_null($user_angeltype)) {
$buttons[] = button(
page_link_to('user_angeltypes', ['action' => 'add', 'angeltype_id' => $angeltype->id]),
icon('box-arrow-in-right') . __('join'),
'add'
url('/user-angeltypes', ['action' => 'add', 'angeltype_id' => $angeltype->id]),
icon('box-arrow-in-right') . ($admin_angeltypes ? '' : __('Join')),
'add',
'',
($admin_angeltypes ? 'Join' : ''),
);
} else {
if ($angeltype->requires_driver_license && !$user_driver_license->wantsToDrive()) {
error(__('This angeltype requires a driver license. Please enter your driver license information!'));
}
if (
config('ifsg_enabled') && $angeltype->requires_ifsg_certificate && !(
$user->license->ifsg_certificate_light || $user->license->ifsg_certificate
)
) {
error(__('angeltype.ifsg.required.info'));
}
if ($angeltype->restricted && !$user_angeltype->confirm_user_id) {
error(sprintf(
__('You are unconfirmed for this angeltype. Please go to the introduction for %s to get confirmed.'),
@ -180,21 +235,30 @@ function AngelType_view_buttons(
));
}
$buttons[] = button(
page_link_to('user_angeltypes', ['action' => 'delete', 'user_angeltype_id' => $user_angeltype->id]),
icon('box-arrow-right') . __('leave')
url('/user-angeltypes', ['action' => 'delete', 'user_angeltype_id' => $user_angeltype->id]),
icon('box-arrow-right') . ($admin_angeltypes ? '' : __('Leave')),
'',
'',
($admin_angeltypes ? __('Leave') : ''),
);
}
if ($admin_angeltypes || $supporter) {
$buttons[] = button(
page_link_to('angeltypes', ['action' => 'edit', 'angeltype_id' => $angeltype->id]),
icon('pencil') . __('edit')
url('/angeltypes', ['action' => 'edit', 'angeltype_id' => $angeltype->id]),
icon('pencil'),
'',
'',
__('form.edit')
);
}
if ($admin_angeltypes) {
$buttons[] = button(
page_link_to('angeltypes', ['action' => 'delete', 'angeltype_id' => $angeltype->id]),
icon('trash') . __('delete')
url('/angeltypes', ['action' => 'delete', 'angeltype_id' => $angeltype->id]),
icon('trash'),
'btn-danger',
'',
__('form.delete')
);
}
@ -218,7 +282,7 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
foreach ($members as $member) {
$member->name = User_Nick_render($member) . User_Pronoun_render($member);
if (config('enable_dect')) {
$member['dect'] = $member->contact->dect;
$member['dect'] = htmlspecialchars((string) $member->contact->dect);
}
if ($angeltype->requires_driver_license) {
$member['wants_to_drive'] = icon_bool($member->license->wantsToDrive());
@ -229,23 +293,29 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
$member['has_license_12t_truck'] = icon_bool($member->license->drive_12t);
$member['has_license_forklift'] = icon_bool($member->license->drive_forklift);
}
if ($angeltype->requires_ifsg_certificate && config('ifsg_enabled')) {
$member['ifsg_certificate'] = icon_bool($member->license->ifsg_certificate);
if (config('ifsg_light_enabled')) {
$member['ifsg_certificate_light'] = icon_bool($member->license->ifsg_certificate_light);
}
}
if ($angeltype->restricted && empty($member->pivot->confirm_user_id)) {
$member['actions'] = table_buttons([
button(
page_link_to(
'user_angeltypes',
url(
'/user-angeltypes',
['action' => 'confirm', 'user_angeltype_id' => $member->pivot->id]
),
__('confirm'),
__('Confirm'),
'btn-sm'
),
button(
page_link_to(
'user_angeltypes',
url(
'/user-angeltypes',
['action' => 'delete', 'user_angeltype_id' => $member->pivot->id]
),
__('deny'),
__('Deny'),
'btn-sm'
),
]);
@ -254,13 +324,15 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
if ($admin_angeltypes) {
$member['actions'] = table_buttons([
button(
page_link_to('user_angeltypes', [
url('/user-angeltypes', [
'action' => 'update',
'user_angeltype_id' => $member->pivot->id,
'supporter' => 0,
]),
icon('person-fill-down') . __('Remove supporter rights'),
'btn-sm'
icon('person-fill-down'),
'btn-sm',
'',
__('Remove supporter rights'),
),
]);
} else {
@ -272,22 +344,26 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
$member['actions'] = table_buttons([
$admin_angeltypes ?
button(
page_link_to('user_angeltypes', [
url('/user-angeltypes', [
'action' => 'update',
'user_angeltype_id' => $member->pivot->id,
'supporter' => 1,
]),
icon('person-fill-up') . __('Add supporter rights'),
'btn-sm'
icon('person-fill-up'),
'btn-sm',
'',
__('Add supporter rights'),
) :
'',
button(
page_link_to('user_angeltypes', [
url('/user-angeltypes', [
'action' => 'delete',
'user_angeltype_id' => $member->pivot->id,
]),
icon('trash') . __('remove'),
'btn-sm'
icon('trash'),
'btn-sm btn-danger',
'',
__('Remove'),
),
]);
}
@ -313,27 +389,34 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
function AngelType_view_table_headers(AngelType $angeltype, $supporter, $admin_angeltypes)
{
$headers = [
'name' => __('Nick'),
'dect' => __('DECT'),
'actions' => '',
'name' => __('general.nick'),
];
if (config('enable_dect')) {
$headers['dect'] = __('general.dect');
}
if ($angeltype->requires_driver_license && ($supporter || $admin_angeltypes)) {
$headers = [
'name' => __('Nick'),
'dect' => __('DECT'),
$headers = array_merge($headers, [
'wants_to_drive' => __('Driver'),
'has_car' => __('Has car'),
'has_license_car' => __('Car'),
'has_license_3_5t_transporter' => __('3,5t Transporter'),
'has_license_7_5t_truck' => __('7,5t Truck'),
'has_license_12t_truck' => __('12t Truck'),
'has_license_forklift' => __('Forklift'),
'actions' => '',
];
'has_license_car' => __('settings.certificates.drive_car'),
'has_license_3_5t_transporter' => __('settings.certificates.drive_3_5t'),
'has_license_7_5t_truck' => __('settings.certificates.drive_7_5t'),
'has_license_12t_truck' => __('settings.certificates.drive_12t'),
'has_license_forklift' => __('settings.certificates.drive_forklift'),
]);
}
if (!config('enable_dect')) {
unset($headers['dect']);
if (config('ifsg_enabled') && $angeltype->requires_ifsg_certificate && ($supporter || $admin_angeltypes)) {
if (config('ifsg_light_enabled')) {
$headers['ifsg_certificate_light'] = __('ifsg.certificate_light');
}
$headers['ifsg_certificate'] = __('ifsg.certificate');
}
$headers['actions'] = '';
return $headers;
}
@ -366,24 +449,29 @@ function AngelType_view(
ShiftCalendarRenderer $shiftCalendarRenderer,
$tab
) {
return page_with_title(sprintf(__('Team %s'), $angeltype->name), [
AngelType_view_buttons($angeltype, $user_angeltype, $admin_angeltypes, $supporter, $user_driver_license, $user),
msg(),
tabs([
__('Info') => AngelType_view_info(
$angeltype,
$members,
$admin_user_angeltypes,
$admin_angeltypes,
$supporter
),
__('Shifts') => AngelType_view_shifts(
$angeltype,
$shiftsFilterRenderer,
$shiftCalendarRenderer
),
], $tab),
], true);
$link = button(url('/angeltypes'), icon('chevron-left'), 'btn-sm', '', __('general.back'));
return page_with_title(
$link . ' ' . sprintf(__('Team %s'), htmlspecialchars($angeltype->name)),
[
AngelType_view_buttons($angeltype, $user_angeltype, $admin_angeltypes, $supporter, $user_driver_license, $user),
msg(),
tabs([
__('Info') => AngelType_view_info(
$angeltype,
$members,
$admin_user_angeltypes,
$admin_angeltypes,
$supporter
),
__('Shifts') => AngelType_view_shifts(
$angeltype,
$shiftsFilterRenderer,
$shiftCalendarRenderer
),
], $tab),
],
true
);
}
/**
@ -394,7 +482,7 @@ function AngelType_view(
*/
function AngelType_view_shifts(AngelType $angeltype, $shiftsFilterRenderer, $shiftCalendarRenderer)
{
$shifts = $shiftsFilterRenderer->render(page_link_to('angeltypes', [
$shifts = $shiftsFilterRenderer->render(url('/angeltypes', [
'action' => 'view',
'angeltype_id' => $angeltype->id,
]), ['type' => $angeltype->id]);
@ -423,10 +511,10 @@ function AngelType_view_info(
$info[] = AngelTypes_render_contact_info($angeltype);
}
$info[] = '<h3>' . __('Description') . '</h3>';
$info[] = '<h3>' . __('general.description') . '</h3>';
$parsedown = new Parsedown();
if ($angeltype->description != '') {
$info[] = $parsedown->parse($angeltype->description);
$info[] = $parsedown->parse(htmlspecialchars($angeltype->description));
}
list($supporters, $members_confirmed, $members_unconfirmed) = AngelType_view_members(
@ -462,11 +550,11 @@ function AngelType_view_info(
if ($admin_user_angeltypes) {
$info[] = buttons([
button(
page_link_to(
'user_angeltypes',
url(
'/user-angeltypes',
['action' => 'add', 'angeltype_id' => $angeltype->id]
),
__('Add'),
icon('plus-lg') . __('Add'),
'add'
),
]);
@ -477,12 +565,12 @@ function AngelType_view_info(
$info[] = '<h3>' . __('Unconfirmed') . '</h3>';
$info[] = buttons([
button(
page_link_to('user_angeltypes', ['action' => 'confirm_all', 'angeltype_id' => $angeltype->id]),
icon('check-lg') . __('confirm all')
url('/user-angeltypes', ['action' => 'confirm_all', 'angeltype_id' => $angeltype->id]),
icon('check-lg') . __('Confirm all')
),
button(
page_link_to('user_angeltypes', ['action' => 'delete_all', 'angeltype_id' => $angeltype->id]),
icon('trash') . __('deny all')
url('/user-angeltypes', ['action' => 'delete_all', 'angeltype_id' => $angeltype->id]),
icon('trash') . __('Deny all')
),
]);
$info[] = table($table_headers, $members_unconfirmed);
@ -500,9 +588,20 @@ function AngelType_view_info(
function AngelTypes_render_contact_info(AngelType $angeltype)
{
$info = [
__('Name') => [$angeltype->contact_name, $angeltype->contact_name],
__('DECT') => config('enable_dect') ? [sprintf('<a href="tel:%s">%1$s</a>', $angeltype->contact_dect), $angeltype->contact_dect] : null,
__('E-Mail') => [sprintf('<a href="mailto:%s">%1$s</a>', $angeltype->contact_email), $angeltype->contact_email],
__('general.name') => [
htmlspecialchars($angeltype->contact_name),
htmlspecialchars($angeltype->contact_name),
],
__('general.dect') => config('enable_dect')
? [
sprintf('<a href="tel:%s">%1$s</a>', htmlspecialchars($angeltype->contact_dect)),
htmlspecialchars($angeltype->contact_dect),
]
: null,
__('general.email') => [
sprintf('<a href="mailto:%s">%1$s</a>', htmlspecialchars($angeltype->contact_email)),
htmlspecialchars($angeltype->contact_email),
],
];
$contactInfo = [];
foreach ($info as $name => $data) {
@ -523,20 +622,22 @@ function AngelTypes_render_contact_info(AngelType $angeltype)
*/
function AngelTypes_list_view($angeltypes, bool $admin_angeltypes)
{
return page_with_title(angeltypes_title(), [
msg(),
buttons([
$admin_angeltypes
? button(page_link_to('angeltypes', ['action' => 'edit']), __('New angeltype'), 'add')
: '',
button(url('/angeltypes/about'), __('angeltypes.about')),
]),
table([
'name' => __('Name'),
'is_restricted' => icon('mortarboard-fill') . __('Requires introduction'),
'no_self_signup_allowed' => icon('pencil-square') . __('Self Sign Up Allowed'),
'membership' => __('Membership'),
'actions' => '',
], $angeltypes),
], true);
$link = button(url('/angeltypes', ['action' => 'edit']), icon('plus-lg'), 'add');
return page_with_title(
angeltypes_title() . ' ' . ($admin_angeltypes ? $link : ''),
[
msg(),
buttons([
button(url('/angeltypes/about'), __('angeltypes.about')),
]),
table([
'name' => __('general.name'),
'is_restricted' => icon('mortarboard-fill') . __('angeltypes.restricted'),
'shift_self_signup_allowed' => icon('pencil-square') . __('shift.self_signup.allowed'),
'membership' => __('Membership'),
'actions' => '',
], $angeltypes),
],
true,
);
}

View File

@ -42,7 +42,7 @@ function EventConfig_edit_view(
]),
div('row', [
div('col-md-6', [
form_submit('submit', __('Save')),
form_submit('submit', icon('save') . __('form.save')),
]),
]),
]),

View File

@ -0,0 +1,100 @@
<?php
use Engelsystem\Models\Location;
use Engelsystem\ShiftCalendarRenderer;
use Engelsystem\ShiftsFilterRenderer;
/**
*
* @param Location $location
* @param ShiftsFilterRenderer $shiftsFilterRenderer
* @param ShiftCalendarRenderer $shiftCalendarRenderer
* @return string
*/
function location_view(Location $location, ShiftsFilterRenderer $shiftsFilterRenderer, ShiftCalendarRenderer $shiftCalendarRenderer)
{
$user = auth()->user();
$assignNotice = '';
if (config('signup_requires_arrival') && !$user->state->arrived) {
$assignNotice = info(render_user_arrived_hint(), true);
}
$description = '';
if ($location->description) {
$description = '<h3>' . __('general.description') . '</h3>';
$parsedown = new Parsedown();
$description .= $parsedown->parse(htmlspecialchars($location->description));
}
$dect = '';
if (config('enable_dect') && $location->dect) {
$dect = heading(__('Contact'), 3)
. description([__('general.dect') => sprintf(
'<a href="tel:%s">%1$s</a>',
htmlspecialchars($location->dect)
)]);
}
$tabs = [];
if ($location->map_url) {
$tabs[__('location.map_url')] = sprintf(
'<div class="map">'
. '<iframe style="width: 100%%; min-height: 400px; border: 0 none;" src="%s"></iframe>'
. '</div>',
htmlspecialchars($location->map_url)
);
}
$tabs[__('Shifts')] = div('first', [
$shiftsFilterRenderer->render(url('/locations', [
'action' => 'view',
'location_id' => $location->id,
]), ['locations' => [$location->id]]),
$shiftCalendarRenderer->render(),
]);
$selected_tab = 0;
$request = request();
if ($request->has('shifts_filter_day')) {
$selected_tab = count($tabs) - 1;
}
$link = button(url('/admin/locations'), icon('chevron-left'), 'btn-sm', '', __('general.back'));
return page_with_title(
(auth()->can('admin_locations') ? $link . ' ' : '') .
icon('pin-map-fill') . htmlspecialchars($location->name),
[
$assignNotice,
auth()->can('admin_locations') ? buttons([
button(
url('/admin/locations/edit/' . $location->id),
icon('pencil'),
'',
'',
__('form.edit')
),
]) : '',
$dect,
$description,
tabs($tabs, $selected_tab),
],
true
);
}
/**
*
* @param Location $location
* @return string
*/
function location_name_render(Location $location)
{
if (auth()->can('view_locations')) {
return '<a href="' . location_link($location) . '">'
. icon('pin-map-fill') . htmlspecialchars($location->name)
. '</a>';
}
return icon('pin-map-fill') . htmlspecialchars($location->name);
}

View File

@ -1,5 +1,6 @@
<?php
use Engelsystem\Helpers\DayOfEvent;
use Engelsystem\Models\News;
use Illuminate\Support\Collection;
@ -8,17 +9,19 @@ use Illuminate\Support\Collection;
*
* @param array $stats
* @param array[] $free_shifts
* @param News[]|Collection $important_news
* @param News[]|Collection $highlighted_news
* @return string
*/
function public_dashboard_view($stats, $free_shifts, $important_news)
function public_dashboard_view($stats, $free_shifts, $highlighted_news)
{
$needed_angels = '';
$news = '';
if ($important_news->isNotEmpty()) {
$first_news = $important_news->first();
if ($highlighted_news->isNotEmpty()) {
$first_news = $highlighted_news->first();
$news = div('alert alert-warning text-center', [
'<a href="' . url('/news/' . $first_news->id) . '"><strong>' . $first_news->title . '</strong></a>',
'<a href="' . url('/news/' . $first_news->id) . '">'
. '<strong>' . htmlspecialchars($first_news->title) . '</strong>'
. '</a>',
]);
}
@ -43,17 +46,25 @@ function public_dashboard_view($stats, $free_shifts, $important_news)
]);
}
$stats = [
stats(__('Angels needed in the next 3 hrs'), $stats['needed-3-hours']),
stats(__('Angels needed for nightshifts'), $stats['needed-night']),
stats(__('Angels currently working'), $stats['angels-working'], 'default'),
stats(__('Hours to be worked'), $stats['hours-to-work'], 'default'),
];
$dayOfEvent = DayOfEvent::get();
if (config('enable_show_day_of_event') && $dayOfEvent !== null) {
$stats[] = stats(__('dashboard.day'), $dayOfEvent, 'default');
}
$isFiltered = request()->get('filtered');
$filter = collect(session()->get('shifts-filter'))->only(['rooms', 'types'])->toArray();
$filter = collect(session()->get('shifts-filter'))->only(['locations', 'types'])->toArray();
return page([
div('wrapper', [
div('public-dashboard', [
div('first row', [
stats(__('Angels needed in the next 3 hrs'), $stats['needed-3-hours']),
stats(__('Angels needed for nightshifts'), $stats['needed-night']),
stats(__('Angels currently working'), $stats['angels-working'], 'default'),
stats(__('Hours to be worked'), $stats['hours-to-work'], 'default'),
], 'statistics'),
div('first row', $stats, 'statistics'),
$news,
$needed_angels,
], 'public-dashboard'),
@ -84,17 +95,17 @@ function public_dashboard_shift_render($shift)
$panel_body = icon('clock-history') . $shift['start'] . ' - ' . $shift['end'];
$panel_body .= ' (' . $shift['duration'] . '&nbsp;h)';
$panel_body .= '<br>' . icon('list-task') . $shift['shifttype_name'];
$panel_body .= '<br>' . icon('list-task') . htmlspecialchars($shift['shifttype_name']);
if (!empty($shift['title'])) {
$panel_body .= ' (' . $shift['title'] . ')';
$panel_body .= ' (' . htmlspecialchars($shift['title']) . ')';
}
$panel_body .= '<br>' . icon('pin-map-fill') . $shift['room_name'];
$panel_body .= '<br>' . icon('pin-map-fill') . htmlspecialchars($shift['location_name']);
foreach ($shift['needed_angels'] as $needed_angels) {
$panel_body .= '<br>' . icon('person')
. '<span class="text-' . $shift['style'] . '">'
. $needed_angels['need'] . ' &times; ' . $needed_angels['angeltype_name']
. $needed_angels['need'] . ' &times; ' . htmlspecialchars($needed_angels['angeltype_name'])
. '</span>';
}

View File

@ -1,86 +0,0 @@
<?php
use Engelsystem\Models\Room;
use Engelsystem\ShiftCalendarRenderer;
use Engelsystem\ShiftsFilterRenderer;
/**
*
* @param Room $room
* @param ShiftsFilterRenderer $shiftsFilterRenderer
* @param ShiftCalendarRenderer $shiftCalendarRenderer
* @return string
*/
function Room_view(Room $room, ShiftsFilterRenderer $shiftsFilterRenderer, ShiftCalendarRenderer $shiftCalendarRenderer)
{
$user = auth()->user();
$assignNotice = '';
if (config('signup_requires_arrival') && !$user->state->arrived) {
$assignNotice = info(render_user_arrived_hint(), true);
}
$description = '';
if ($room->description) {
$description = '<h3>' . __('Description') . '</h3>';
$parsedown = new Parsedown();
$description .= $parsedown->parse($room->description);
}
$dect = '';
if (config('enable_dect') && $room->dect) {
$dect = heading(__('Contact'), 3)
. description([__('DECT') => sprintf('<a href="tel:%s">%1$s</a>', $room->dect)]);
}
$tabs = [];
if ($room->map_url) {
$tabs[__('Map')] = sprintf(
'<div class="map">'
. '<iframe style="width: 100%%; min-height: 400px; border: 0 none;" src="%s"></iframe>'
. '</div>',
$room->map_url
);
}
$tabs[__('Shifts')] = div('first', [
$shiftsFilterRenderer->render(page_link_to('rooms', [
'action' => 'view',
'room_id' => $room->id,
]), ['rooms' => [$room->id]]),
$shiftCalendarRenderer->render(),
]);
$selected_tab = 0;
$request = request();
if ($request->has('shifts_filter_day')) {
$selected_tab = count($tabs) - 1;
}
return page_with_title(icon('pin-map-fill') . $room->name, [
$assignNotice,
auth()->can('admin_rooms') ? buttons([
button(
page_link_to('admin/rooms/edit/' . $room->id),
icon('pencil') . __('edit')
),
]) : '',
$dect,
$description,
tabs($tabs, $selected_tab),
], true);
}
/**
*
* @param Room $room
* @return string
*/
function Room_name_render(Room $room)
{
if (auth()->can('view_rooms')) {
return '<a href="' . room_link($room) . '">' . icon('pin-map-fill') . $room->name . '</a>';
}
return icon('pin-map-fill') . $room->name;
}

View File

@ -2,6 +2,7 @@
namespace Engelsystem;
use Engelsystem\Helpers\Carbon;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Illuminate\Support\Collection;
@ -61,28 +62,28 @@ class ShiftCalendarRenderer
}
/**
* Assigns the shifts to different lanes per room if they collide
* Assigns the shifts to different lanes per location if they collide
*
* @param Shift[] $shifts The shifts to assign
* @return array Returns an array that assigns a room_id to an array of ShiftCalendarLane containing the shifts
* @return array Returns an array that assigns a location_id to an array of ShiftCalendarLane containing the shifts
*/
private function assignShiftsToLanes($shifts)
{
// array that assigns a room id to a list of lanes (per room)
// array that assigns a location id to a list of lanes (per location)
$lanes = [];
foreach ($shifts as $shift) {
$room = $shift->room;
$header = Room_name_render($room);
if (!isset($lanes[$room->id])) {
// initialize room with one lane
$lanes[$room->id] = [
$location = $shift->location;
$header = location_name_render($location);
if (!isset($lanes[$location->id])) {
// initialize location with one lane
$lanes[$location->id] = [
new ShiftCalendarLane($header),
];
}
// Try to add the shift to the existing lanes for this room
// Try to add the shift to the existing lanes for this location
$shift_added = false;
foreach ($lanes[$room->id] as $lane) {
foreach ($lanes[$location->id] as $lane) {
/** @var ShiftCalendarLane $lane */
if ($lane->shiftFits($shift)) {
$lane->addShift($shift);
@ -90,11 +91,11 @@ class ShiftCalendarRenderer
break;
}
}
// If all lanes for this room are busy, create a new lane and add shift to it
// If all lanes for this location are busy, create a new lane and add shift to it
if (!$shift_added) {
$newLane = new ShiftCalendarLane($header);
$newLane->addShift($shift);
$lanes[$room->id][] = $newLane;
$lanes[$location->id][] = $newLane;
}
}
@ -153,8 +154,8 @@ class ShiftCalendarRenderer
private function renderShiftLanes()
{
$html = '';
foreach ($this->lanes as $room_lanes) {
foreach ($room_lanes as $lane) {
foreach ($this->lanes as $location_lanes) {
foreach ($location_lanes as $lane) {
$html .= $this->renderLane($lane);
}
}
@ -212,20 +213,21 @@ class ShiftCalendarRenderer
*/
private function renderTick($time, $label = false)
{
$time = Carbon::createFromTimestamp($time);
$class = $label ? 'tick bg-' . theme_type() : 'tick ';
if ($time % (24 * 60 * 60) == 23 * 60 * 60) {
if ($time->isStartOfDay()) {
if (!$label) {
return div($class . ' day');
}
return div($class . ' day', [
date(__('m-d'), $time) . '<br>' . date(__('H:i'), $time),
$time->format(__('m-d')) . '<br>' . $time->format(__('H:i')),
]);
} elseif ($time % (60 * 60) == 0) {
} elseif ($time->isStartOfHour()) {
if (!$label) {
return div($class . ' hour');
}
return div($class . ' hour', [
date(__('m-d'), $time) . '<br>' . date(__('H:i'), $time),
$time->format(__('m-d')) . '<br>' . $time->format(__('H:i')),
]);
}
return div($class);
@ -242,7 +244,7 @@ class ShiftCalendarRenderer
$time_slot = [
div('header ' . $bg, [
__('Time'),
__('log.time'),
]),
];
for ($block = 0; $block < $this->getBlocksPerSlot(); $block++) {
@ -312,7 +314,7 @@ class ShiftCalendarRenderer
badge(__('Help needed'), 'danger'),
badge(__('Other angeltype needed / collides with my shifts'), 'warning'),
badge(__('Shift is full'), 'success'),
badge(__('Shift running/ended or you have not arrived'), 'secondary'),
badge(__('Shift is running/ended or you have not arrived'), 'secondary'),
]);
}
}

View File

@ -29,7 +29,7 @@ class ShiftCalendarShiftRenderer
{
$info_text = '';
if ($shift->title != '') {
$info_text = icon('info-circle') . $shift->title . '<br>';
$info_text = icon('info-circle') . htmlspecialchars($shift->title) . '<br>';
}
list($shift_signup_state, $shifts_row) = $this->renderShiftNeededAngeltypes(
$shift,
@ -43,8 +43,6 @@ class ShiftCalendarShiftRenderer
$blocks = ceil(($shift->end->timestamp - $shift->start->timestamp) / ShiftCalendarRenderer::SECONDS_PER_ROW);
$blocks = max(1, $blocks);
$room = $shift->room;
return [
$blocks,
div(
@ -57,7 +55,7 @@ class ShiftCalendarShiftRenderer
$this->renderShiftHead($shift, $class, $shift_signup_state->getFreeEntries()),
div('card-body ' . $this->classBg(), [
$info_text,
Room_name_render($room),
location_name_render($shift->location),
]),
$shifts_row,
]
@ -192,14 +190,14 @@ class ShiftCalendarShiftRenderer
// No link and add a text hint, when the shift ended
ShiftSignupStatus::NOT_ARRIVED => $inner_text . ' (' . __('please arrive for signup') . ')',
ShiftSignupStatus::NOT_YET => $inner_text . ' (' . __('not yet') . ')',
ShiftSignupStatus::ANGELTYPE => $angeltype->restricted
// User has to be confirmed on the angeltype first
ShiftSignupStatus::ANGELTYPE => $angeltype->restricted || !$angeltype->shift_self_signup
// User has to be confirmed on the angeltype first or can't sign up by themselves
? $inner_text . icon('mortarboard-fill')
// Add link to join the angeltype first
: $inner_text . '<br />'
. button(
page_link_to('user_angeltypes', ['action' => 'add', 'angeltype_id' => $angeltype->id]),
sprintf(__('Become %s'), $angeltype->name),
url('/user-angeltypes', ['action' => 'add', 'angeltype_id' => $angeltype->id]),
sprintf(__('Become %s'), htmlspecialchars($angeltype->name)),
'btn-sm'
),
// Shift collides or user is already signed up: No signup allowed
@ -249,20 +247,24 @@ class ShiftCalendarShiftRenderer
if (auth()->can('admin_shifts')) {
$header_buttons = '<div class="ms-auto d-print-none">' . table_buttons([
button(
page_link_to('user_shifts', ['edit_shift' => $shift->id]),
url('/user-shifts', ['edit_shift' => $shift->id]),
icon('pencil'),
'btn-' . $class . ' btn-sm border-light text-white'
'btn-' . $class . ' btn-sm border-light text-white',
'',
__('form.edit')
),
button(
page_link_to('user_shifts', ['delete_shift' => $shift->id]),
url('/user-shifts', ['delete_shift' => $shift->id]),
icon('trash'),
'btn-' . $class . ' btn-sm border-light text-white'
'btn-' . $class . ' btn-sm border-light text-white',
'',
__('form.delete')
),
]) . '</div>';
}
$shift_heading = $shift->start->format('H:i') . ' &dash; '
. $shift->end->format('H:i') . ' &mdash; '
. $shift->shiftType->name;
. htmlspecialchars($shift->shiftType->name);
if ($needed_angeltypes_count > 0) {
$shift_heading = '<span class="badge bg-light text-danger me-1">' . $needed_angeltypes_count . '</span> ' . $shift_heading;

Some files were not shown because too many files have changed in this diff Show More