Compare commits
131 Commits
197d0d724a
...
3fb5120142
Author | SHA1 | Date |
---|---|---|
Xu | 3fb5120142 | |
Xu | 833af4c62e | |
Xu | b380d7e68e | |
Xu | 6f8dad070c | |
Xu | 40bd61fd32 | |
Xu | 300786e5d0 | |
Xu | 13ded8de49 | |
Xu | c82e3183d6 | |
Xu | f1f5cd7c01 | |
Xu | b2951b7337 | |
Xu | 4e2e929c7e | |
Xu | 4a0f5c2e78 | |
Xu | 0fb09280b3 | |
Xu | 3972998ba0 | |
Igor Scheller | e514685444 | |
Igor Scheller | 87f7a74f27 | |
Igor Scheller | a214e0ff8f | |
Xu | d18e203560 | |
Xu | 0f2c7c5394 | |
Xu | 3fd8267f12 | |
Xu | d0388e8344 | |
Igor Scheller | 8f6bd547d3 | |
Igor Scheller | f397d809de | |
Igor Scheller | dd096b0f46 | |
Igor Scheller | cb82ad9c74 | |
Daniel Poelzleithner | 0155a33beb | |
Igor Scheller | ab967c6f9c | |
Igor Scheller | 87b50507f3 | |
Igor Scheller | 905d91d6ed | |
Xu | 9bf9bd2823 | |
Xu | d7d99900f8 | |
Xu | 7e06923ed0 | |
Xu | e6251256b3 | |
Xu | 6e76843db4 | |
Igor Scheller | fcf23d3824 | |
Igor Scheller | c82e902360 | |
Igor Scheller | fc9b4d6da4 | |
Igor Scheller | 63c70c0ec2 | |
Igor Scheller | 087d1cf31e | |
Igor Scheller | f9059161ec | |
Xu | 541789ae27 | |
Xu | 865ffe5e5a | |
Xu | 49cc935ceb | |
Igor Scheller | 9639a0fe3f | |
Igor Scheller | 8c24b78333 | |
Igor Scheller | 1217de096a | |
Igor Scheller | ba908bf849 | |
Igor Scheller | 0642bff804 | |
Igor Scheller | aea88b3579 | |
Igor Scheller | 460b416ff1 | |
Igor Scheller | 8dda9a0dc3 | |
Igor Scheller | 4ad8385386 | |
Igor Scheller | f56e9c534c | |
Xu | c0088d6601 | |
Xu | f3a12ebda8 | |
Xu | 100d62134f | |
Xu | 873803eb2d | |
Jens Brandt | 400edd9a19 | |
Xu | a94aa36fa4 | |
Xu | 9a07a7afb3 | |
Xu | ef3f58e999 | |
Xu | e18cddae86 | |
Xu | 5b8b59008a | |
Xu | ec7fb0615c | |
Xu | 759a4f9a14 | |
Igor Scheller | d8f8a4f67d | |
Igor Scheller | b63eb44b39 | |
Igor Scheller | e3e0fb33a2 | |
Igor Scheller | 790a04dc14 | |
Igor Scheller | aef53a306b | |
Igor Scheller | 4fa5db8a42 | |
Igor Scheller | b10264d6ef | |
Igor Scheller | ff1edaad10 | |
Lotte Steenbrink | 5e505cb8d2 | |
Xu | cf570502f4 | |
Xu | e7ff3b657a | |
Xu | 7e26e20608 | |
Xu | 67b24032a0 | |
Xu | 76c37a5f18 | |
weeman | 496d75b9ef | |
weeman | c03ccf94c7 | |
weeman | e47da9b8dd | |
Xu | 0476083e77 | |
xuwhite | 89d8a070d9 | |
Lotte Steenbrink | f387bc655d | |
Xu | 276b1aa976 | |
Lotte Steenbrink | 50b1abe0d1 | |
Xu | b20124e57e | |
Igor Scheller | 4d1502d092 | |
Igor Scheller | 00f039dbaa | |
Igor Scheller | dbbf30e233 | |
Igor Scheller | 3f03d6b1d8 | |
Igor Scheller | 8c64447273 | |
Igor Scheller | 3432829c91 | |
Igor Scheller | 6e4c9b2405 | |
Xu | 7637e0c66e | |
Xu | 8cff7aa205 | |
Xu | 0239bf1988 | |
Xu | 1798ccda83 | |
Xu | 7d5837c5f1 | |
msquare | 8603d47fe0 | |
Xu | 4a0c0994f0 | |
Xu | ffa531f311 | |
xuwhite | 89d68a56e7 | |
Xu | 9dac2a53db | |
Igor Scheller | 33209ea70b | |
Igor Scheller | 05725cd58c | |
Igor Scheller | 5fccc7e421 | |
Igor Scheller | b76144b23d | |
Xu | 2883a66f49 | |
weeman | d6412605f2 | |
Xu | 57373c846a | |
Igor Scheller | 1d3509ba3c | |
Igor Scheller | 57862ba722 | |
Igor Scheller | b229d697a3 | |
Igor Scheller | 3c0cbe55b6 | |
Igor Scheller | 8140ebd1cc | |
Igor Scheller | b62d4b4dce | |
Igor Scheller | d803591a91 | |
Igor Scheller | 6d4f059b3a | |
Igor Scheller | f3e1192695 | |
Igor Scheller | 8833506e04 | |
Igor Scheller | cc160e3e20 | |
Igor Scheller | bcfcb95786 | |
Igor Scheller | f7b0ee9ebb | |
Igor Scheller | 0f794be25e | |
Igor Scheller | c5ae5d4aa0 | |
Leandra Eberle | 64fef42087 | |
msquare | ea9aa9ef40 | |
Igor Scheller | caa699ff05 | |
Xu | 99dd081651 |
|
@ -27,6 +27,8 @@ max_line_length = unset
|
|||
indent_size = 2
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
max_line_length = unset
|
||||
quote_type = single
|
||||
|
||||
[{LICENSE,db/*.sql}]
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
{
|
||||
"parser": "@babel/eslint-parser",
|
||||
"extends": [ "plugin:editorconfig/all" ],
|
||||
"extends": ["plugin:editorconfig/all", "prettier"],
|
||||
"plugins": ["editorconfig"],
|
||||
"rules": {
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-template": "error",
|
||||
"no-var": "error",
|
||||
"quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
]
|
||||
"no-var": "error"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,14 @@ generate-version:
|
|||
before_script:
|
||||
- apk add -q git
|
||||
script:
|
||||
- VERSION="$(git describe --abbrev=0 --tags)-${CI_COMMIT_REF_NAME}+${CI_PIPELINE_ID}.${CI_COMMIT_SHORT_SHA}"
|
||||
- >
|
||||
VERSION="$(\
|
||||
git describe --exact-match --tags HEAD 2> /dev/null\
|
||||
|| (\
|
||||
(git describe --abbrev=0 --tags | tr -d '\n')\
|
||||
&& echo "-${CI_COMMIT_REF_NAME}+${CI_PIPELINE_ID}.${CI_COMMIT_SHORT_SHA}"\
|
||||
)\
|
||||
)"
|
||||
- echo "${VERSION}"
|
||||
- echo -n "${VERSION}" > storage/app/VERSION
|
||||
|
||||
|
@ -232,6 +239,7 @@ test:
|
|||
--coverage-text --coverage-html "${HOMEDIR}/coverage/"
|
||||
--log-junit "${HOMEDIR}/unittests.xml"
|
||||
after_script:
|
||||
- sed -i 's~/var/www/~~' unittests.xml
|
||||
- '"${DOCROOT}/bin/migrate" down'
|
||||
|
||||
dump-database:
|
||||
|
@ -252,6 +260,9 @@ dump-database:
|
|||
- cd "${DOCROOT}"
|
||||
- ./bin/migrate
|
||||
script:
|
||||
- >-
|
||||
mysql -h "${MYSQL_HOST}" -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" "${MYSQL_DATABASE}"
|
||||
-e 'UPDATE users SET api_key="" WHERE name="admin"'
|
||||
- >-
|
||||
mysqldump -h "${MYSQL_HOST}" -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" "${MYSQL_DATABASE}"
|
||||
> "${HOMEDIR}/initial-install.sql"
|
||||
|
@ -441,7 +452,8 @@ deploy:
|
|||
GIT_STRATEGY: none
|
||||
when: manual
|
||||
script:
|
||||
- kubectl delete all,ingress,pvc -l app=$CI_PROJECT_PATH_SLUG -l environment=$CI_ENVIRONMENT_SLUG
|
||||
- TARGETS=all,ingress,pvc,certificate
|
||||
- kubectl -n "${KUBE_NAMESPACE}" delete $TARGETS -l app=$CI_PROJECT_PATH_SLUG -l environment=$CI_ENVIRONMENT_SLUG
|
||||
|
||||
deploy-k8s-review:
|
||||
<<: *deploy_k8s
|
||||
|
|
|
@ -88,6 +88,11 @@ docker compose exec es_workspace yarn build:watch
|
|||
docker compose exec -e THEMES=0,1 es_workspace yarn build:watch
|
||||
```
|
||||
|
||||
It might also be useful to have an interactive database interface for which a phpMyAdmin instance can be startet at [http://localhost:8888](http://localhost:8888).
|
||||
```bash
|
||||
docker compose --profile dev up
|
||||
```
|
||||
|
||||
## Localhost
|
||||
You can find your local Engelsystem on [http://localhost:5080](http://localhost:5080).
|
||||
|
||||
|
|
15
README.md
15
README.md
|
@ -28,6 +28,8 @@ The Engelsystem may be installed manually or by using the provided [docker setup
|
|||
* MySQL-Server >= 5.7.8 or MariaDB-Server >= 10.2.2
|
||||
* Webserver, i.e. lighttpd, nginx, or Apache
|
||||
|
||||
From previous experience, 2 cores and 2GB ram are roughly enough for up to 1000 Angels (~700 arrived + 500 arrived but not working) during an event.
|
||||
|
||||
### Download
|
||||
* Go to the [Releases](https://github.com/engelsystem/engelsystem/releases) page and download the latest stable release file.
|
||||
* Extract the files to your webroot and continue with the directions for configurations and setup.
|
||||
|
@ -40,7 +42,14 @@ 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 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 disable/remove values from the following lists, set the value of the entry to `null`:
|
||||
* `themes`
|
||||
* `tshirt_sizes`
|
||||
* `headers`
|
||||
* `header_items`
|
||||
* `footer_items`
|
||||
* `locales`
|
||||
* `contact_options`
|
||||
* 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.
|
||||
|
||||
|
@ -70,8 +79,8 @@ cd docker
|
|||
docker compose up -d
|
||||
```
|
||||
|
||||
#### Migrate
|
||||
Import database changes to migrate it to the newest version
|
||||
#### Set Up / Migrate Database
|
||||
Create the Database Schema (on a fresh install) or import database changes to migrate it to the newest version
|
||||
```bash
|
||||
cd docker
|
||||
docker compose exec es_server bin/migrate
|
||||
|
|
|
@ -35,14 +35,14 @@
|
|||
"ext-pdo": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-xml": "*",
|
||||
"doctrine/dbal": "^3.6",
|
||||
"doctrine/dbal": "^3.7",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"gettext/gettext": "^5.7",
|
||||
"gettext/translator": "^1.1",
|
||||
"gettext/translator": "^1.2",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"illuminate/container": "^10.23",
|
||||
"illuminate/database": "^10.23",
|
||||
"illuminate/support": "^10.23",
|
||||
"illuminate/container": "^10.38",
|
||||
"illuminate/database": "^10.38",
|
||||
"illuminate/support": "^10.38",
|
||||
"league/oauth2-client": "^2.7",
|
||||
"league/openapi-psr7-validator": "^0.21",
|
||||
"nikic/fast-route": "^1.3",
|
||||
|
@ -51,13 +51,13 @@
|
|||
"psr/http-message": "^1.1",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"psr/log": "^3.0",
|
||||
"rcrowe/twigbridge": "^0.14.0",
|
||||
"rcrowe/twigbridge": "^0.14.1",
|
||||
"respect/validation": "^1.1",
|
||||
"symfony/http-foundation": "^6.3",
|
||||
"symfony/mailer": "^6.3",
|
||||
"symfony/http-foundation": "^6.4",
|
||||
"symfony/mailer": "^6.4",
|
||||
"symfony/psr-http-message-bridge": "^2.3",
|
||||
"twig/twig": "^3.7",
|
||||
"vlucas/phpdotenv": "^5.5"
|
||||
"twig/twig": "^3.8",
|
||||
"vlucas/phpdotenv": "^5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"dms/phpunit-arraysubset-asserts": "^0.5",
|
||||
|
@ -66,9 +66,9 @@
|
|||
"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.3"
|
||||
"slevomat/coding-standard": "^8.14",
|
||||
"squizlabs/php_codesniffer": "^3.8",
|
||||
"symfony/var-dumper": "^6.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -80,11 +80,11 @@ return [
|
|||
|
||||
'oauth2.login' => \Engelsystem\Events\Listener\OAuth2::class . '@login',
|
||||
|
||||
'shift.entry.deleting' => [
|
||||
\Engelsystem\Events\Listener\Shift::class . '@deletedEntryCreateWorklog',
|
||||
\Engelsystem\Events\Listener\Shift::class . '@deletedEntrySendEmail',
|
||||
'shift.deleting' => [
|
||||
\Engelsystem\Events\Listener\Shifts::class . '@deletingCreateWorklogs',
|
||||
\Engelsystem\Events\Listener\Shifts::class . '@deletingSendEmails',
|
||||
],
|
||||
|
||||
'shift.updating' => \Engelsystem\Events\Listener\Shift::class . '@updatedShiftSendEmail',
|
||||
'shift.updating' => \Engelsystem\Events\Listener\Shifts::class . '@updatedSendEmail',
|
||||
],
|
||||
];
|
||||
|
|
|
@ -26,7 +26,7 @@ return [
|
|||
'environment' => env('ENVIRONMENT', 'production'),
|
||||
|
||||
// Application URL and base path to use instead of the auto-detected one
|
||||
'url' => env('APP_URL', null),
|
||||
'url' => env('APP_URL'),
|
||||
|
||||
// Header links
|
||||
// Available link placeholders: %lang%
|
||||
|
@ -53,8 +53,15 @@ return [
|
|||
'Contact' => env('CONTACT_EMAIL', 'mailto:ticket@c3heaven.de'),
|
||||
],
|
||||
|
||||
// Other ways to ask the heaven
|
||||
// Multiple contact options / links are possible, analogue to footer_items
|
||||
'contact_options' => [
|
||||
// E-mail address
|
||||
'general.email' => env('CONTACT_EMAIL', 'mailto:ticket@c3heaven.de'),
|
||||
],
|
||||
|
||||
// Text displayed on the FAQ page, rendered as markdown
|
||||
'faq_text' => env('FAQ_TEXT', null),
|
||||
'faq_text' => env('FAQ_TEXT'),
|
||||
|
||||
// Link to documentation/help
|
||||
'documentation_url' => env('DOCUMENTATION_URL', 'https://engelsystem.de/doc/'),
|
||||
|
@ -72,17 +79,20 @@ return [
|
|||
'host' => env('MAIL_HOST', 'localhost'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
// If tls transport encryption should be used
|
||||
'tls' => env('MAIL_TLS', null),
|
||||
'tls' => env('MAIL_TLS'),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'sendmail' => env('MAIL_SENDMAIL', '/usr/sbin/sendmail -bs'),
|
||||
],
|
||||
|
||||
# Your privacy@ contact address
|
||||
'privacy_email' => env('PRIVACY_EMAIL', null),
|
||||
// Your privacy@ contact address
|
||||
'privacy_email' => env('PRIVACY_EMAIL'),
|
||||
|
||||
// Show opt in to save some personal data after the event on user profile and registration pages
|
||||
'enable_email_goodie' => (bool) env('ENABLE_EMAIL_GOODIE', true),
|
||||
|
||||
// Initial admin password
|
||||
'setup_admin_password' => env('SETUP_ADMIN_PASSWORD', null),
|
||||
'setup_admin_password' => env('SETUP_ADMIN_PASSWORD'),
|
||||
|
||||
'oauth' => [
|
||||
// '[name]' => [config]
|
||||
|
@ -144,6 +154,11 @@ return [
|
|||
// Supported themes
|
||||
// To disable a theme in the config.php, you can set its value to null
|
||||
'themes' => [
|
||||
17 => [
|
||||
'name' => 'Engelsystem 37c3 (2023)',
|
||||
'type' => 'dark',
|
||||
'navbar_classes' => 'navbar-dark',
|
||||
],
|
||||
16 => [
|
||||
'name' => 'Engelsystem cccamp23 (2023)',
|
||||
'type' => 'dark',
|
||||
|
@ -241,6 +256,9 @@ return [
|
|||
// Users are able to sign up
|
||||
'registration_enabled' => (bool) env('REGISTRATION_ENABLED', true),
|
||||
|
||||
// URL to external registration page, used on login page
|
||||
'external_registration_url' => env('EXTERNAL_REGISTRATION_URL'),
|
||||
|
||||
// Required user fields
|
||||
'required_user_fields' => [
|
||||
'pronoun' => (bool) env('PRONOUN_REQUIRED', false),
|
||||
|
@ -257,6 +275,9 @@ return [
|
|||
// Whether newly-registered user should automatically be marked as arrived
|
||||
'autoarrive' => (bool) env('ANGEL_AUTOARRIVE', false),
|
||||
|
||||
// Supporters of an angeltype can promote other angels of the angeltype to supporter
|
||||
'supporters_can_promote' => (bool) env('SUPPORTERS_CAN_PROMOTE', false),
|
||||
|
||||
// Only allow shift signup this number of hours in advance
|
||||
// Setting this to 0 disables the feature
|
||||
'signup_advance_hours' => env('SIGNUP_ADVANCE_HOURS', 0),
|
||||
|
@ -282,9 +303,8 @@ return [
|
|||
// The minimum length for passwords
|
||||
'min_password_length' => env('PASSWORD_MINIMUM_LENGTH', 8),
|
||||
|
||||
// Whether the Password field should be enabled on registration.
|
||||
// This is useful when using oauth, disabling it also disables normal
|
||||
// registration without oauth.
|
||||
// Whether the login and registration via password should be enabled (login will be hidden)
|
||||
// This is useful when using oauth, disabling it also disables normal registration without oauth
|
||||
'enable_password' => (bool) env('ENABLE_PASSWORD', true),
|
||||
|
||||
// Whether the DECT field should be enabled
|
||||
|
@ -310,6 +330,9 @@ return [
|
|||
// Enables the planned arrival/leave date
|
||||
'enable_planned_arrival' => (bool) env('ENABLE_PLANNED_ARRIVAL', true),
|
||||
|
||||
// Whether force active should be enabled
|
||||
'enable_force_active' => (bool) env('ENABLE_FORCE_ACTIVE', true),
|
||||
|
||||
// Resembles the Goodie Type. There are three options:
|
||||
// 'none' => no goodie at all
|
||||
// 'goodie' => a goodie which has no sizing options
|
||||
|
@ -328,11 +351,12 @@ return [
|
|||
// Local timezone
|
||||
'timezone' => env('TIMEZONE', 'Europe/Berlin'),
|
||||
|
||||
// Multiply 'night shifts' and freeloaded shifts (start or end between 2 and 6 exclusive) by 2
|
||||
// Multiply 'night shifts' and freeloaded shifts (start or end between 2 and 8 exclusive) by 2 in goodie score
|
||||
// Goodies must be enabled to use this feature
|
||||
'night_shifts' => [
|
||||
'enabled' => (bool) env('NIGHT_SHIFTS', true), // Disable to weigh every shift the same
|
||||
'start' => env('NIGHT_SHIFTS_START', 2),
|
||||
'end' => env('NIGHT_SHIFTS_END', 6),
|
||||
'start' => env('NIGHT_SHIFTS_START', 2), // Starting from hour
|
||||
'end' => env('NIGHT_SHIFTS_END', 8), // Ends at (without including) hour
|
||||
'multiplier' => env('NIGHT_SHIFTS_MULTIPLIER', 2),
|
||||
],
|
||||
|
||||
|
@ -342,15 +366,17 @@ return [
|
|||
'shifts_per_voucher' => env('SHIFTS_PER_VOUCHER', 0),
|
||||
'hours_per_voucher' => env('HOURS_PER_VOUCHER', 2),
|
||||
// 'Y-m-d' formatted
|
||||
'voucher_start' => env('VOUCHER_START', null) ?: null,
|
||||
'voucher_start' => env('VOUCHER_START') ?: null,
|
||||
],
|
||||
|
||||
// Enables Driving License
|
||||
'driving_license_enabled' => (bool) env('DRIVING_LICENSE_ENABLED', true),
|
||||
|
||||
# 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),
|
||||
'ifsg_light_enabled' => 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
|
||||
|
@ -378,11 +404,14 @@ return [
|
|||
'4XL' => '4XLarge Straight-Cut',
|
||||
],
|
||||
|
||||
// T-shirt Size-Guide link
|
||||
'tshirt_link' => env('TSHIRT_LINK'),
|
||||
|
||||
// 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,
|
||||
'enable_show_day_of_event' => (bool) env('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,
|
||||
'event_has_day0' => (bool) env('EVENT_HAS_DAY0', true),
|
||||
|
||||
'metrics' => [
|
||||
// User work buckets in seconds
|
||||
|
@ -418,7 +447,10 @@ return [
|
|||
'X-Content-Type-Options' => 'nosniff',
|
||||
'X-Frame-Options' => 'sameorigin',
|
||||
'Referrer-Policy' => 'strict-origin-when-cross-origin',
|
||||
'Content-Security-Policy' => 'default-src \'self\' \'unsafe-inline\' \'unsafe-eval\'; img-src \'self\' data:;',
|
||||
'Content-Security-Policy' =>
|
||||
'default-src \'self\'; '
|
||||
. ' style-src \'self\' \'unsafe-inline\'; '
|
||||
. 'img-src \'self\' data:;',
|
||||
'X-XSS-Protection' => '1; mode=block',
|
||||
'Feature-Policy' => 'autoplay \'none\'',
|
||||
//'Strict-Transport-Security' => 'max-age=7776000',
|
||||
|
|
|
@ -51,6 +51,16 @@ $route->addGroup(
|
|||
}
|
||||
);
|
||||
|
||||
// User admin settings
|
||||
$route->addGroup(
|
||||
'/users/{user_id:\d+}',
|
||||
function (RouteCollector $route): void {
|
||||
$route->get('/certificates', 'Admin\\UserSettingsController@certificate');
|
||||
$route->post('/certificates/ifsg', 'Admin\\UserSettingsController@saveIfsgCertificate');
|
||||
$route->post('/certificates/driving', 'Admin\\UserSettingsController@saveDrivingLicense');
|
||||
}
|
||||
);
|
||||
|
||||
// Password recovery
|
||||
$route->addGroup(
|
||||
'/password/reset',
|
||||
|
@ -187,11 +197,11 @@ $route->addGroup(
|
|||
$route->addGroup(
|
||||
'/schedule',
|
||||
function (RouteCollector $route): void {
|
||||
$route->get('', 'Admin\\Schedule\\ImportSchedule@index');
|
||||
$route->get('/edit[/{schedule_id:\d+}]', 'Admin\\Schedule\\ImportSchedule@edit');
|
||||
$route->post('/edit[/{schedule_id:\d+}]', 'Admin\\Schedule\\ImportSchedule@save');
|
||||
$route->get('/load/{schedule_id:\d+}', 'Admin\\Schedule\\ImportSchedule@loadSchedule');
|
||||
$route->post('/import/{schedule_id:\d+}', 'Admin\\Schedule\\ImportSchedule@importSchedule');
|
||||
$route->get('', 'Admin\\ScheduleController@index');
|
||||
$route->get('/edit[/{schedule_id:\d+}]', 'Admin\\ScheduleController@edit');
|
||||
$route->post('/edit[/{schedule_id:\d+}]', 'Admin\\ScheduleController@save');
|
||||
$route->get('/load/{schedule_id:\d+}', 'Admin\\ScheduleController@loadSchedule');
|
||||
$route->post('/import/{schedule_id:\d+}', 'Admin\\ScheduleController@importSchedule');
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -242,12 +252,12 @@ $route->addGroup(
|
|||
$route->addGroup(
|
||||
'/user/{user_id:\d+}',
|
||||
function (RouteCollector $route): void {
|
||||
// Shirts
|
||||
// Goodies
|
||||
$route->addGroup(
|
||||
'/goodie',
|
||||
function (RouteCollector $route): void {
|
||||
$route->get('', 'Admin\\UserShirtController@editShirt');
|
||||
$route->post('', 'Admin\\UserShirtController@saveShirt');
|
||||
$route->get('', 'Admin\\UserGoodieController@editGoodie');
|
||||
$route->post('', 'Admin\\UserGoodieController@saveGoodie');
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -21,9 +21,17 @@ class LicenseFactory extends Factory
|
|||
$drive_12t = $drive_7_5t && $this->faker->boolean(.3);
|
||||
$drive_forklift = ($drive_car && $this->faker->boolean(.1))
|
||||
|| ($drive_12t && $this->faker->boolean(.7));
|
||||
$drive_confirmed = $this->faker->boolean(0.5) && (
|
||||
$drive_car
|
||||
|| $drive_3_5t
|
||||
|| $drive_7_5t
|
||||
|| $drive_12t
|
||||
|| $drive_forklift
|
||||
);
|
||||
|
||||
$ifsg_certificate = $this->faker->boolean(0.1);
|
||||
$ifsg_certificate_light = $this->faker->boolean(0.5) && !$ifsg_certificate;
|
||||
$ifsg_confirmed = $this->faker->boolean(0.5) && ($ifsg_certificate || $ifsg_certificate_light);
|
||||
|
||||
return [
|
||||
'user_id' => User::factory(),
|
||||
|
@ -33,8 +41,10 @@ class LicenseFactory extends Factory
|
|||
'drive_3_5t' => $drive_3_5t,
|
||||
'drive_7_5t' => $drive_7_5t,
|
||||
'drive_12t' => $drive_12t,
|
||||
'drive_confirmed' => $drive_confirmed,
|
||||
'ifsg_certificate' => $ifsg_certificate,
|
||||
'ifsg_certificate_light' => $ifsg_certificate_light,
|
||||
'ifsg_confirmed' => $ifsg_confirmed,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class SettingsFactory extends Factory
|
|||
'theme' => $this->faker->numberBetween(1, 20),
|
||||
'email_human' => $this->faker->boolean(),
|
||||
'email_messages' => $this->faker->boolean(),
|
||||
'email_goody' => $this->faker->boolean(),
|
||||
'email_goodie' => $this->faker->boolean(),
|
||||
'email_shiftinfo' => $this->faker->boolean(),
|
||||
'email_news' => $this->faker->boolean(),
|
||||
'mobile_show' => $this->faker->boolean(),
|
||||
|
|
|
@ -25,7 +25,7 @@ class StateFactory extends Factory
|
|||
'user_info' => $this->faker->optional(.1)->text(),
|
||||
'active' => $this->faker->boolean(.3),
|
||||
'force_active' => $this->faker->boolean(.1),
|
||||
'got_shirt' => $this->faker->boolean(),
|
||||
'got_goodie' => $this->faker->boolean(),
|
||||
'got_voucher' => $this->faker->numberBetween(0, 10),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class ChangeUsersContactDectFieldSize extends Migration
|
||||
{
|
||||
/** @var array */
|
||||
protected array $tables = [
|
||||
'AngelTypes' => 'contact_dect',
|
||||
'users_contact' => 'dect',
|
||||
|
|
|
@ -11,7 +11,6 @@ use stdClass;
|
|||
class CreateRoomsTable extends Migration
|
||||
{
|
||||
use ChangesReferences;
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
|
|
|
@ -10,8 +10,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddTimestampsToQuestions extends Migration
|
||||
{
|
||||
use ChangesReferences;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddEmailNewsToUsersSettings extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class OauthAddTokens extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class NewsAddIsPinned extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class OauthChangeTokensToText extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -12,8 +12,6 @@ use stdClass;
|
|||
|
||||
class CreateFirstUser extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
public function __construct(SchemaBuilder $schemaBuilder, protected Config $config)
|
||||
{
|
||||
parent::__construct($schemaBuilder);
|
||||
|
|
|
@ -11,8 +11,6 @@ use stdClass;
|
|||
|
||||
class SetAdminPassword extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
public function __construct(SchemaBuilder $schemaBuilder, protected Config $config)
|
||||
{
|
||||
parent::__construct($schemaBuilder);
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddShiftsDescription extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class UsersSettingsAddEmailGoody extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,9 +9,6 @@ use stdClass;
|
|||
|
||||
class FillPrivilegesAndGroupsRelatedTables extends Migration
|
||||
{
|
||||
use ChangesReferences;
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Inserts missing data into permissions & groups related tables
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddEmailMessagesToUsersSettings extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CleanupShortApiKeys extends Migration
|
||||
{
|
||||
|
@ -14,8 +15,14 @@ class CleanupShortApiKeys extends Migration
|
|||
public function up(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
foreach ($db->table('users')->get() as $user) {
|
||||
if (Str::length($user->api_key) > 42) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$db->table('users')
|
||||
->where($db->raw('LENGTH(api_key)'), '<=', 42)
|
||||
->update(['api_key' => '']);
|
||||
->where('id', $user->id)
|
||||
->update(['api_key' => bin2hex(random_bytes(32))]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddIfsgCerificatesToUsersLicenses extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddRequiresIfsgCerificateToAngeltypes extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AngeltypesRenameNoSelfSignupToShiftSelfSignup extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddHideOnShiftViewToAngeltypes extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,6 @@ use Illuminate\Database\Schema\Blueprint;
|
|||
|
||||
class AddUserInfoToUsersState extends Migration
|
||||
{
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateScheduleLocationsTable extends Migration
|
||||
{
|
||||
use ChangesReferences;
|
||||
use Reference;
|
||||
|
||||
/**
|
||||
* Creates the new table
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$connection = $this->schema->getConnection();
|
||||
|
||||
$this->schema->create('schedule_locations', function (Blueprint $table): void {
|
||||
$table->increments('id');
|
||||
$this->references($table, 'schedules');
|
||||
$this->references($table, 'locations');
|
||||
|
||||
$table->index(['schedule_id', 'location_id']);
|
||||
});
|
||||
|
||||
$scheduleLocations = $connection
|
||||
->table('schedule_shift')
|
||||
->select(['schedules.id AS schedule_id', 'locations.id AS location_id'])
|
||||
->leftJoin('schedules', 'schedules.id', 'schedule_shift.schedule_id')
|
||||
->leftJoin('shifts', 'shifts.id', 'schedule_shift.shift_id')
|
||||
->leftJoin('locations', 'locations.id', 'shifts.location_id')
|
||||
->groupBy(['schedules.id', 'locations.id'])
|
||||
->get();
|
||||
|
||||
foreach ($scheduleLocations as $scheduleLocation) {
|
||||
$connection->table('schedule_locations')
|
||||
->insert((array) $scheduleLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the table
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->schema->drop('schedule_locations');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?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 AddShifttypesEditPermissionAndShifttypesRequiresShico extends Migration
|
||||
{
|
||||
protected int $bureaucrat = 80;
|
||||
protected int $shiCo = 60;
|
||||
|
||||
protected int $shifttypes;
|
||||
|
||||
protected Connection $db;
|
||||
|
||||
public function __construct(SchemaBuilder $schema)
|
||||
{
|
||||
parent::__construct($schema);
|
||||
$this->db = $this->schema->getConnection();
|
||||
|
||||
$this->shifttypes = $this->db->table('privileges')
|
||||
->where('name', 'shifttypes')
|
||||
->get(['id'])
|
||||
->first()->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->insert([
|
||||
'name' => 'shifttypes.edit', 'description' => 'Edit shift types',
|
||||
]);
|
||||
|
||||
$editShifttypes = $db->table('privileges')
|
||||
->where('name', 'shifttypes.edit')
|
||||
->get(['id'])
|
||||
->first();
|
||||
|
||||
$this->movePermission($this->shifttypes, $this->bureaucrat, $this->shiCo);
|
||||
|
||||
$db->table('group_privileges')
|
||||
->insertOrIgnore([
|
||||
'group_id' => $this->bureaucrat, 'privilege_id' => $editShifttypes->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->where('name', 'shifttypes.edit')
|
||||
->delete();
|
||||
|
||||
$this->movePermission($this->shifttypes, $this->shiCo, $this->bureaucrat);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class AddIfsgConfirmedToUsersLicenses extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->schema->table('users_licenses', function (Blueprint $table): void {
|
||||
$table->boolean('ifsg_confirmed')->default(false)->after('ifsg_certificate');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->schema->table('users_licenses', function (Blueprint $table): void {
|
||||
$table->dropColumn('ifsg_confirmed');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
|
||||
class AddUserIfsgEditPermission extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->insert([
|
||||
'name' => 'user.ifsg.edit', 'description' => 'Edit IfSG Certificate',
|
||||
]);
|
||||
|
||||
$editIfsg = $db->table('privileges')
|
||||
->where('name', 'user.ifsg.edit')
|
||||
->get(['id'])
|
||||
->first();
|
||||
|
||||
$shico = 60;
|
||||
$team_coordinator = 65;
|
||||
$db->table('group_privileges')
|
||||
->insertOrIgnore([
|
||||
['group_id' => $shico, 'privilege_id' => $editIfsg->id],
|
||||
['group_id' => $team_coordinator, 'privilege_id' => $editIfsg->id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->where('name', 'user.ifsg.edit')
|
||||
->delete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class AddDriveConfirmedToUsersLicenses extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->schema->table('users_licenses', function (Blueprint $table): void {
|
||||
$table->boolean('drive_confirmed')->default(false)->after('drive_12t');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->schema->table('users_licenses', function (Blueprint $table): void {
|
||||
$table->dropColumn('drive_confirmed');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
|
||||
class AddUserDriveEditPermission extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->insert([
|
||||
'name' => 'user.drive.edit', 'description' => 'Edit Driving License',
|
||||
]);
|
||||
|
||||
$editDrive = $db->table('privileges')
|
||||
->where('name', 'user.drive.edit')
|
||||
->get(['id'])
|
||||
->first();
|
||||
|
||||
$shico = 60;
|
||||
$team_coordinator = 65;
|
||||
$db->table('group_privileges')
|
||||
->insertOrIgnore([
|
||||
['group_id' => $shico, 'privilege_id' => $editDrive->id],
|
||||
['group_id' => $team_coordinator, 'privilege_id' => $editDrive->id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->where('name', 'user.drive.edit')
|
||||
->delete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
|
||||
class AddUserFaEditPermission extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->insert([
|
||||
'name' => 'user.fa.edit', 'description' => 'Edit User Force Active State',
|
||||
]);
|
||||
|
||||
$editFa = $db->table('privileges')
|
||||
->where('name', 'user.fa.edit')
|
||||
->get(['id'])
|
||||
->first();
|
||||
|
||||
$bureaucrat = 80;
|
||||
$db->table('group_privileges')
|
||||
->insertOrIgnore([
|
||||
['group_id' => $bureaucrat, 'privilege_id' => $editFa->id],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')
|
||||
->where('name', 'user.fa.edit')
|
||||
->delete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class RenameGoodyToGoodie extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->schema->table('users_settings', function (Blueprint $table): void {
|
||||
$table->renameColumn('email_goody', 'email_goodie');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->schema->table('users_settings', function (Blueprint $table): void {
|
||||
$table->renameColumn('email_goodie', 'email_goody');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Migrations;
|
||||
|
||||
use Engelsystem\Database\Migration\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class RenameShirtToGoodie extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->schema->table('users_state', function (Blueprint $table): void {
|
||||
$table->renameColumn('got_shirt', 'got_goodie');
|
||||
});
|
||||
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')->where('name', 'admin_active')->update([
|
||||
'description' => 'Mark angels as active and if they got a goodie.',
|
||||
]);
|
||||
$db->table('privileges')->where('name', 'user.edit.shirt')->update([
|
||||
'name' => 'user.goodie.edit',
|
||||
'description' => 'Edit user goodies',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->schema->table('users_state', function (Blueprint $table): void {
|
||||
$table->renameColumn('got_goodie', 'got_shirt');
|
||||
});
|
||||
|
||||
$db = $this->schema->getConnection();
|
||||
$db->table('privileges')->where('name', 'admin_active')->update([
|
||||
'description' => 'Mark angels as active and if they got a t-shirt.',
|
||||
]);
|
||||
$db->table('privileges')->where('name', 'user.goodie.edit')->update([
|
||||
'name' => 'user.edit.shirt',
|
||||
'description' => 'Edit user shirts',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
<?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 RefactorPermissionsAndGroups extends Migration
|
||||
{
|
||||
protected int $developer = 90;
|
||||
protected int $bureaucrat = 80;
|
||||
protected int $shiCo = 60;
|
||||
protected int $newsAdmin = 85;
|
||||
protected int $teamCoordinator = 65;
|
||||
protected int $angel = 20;
|
||||
|
||||
protected int $active;
|
||||
protected int $driveEdit;
|
||||
protected int $eventConfig;
|
||||
protected int $goodieEdit;
|
||||
protected int $ifsgEdit;
|
||||
protected int $log;
|
||||
protected int $news;
|
||||
protected int $register;
|
||||
protected int $scheduleImport;
|
||||
protected int $shifts;
|
||||
protected int $user;
|
||||
protected int $userAngeltypes;
|
||||
protected int $userShifts;
|
||||
|
||||
protected string $shiftentry = 'shiftentry_edit_angeltype_supporter';
|
||||
protected string $language = 'admin_language';
|
||||
protected string $userEdit = 'user.edit';
|
||||
protected string $userNickEdit = 'user.nick.edit';
|
||||
protected string $shifttypes = 'shifttypes';
|
||||
protected string $shifttypesView = 'shifttypes.view';
|
||||
|
||||
protected Connection $db;
|
||||
|
||||
public function __construct(SchemaBuilder $schema)
|
||||
{
|
||||
parent::__construct($schema);
|
||||
$this->db = $this->schema->getConnection();
|
||||
|
||||
$this->active = $this->getPrivilegeId('admin_active');
|
||||
$this->driveEdit = $this->getPrivilegeId('user.drive.edit');
|
||||
$this->eventConfig = $this->getPrivilegeId('admin_event_config');
|
||||
$this->goodieEdit = $this->getPrivilegeId('user.goodie.edit');
|
||||
$this->ifsgEdit = $this->getPrivilegeId('user.ifsg.edit');
|
||||
$this->log = $this->getPrivilegeId('admin_log');
|
||||
$this->news = $this->getPrivilegeId('admin_news');
|
||||
$this->register = $this->getPrivilegeId('register');
|
||||
$this->scheduleImport = $this->getPrivilegeId('schedule.import');
|
||||
$this->shifts = $this->getPrivilegeId('admin_shifts');
|
||||
$this->user = $this->getPrivilegeId('admin_user');
|
||||
$this->userAngeltypes = $this->getPrivilegeId('admin_user_angeltypes');
|
||||
$this->userShifts = $this->getPrivilegeId('user_shifts_admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->deletePermission($this->shiftentry);
|
||||
$this->deletePermission($this->language);
|
||||
|
||||
$this->movePermission($this->active, $this->bureaucrat, $this->shiCo);
|
||||
$this->movePermission($this->userAngeltypes, $this->bureaucrat, $this->shiCo);
|
||||
$this->movePermission($this->eventConfig, $this->shiCo, $this->developer);
|
||||
$this->movePermission($this->goodieEdit, $this->bureaucrat, $this->shiCo);
|
||||
|
||||
$this->insertGroupPermission($this->log, $this->bureaucrat);
|
||||
|
||||
$this->deleteGroupPermission($this->news, $this->bureaucrat);
|
||||
$this->deleteGroupPermission($this->shifts, $this->bureaucrat);
|
||||
$this->deleteGroupPermission($this->user, $this->bureaucrat);
|
||||
$this->deleteGroupPermission($this->register, $this->bureaucrat);
|
||||
$this->deleteGroupPermission($this->scheduleImport, $this->developer);
|
||||
|
||||
$this->updatePermission($this->shifttypes, $this->shifttypesView, 'View shift types');
|
||||
$this->updatePermission($this->userEdit, $this->userNickEdit, 'Edit user nick');
|
||||
|
||||
$this->deleteGroup($this->newsAdmin);
|
||||
$this->deleteGroup($this->teamCoordinator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->insertPermission(
|
||||
$this->shiftentry,
|
||||
'If user with this privilege is angeltype supporter, he can put users in shifts for their angeltype',
|
||||
$this->angel
|
||||
);
|
||||
$this->insertPermission(
|
||||
$this->language,
|
||||
'Translate the system',
|
||||
$this->developer
|
||||
);
|
||||
|
||||
$this->movePermission($this->active, $this->shiCo, $this->bureaucrat);
|
||||
$this->movePermission($this->userAngeltypes, $this->shiCo, $this->bureaucrat);
|
||||
$this->movePermission($this->eventConfig, $this->developer, $this->shiCo);
|
||||
$this->movePermission($this->goodieEdit, $this->shiCo, $this->bureaucrat);
|
||||
|
||||
$this->deleteGroupPermission($this->log, $this->bureaucrat);
|
||||
|
||||
$this->insertGroupPermission($this->news, $this->bureaucrat);
|
||||
$this->insertGroupPermission($this->shifts, $this->bureaucrat);
|
||||
$this->insertGroupPermission($this->user, $this->bureaucrat);
|
||||
$this->insertGroupPermission($this->register, $this->bureaucrat);
|
||||
$this->insertGroupPermission($this->scheduleImport, $this->developer);
|
||||
|
||||
$this->updatePermission($this->shifttypesView, $this->shifttypes, 'Administrate shift types');
|
||||
$this->updatePermission($this->userNickEdit, $this->userEdit, 'Edit user');
|
||||
|
||||
$this->insertGroup($this->newsAdmin, 'News Admin', [$this->news]);
|
||||
$this->insertGroup($this->teamCoordinator, 'Team Coordinator', [
|
||||
$this->news,
|
||||
$this->userAngeltypes,
|
||||
$this->driveEdit,
|
||||
$this->ifsgEdit,
|
||||
$this->userShifts,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getPrivilegeId(string $privilege): int
|
||||
{
|
||||
return $this->db->table('privileges')
|
||||
->where('name', $privilege)
|
||||
->get(['id'])
|
||||
->first()->id;
|
||||
}
|
||||
|
||||
protected function deleteGroup(int $group): void
|
||||
{
|
||||
$this->db->table('groups')
|
||||
->where(['id' => $group])
|
||||
->delete();
|
||||
}
|
||||
|
||||
protected function insertGroup(int $id, string $name, array $privileges): void
|
||||
{
|
||||
$this->db->table('groups')
|
||||
->insertOrIgnore([
|
||||
'name' => $name,
|
||||
'id' => $id,
|
||||
]);
|
||||
foreach ($privileges as $privilege) {
|
||||
$this->insertGroupPermission($privilege, $id);
|
||||
}
|
||||
}
|
||||
|
||||
protected function deleteGroupPermission(int $privilege, int $group): void
|
||||
{
|
||||
$this->db->table('group_privileges')
|
||||
->where(['group_id' => $group, 'privilege_id' => $privilege])
|
||||
->delete();
|
||||
}
|
||||
|
||||
protected function insertGroupPermission(int $privilege, int $group): void
|
||||
{
|
||||
$this->db->table('group_privileges')
|
||||
->insertOrIgnore([
|
||||
['group_id' => $group, 'privilege_id' => $privilege],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function movePermission(int $privilege, int $oldGroup, int $newGroup): void
|
||||
{
|
||||
$this->insertGroupPermission($privilege, $newGroup);
|
||||
$this->deleteGroupPermission($privilege, $oldGroup);
|
||||
}
|
||||
|
||||
protected function insertPermission(string $name, string $description, int $group): void
|
||||
{
|
||||
$this->db->table('privileges')
|
||||
->insertOrIgnore([
|
||||
'name' => $name, 'description' => $description,
|
||||
]);
|
||||
$permission = $this->getPrivilegeId($name);
|
||||
$this->insertGroupPermission($permission, $group);
|
||||
}
|
||||
|
||||
protected function deletePermission(string $privilege): void
|
||||
{
|
||||
$this->db->table('privileges')
|
||||
->where(['name' => $privilege])
|
||||
->delete();
|
||||
}
|
||||
|
||||
protected function updatePermission(string $oldName, string $newName, string $description): void
|
||||
{
|
||||
$this->db->table('privileges')->where('name', $oldName)->update([
|
||||
'name' => $newName,
|
||||
'description' => $description,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?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 AddUsersArriveListPermission extends Migration
|
||||
{
|
||||
protected int $voucher = 35;
|
||||
|
||||
protected int $arrive;
|
||||
|
||||
protected Connection $db;
|
||||
|
||||
public function __construct(SchemaBuilder $schema)
|
||||
{
|
||||
parent::__construct($schema);
|
||||
$this->db = $this->schema->getConnection();
|
||||
|
||||
$this->arrive = $this->db->table('privileges')
|
||||
->where('name', 'admin_arrive')
|
||||
->get(['id'])
|
||||
->first()->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->db->table('privileges')
|
||||
->insert([
|
||||
'name' => 'users.arrive.list', 'description' => 'View arrive angels list',
|
||||
]);
|
||||
|
||||
$arriveList = $this->db->table('privileges')
|
||||
->where('name', 'users.arrive.list')
|
||||
->get(['id'])
|
||||
->first()->id;
|
||||
|
||||
// Goodie Manager, Shift Coordinator, Voucher Angel, Welcome Angel
|
||||
$groups = [50, 60, 35, 30];
|
||||
foreach ($groups as $group) {
|
||||
$this->db->table('group_privileges')
|
||||
->insertOrIgnore([
|
||||
['group_id' => $group, 'privilege_id' => $arriveList],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->db->table('group_privileges')
|
||||
->where(['group_id' => $this->voucher, 'privilege_id' => $this->arrive])
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->db->table('privileges')
|
||||
->where('name', 'users.arrive.list')
|
||||
->delete();
|
||||
|
||||
$this->db->table('group_privileges')
|
||||
->insertOrIgnore([
|
||||
['group_id' => $this->voucher, 'privilege_id' => $this->arrive],
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ ENV TRUSTED_PROXIES 10.0.0.0/8,::ffff:10.0.0.0/8,\
|
|||
# Engelsystem development workspace
|
||||
# Contains all tools required to build / manage the system
|
||||
FROM es_base AS es_workspace
|
||||
RUN echo 'memory_limit = 512M' > /usr/local/etc/php/conf.d/docker-php.ini
|
||||
RUN echo 'memory_limit = 1024M' > /usr/local/etc/php/conf.d/docker-php.ini
|
||||
RUN apk add --no-cache gettext git nodejs npm yarn
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
ENTRYPOINT php -r 'sleep(PHP_INT_MAX);'
|
||||
|
|
|
@ -20,10 +20,7 @@ services:
|
|||
APP_NAME: Engelsystem DEV
|
||||
env_file: deployment.env
|
||||
ports:
|
||||
- "5080:80"
|
||||
networks:
|
||||
- database
|
||||
- internet
|
||||
- "127.0.0.1:5080:80"
|
||||
depends_on:
|
||||
- es_database
|
||||
es_workspace:
|
||||
|
@ -44,11 +41,17 @@ services:
|
|||
ENVIRONMENT: development
|
||||
MAIL_DRIVER: log
|
||||
APP_NAME: Engelsystem DEV
|
||||
networks:
|
||||
- database
|
||||
- internet
|
||||
depends_on:
|
||||
- es_database
|
||||
es_phpmyadmin:
|
||||
image: phpmyadmin
|
||||
environment:
|
||||
PMA_HOST: es_database
|
||||
ports:
|
||||
- "127.0.0.1:8888:80"
|
||||
depends_on:
|
||||
- es_database
|
||||
profiles: [ dev ]
|
||||
es_database:
|
||||
image: mariadb:10.2
|
||||
environment:
|
||||
|
@ -59,12 +62,5 @@ services:
|
|||
MYSQL_INITDB_SKIP_TZINFO: "yes"
|
||||
volumes:
|
||||
- db:/var/lib/mysql
|
||||
networks:
|
||||
- database
|
||||
volumes:
|
||||
db: {}
|
||||
|
||||
networks:
|
||||
database:
|
||||
internal: true
|
||||
internet:
|
||||
|
|
|
@ -136,12 +136,16 @@ function angeltype_edit_controller()
|
|||
if ($valid) {
|
||||
$angeltype->save();
|
||||
|
||||
success('Angel type saved.');
|
||||
success(__('Angel type saved.'));
|
||||
engelsystem_log(
|
||||
'Saved angeltype: ' . $angeltype->name . ($angeltype->restricted ? ', restricted' : '')
|
||||
. ($angeltype->shift_self_signup ? ', shift_self_signup' : '')
|
||||
. ($angeltype->requires_driver_license ? ', requires driver license' : '') . ', '
|
||||
. ($angeltype->requires_ifsg_certificate ? ', requires ifsg certificate' : '') . ', '
|
||||
. (config('driving_license_enabled')
|
||||
? (($angeltype->requires_driver_license ? ', requires driver license' : '') . ', ')
|
||||
: '')
|
||||
. (config('ifsg_enabled')
|
||||
? (($angeltype->requires_ifsg_certificate ? ', requires ifsg certificate' : '') . ', ')
|
||||
: '')
|
||||
. $angeltype->contact_name . ', '
|
||||
. $angeltype->contact_dect . ', '
|
||||
. $angeltype->contact_email . ', '
|
||||
|
@ -175,7 +179,9 @@ function angeltype_controller()
|
|||
$angeltype = AngelType::findOrFail(request()->input('angeltype_id'));
|
||||
/** @var UserAngelType $user_angeltype */
|
||||
$user_angeltype = UserAngelType::whereUserId($user->id)->where('angel_type_id', $angeltype->id)->first();
|
||||
$members = $angeltype->userAngelTypes->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE);
|
||||
$members = $angeltype->userAngelTypes
|
||||
->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)
|
||||
->load(['state', 'personalData', 'contact']);
|
||||
$days = angeltype_controller_shiftsFilterDays($angeltype);
|
||||
$shiftsFilter = angeltype_controller_shiftsFilter($angeltype, $days);
|
||||
if (request()->input('showFilledShifts')) {
|
||||
|
@ -323,7 +329,7 @@ function angeltypes_list_controller()
|
|||
$actions[] = button(
|
||||
url('/user_angeltypes', ['action' => 'add', 'angeltype_id' => $angeltype->id]),
|
||||
icon('box-arrow-in-right') . ($admin_angeltypes ? '' : __('Join')),
|
||||
'btn-sm',
|
||||
'btn-sm' . ($admin_angeltypes ? ' btn-success' : ''),
|
||||
'',
|
||||
($admin_angeltypes ? __('Join') : '')
|
||||
);
|
||||
|
|
|
@ -210,7 +210,7 @@ function shift_entry_create_controller_user(Shift $shift, AngelType $angeltype):
|
|||
$request = request();
|
||||
|
||||
$signup_user = auth()->user();
|
||||
$needed_angeltype = (new AngelType())->forceFill(NeededAngeltype_by_Shift_and_Angeltype($shift, $angeltype));
|
||||
$needed_angeltype = (new AngelType())->forceFill(NeededAngeltype_by_Shift_and_Angeltype($shift, $angeltype) ?: []);
|
||||
$shift_entries = $shift->shiftEntries()
|
||||
->where('angel_type_id', $angeltype->id)
|
||||
->get();
|
||||
|
@ -307,9 +307,8 @@ function shift_entry_load()
|
|||
if (!$request->has('shift_entry_id') || !test_request_int('shift_entry_id')) {
|
||||
throw_redirect(url('/user-shifts'));
|
||||
}
|
||||
$shiftEntry = ShiftEntry::findOrFail($request->input('shift_entry_id'));
|
||||
|
||||
return $shiftEntry;
|
||||
return ShiftEntry::findOrFail($request->input('shift_entry_id'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||
use Engelsystem\Http\Exceptions\HttpNotFound;
|
||||
use Engelsystem\Http\Redirector;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Location;
|
||||
use Engelsystem\Models\Shifts\NeededAngelType;
|
||||
|
@ -8,6 +11,7 @@ use Engelsystem\Models\Shifts\Shift;
|
|||
use Engelsystem\Models\Shifts\ShiftType;
|
||||
use Engelsystem\Models\Shifts\ShiftSignupStatus;
|
||||
use Engelsystem\ShiftSignupState;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @param array|Shift $shift
|
||||
|
@ -23,15 +27,6 @@ function shift_link($shift)
|
|||
return url('/shifts', $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Shift $shift
|
||||
* @return string
|
||||
*/
|
||||
function shift_delete_link(Shift $shift)
|
||||
{
|
||||
return url('/user-shifts', ['delete_shift' => $shift->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Shift $shift
|
||||
* @return string
|
||||
|
@ -200,7 +195,7 @@ function shift_edit_controller()
|
|||
htmlspecialchars($angeltype_name),
|
||||
$needed_angel_types[$angeltype_id],
|
||||
[],
|
||||
ScheduleShift::whereShiftId($shift->id)->first() ? true : false,
|
||||
(bool) ScheduleShift::whereShiftId($shift->id)->first(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -231,42 +226,24 @@ function shift_edit_controller()
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function shift_delete_controller()
|
||||
function shift_delete_controller(): void
|
||||
{
|
||||
$request = request();
|
||||
|
||||
// Only accessible for admins / ShiCos with user_shifts_admin privileg
|
||||
if (!auth()->can('user_shifts_admin')) {
|
||||
throw_redirect(url('/user-shifts'));
|
||||
throw new HttpForbidden();
|
||||
}
|
||||
|
||||
// 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(url('/user-shifts'));
|
||||
// Must contain shift id and confirmation
|
||||
if (!$request->has('delete_shift') || !$request->hasPostData('delete')) {
|
||||
throw new HttpNotFound();
|
||||
}
|
||||
|
||||
$shift_id = $request->input('delete_shift');
|
||||
$shift = Shift::findOrFail($shift_id);
|
||||
|
||||
$shift = Shift($shift_id);
|
||||
if (empty($shift)) {
|
||||
throw_redirect(url('/user-shifts'));
|
||||
}
|
||||
|
||||
// Schicht löschen bestätigt
|
||||
if ($request->hasPostData('delete')) {
|
||||
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,
|
||||
'location' => $shift->location,
|
||||
'freeloaded' => $entry->freeloaded,
|
||||
]);
|
||||
}
|
||||
event('shift.deleting', ['shift' => $shift]);
|
||||
|
||||
$shift->delete();
|
||||
|
||||
|
@ -276,25 +253,15 @@ function shift_delete_controller()
|
|||
. ' to ' . $shift->end->format('Y-m-d H:i')
|
||||
);
|
||||
success(__('Shift deleted.'));
|
||||
|
||||
/** @var Redirector $redirect */
|
||||
$redirect = app('redirect');
|
||||
$old = $redirect->back()->getHeaderLine('location');
|
||||
if (Str::contains($old, '/shifts') && Str::contains($old, 'action=view')) {
|
||||
throw_redirect(url('/user-shifts'));
|
||||
}
|
||||
|
||||
$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'),
|
||||
]),
|
||||
]
|
||||
);
|
||||
throw_redirect($old);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -231,8 +231,9 @@ function user_angeltype_delete_controller(): array
|
|||
$user_angeltype = UserAngelType::findOrFail($request->input('user_angeltype_id'));
|
||||
$angeltype = $user_angeltype->angelType;
|
||||
$user_source = $user_angeltype->user;
|
||||
$isOwnAngelType = $user->id == $user_source->id;
|
||||
if (
|
||||
$user->id != $user_angeltype->user_id
|
||||
!$isOwnAngelType
|
||||
&& !$user->isAngelTypeSupporter($angeltype)
|
||||
&& !auth()->can('admin_user_angeltypes')
|
||||
) {
|
||||
|
@ -243,15 +244,15 @@ function user_angeltype_delete_controller(): array
|
|||
if ($request->hasPostData('delete')) {
|
||||
$user_angeltype->delete();
|
||||
|
||||
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));
|
||||
engelsystem_log(sprintf('User "%s" removed from "%s".', User_Nick_render($user_source, true), $angeltype->name));
|
||||
success(sprintf($isOwnAngelType ? __('You successfully left "%2$s".') : __('User "%s" removed from "%s".'), $user_source->displayName, $angeltype->name));
|
||||
|
||||
throw_redirect(url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
|
||||
}
|
||||
|
||||
return [
|
||||
__('Remove angeltype'),
|
||||
UserAngelType_delete_view($user_angeltype, $user_source, $angeltype),
|
||||
__('Leave angeltype'),
|
||||
UserAngelType_delete_view($user_angeltype, $user_source, $angeltype, $isOwnAngelType),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -265,7 +266,7 @@ function user_angeltype_update_controller(): array
|
|||
$supporter = false;
|
||||
$request = request();
|
||||
|
||||
if (!auth()->can('admin_angel_types')) {
|
||||
if (!auth()->can('admin_angel_types') && !config('supporters_can_promote')) {
|
||||
error(__('You are not allowed to set supporter rights.'));
|
||||
throw_redirect(url('/angeltypes'));
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
use Engelsystem\Database\Db;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Shifts\ShiftEntry;
|
||||
use Engelsystem\Models\User\State;
|
||||
use Engelsystem\Models\User\User;
|
||||
use Engelsystem\ShiftCalendarRenderer;
|
||||
use Engelsystem\ShiftsFilter;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
|
@ -204,7 +206,7 @@ function user_controller()
|
|||
}
|
||||
}
|
||||
|
||||
$shifts = Shifts_by_user($user_source->id, auth()->can('user_shifts_admin'));
|
||||
$shifts = Shifts_by_user($user_source->id, true);
|
||||
foreach ($shifts as $shift) {
|
||||
// TODO: Move queries to model
|
||||
$shift->needed_angeltypes = Db::select(
|
||||
|
@ -237,12 +239,27 @@ function user_controller()
|
|||
auth()->resetApiKey($user_source);
|
||||
}
|
||||
|
||||
if ($user_source->state->force_active) {
|
||||
$tshirt_score = __('Enough');
|
||||
} else {
|
||||
$tshirt_score = sprintf('%.2f', User_tshirt_score($user_source->id)) . ' h';
|
||||
$goodie_score = sprintf('%.2f', User_goodie_score($user_source->id)) . ' h';
|
||||
if ($user_source->state->force_active && config('enable_force_active')) {
|
||||
$goodie_score = '<span title="' . $goodie_score . '">' . __('Enough') . '</span>';
|
||||
}
|
||||
|
||||
$worklogs = $user_source->worklogs()
|
||||
->with(['user', 'creator'])
|
||||
->get();
|
||||
|
||||
$is_ifsg_supporter = (bool) AngelType::whereRequiresIfsgCertificate(true)
|
||||
->leftJoin('user_angel_type', 'user_angel_type.angel_type_id', 'angel_types.id')
|
||||
->where('user_angel_type.user_id', $user->id)
|
||||
->where('user_angel_type.supporter', true)
|
||||
->count();
|
||||
|
||||
$is_drive_supporter = (bool) AngelType::whereRequiresDriverLicense(true)
|
||||
->leftJoin('user_angel_type', 'user_angel_type.angel_type_id', 'angel_types.id')
|
||||
->where('user_angel_type.user_id', $user->id)
|
||||
->where('user_angel_type.supporter', true)
|
||||
->count();
|
||||
|
||||
return [
|
||||
htmlspecialchars($user_source->displayName),
|
||||
User_view(
|
||||
|
@ -253,10 +270,14 @@ function user_controller()
|
|||
$user_source->groups,
|
||||
$shifts,
|
||||
$user->id == $user_source->id,
|
||||
$tshirt_score,
|
||||
auth()->can('admin_active'),
|
||||
$goodie_score,
|
||||
auth()->can('user.goodie.edit'),
|
||||
auth()->can('admin_user_worklog'),
|
||||
UserWorkLogsForUser($user_source->id)
|
||||
$worklogs,
|
||||
auth()->can('user.ifsg.edit')
|
||||
|| $is_ifsg_supporter
|
||||
|| auth()->can('user.drive.edit')
|
||||
|| $is_drive_supporter,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -286,7 +307,7 @@ function users_list_controller()
|
|||
'freeloads',
|
||||
'active',
|
||||
'force_active',
|
||||
'got_shirt',
|
||||
'got_goodie',
|
||||
'shirt_size',
|
||||
'planned_arrival_date',
|
||||
'planned_departure_date',
|
||||
|
@ -297,13 +318,15 @@ function users_list_controller()
|
|||
}
|
||||
|
||||
/** @var User[]|Collection $users */
|
||||
$users = User::with(['contact', 'personalData', 'state'])
|
||||
$users = User::with(['contact', 'personalData', 'state', 'shiftEntries' => function (HasMany $query) {
|
||||
$query->where('freeloaded', true);
|
||||
}])
|
||||
->orderBy('name')
|
||||
->get();
|
||||
foreach ($users as $user) {
|
||||
$user->setAttribute(
|
||||
'freeloads',
|
||||
$user->shiftEntries()
|
||||
$user->shiftEntries
|
||||
->where('freeloaded', true)
|
||||
->count()
|
||||
);
|
||||
|
@ -328,7 +351,7 @@ function users_list_controller()
|
|||
State::whereActive(true)->count(),
|
||||
State::whereForceActive(true)->count(),
|
||||
ShiftEntry::whereFreeloaded(true)->count(),
|
||||
State::whereGotShirt(true)->count(),
|
||||
State::whereGotGoodie(true)->count(),
|
||||
State::query()->sum('got_voucher')
|
||||
),
|
||||
];
|
||||
|
@ -449,7 +472,7 @@ function user_driver_license_required_hint()
|
|||
$user = auth()->user();
|
||||
|
||||
// User has already entered data, no hint needed.
|
||||
if ($user->license->wantsToDrive()) {
|
||||
if (!config('driving_license_enabled') || $user->license->wantsToDrive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* Bootstrap application
|
||||
*/
|
||||
|
||||
use Engelsystem\Application;
|
||||
use Engelsystem\Http\UrlGeneratorInterface;
|
||||
|
||||
require __DIR__ . '/application.php';
|
||||
|
@ -18,7 +19,7 @@ require __DIR__ . '/includes.php';
|
|||
/**
|
||||
* Check for maintenance
|
||||
*/
|
||||
/** @var \Engelsystem\Application $app */
|
||||
/** @var Application $app */
|
||||
if ($app->get('config')->get('maintenance')) {
|
||||
http_response_code(503);
|
||||
$url = $app->get(UrlGeneratorInterface::class);
|
||||
|
|
|
@ -14,9 +14,6 @@ function theme_id(): int
|
|||
return $globals['themeId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function theme(): array
|
||||
{
|
||||
$theme_id = theme_id();
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Engelsystem\Events\Listener;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Engelsystem\Helpers\Shifts;
|
||||
use Engelsystem\Mail\EngelsystemMailer;
|
||||
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;
|
||||
|
||||
class Shift
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $log,
|
||||
protected EngelsystemMailer $mailer
|
||||
) {
|
||||
}
|
||||
|
||||
public function deletedEntryCreateWorklog(
|
||||
User $user,
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
string $name,
|
||||
string $title,
|
||||
string $type,
|
||||
Location $location,
|
||||
bool $freeloaded
|
||||
): void {
|
||||
if ($freeloaded || $start > Carbon::now()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$workLog = new Worklog();
|
||||
$workLog->user()->associate($user);
|
||||
$workLog->creator()->associate(auth()->user());
|
||||
$workLog->worked_at = $start->copy()->startOfDay();
|
||||
$workLog->hours =
|
||||
(($end->timestamp - $start->timestamp) / 60 / 60)
|
||||
* Shifts::getNightShiftMultiplier($start, $end);
|
||||
$workLog->comment = sprintf(
|
||||
__('%s (%s as %s) in %s, %s - %s'),
|
||||
$name,
|
||||
$title,
|
||||
$type,
|
||||
$location->name,
|
||||
$start->format(__('general.datetime')),
|
||||
$end->format(__('general.datetime'))
|
||||
);
|
||||
$workLog->save();
|
||||
|
||||
$this->log->info(
|
||||
'Created worklog entry from shift for {user} ({uid}): {worklog})',
|
||||
['user' => $workLog->user->name, 'uid' => $workLog->user->id, 'worklog' => $workLog->comment]
|
||||
);
|
||||
}
|
||||
|
||||
public function deletedEntrySendEmail(
|
||||
User $user,
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
string $name,
|
||||
string $title,
|
||||
string $type,
|
||||
Location $location,
|
||||
bool $freeloaded
|
||||
): void {
|
||||
if (!$user->settings->email_shiftinfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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,
|
||||
'notification.shift.updated',
|
||||
'emails/updated-shift',
|
||||
[
|
||||
'shift' => $shift,
|
||||
'oldShift' => $oldShift,
|
||||
'angelType' => $angelType,
|
||||
'username' => $user->displayName,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@ $includeFiles = [
|
|||
__DIR__ . '/../includes/model/ShiftSignupState.php',
|
||||
__DIR__ . '/../includes/model/Stats.php',
|
||||
__DIR__ . '/../includes/model/User_model.php',
|
||||
__DIR__ . '/../includes/model/UserWorkLog_model.php',
|
||||
__DIR__ . '/../includes/model/ValidationResult.php',
|
||||
|
||||
__DIR__ . '/../includes/view/AngelTypes_view.php',
|
||||
|
@ -47,7 +46,6 @@ $includeFiles = [
|
|||
__DIR__ . '/../includes/helper/legacy_helper.php',
|
||||
__DIR__ . '/../includes/helper/message_helper.php',
|
||||
__DIR__ . '/../includes/helper/email_helper.php',
|
||||
__DIR__ . '/../includes/helper/shift_helper.php',
|
||||
|
||||
__DIR__ . '/../includes/mailer/shifts_mailer.php',
|
||||
__DIR__ . '/../includes/mailer/users_mailer.php',
|
||||
|
@ -60,8 +58,6 @@ $includeFiles = [
|
|||
__DIR__ . '/../includes/pages/admin_user.php',
|
||||
__DIR__ . '/../includes/pages/user_myshifts.php',
|
||||
__DIR__ . '/../includes/pages/user_shifts.php',
|
||||
|
||||
__DIR__ . '/../includes/pages/schedule/ImportSchedule.php',
|
||||
];
|
||||
|
||||
foreach ($includeFiles as $file) {
|
||||
|
|
|
@ -179,14 +179,6 @@ class ShiftsFilter
|
|||
$this->locations = $locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isUserShiftsAdmin()
|
||||
{
|
||||
return $this->userShiftsAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $userShiftsAdmin
|
||||
*/
|
||||
|
|
|
@ -116,7 +116,7 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
|
|||
|
||||
$shifts = collect($shifts);
|
||||
|
||||
return Shift::query()
|
||||
return Shift::with(['location', 'shiftType'])
|
||||
->whereIn('id', $shifts->pluck('id')->toArray())
|
||||
->orderBy('shifts.start')
|
||||
->get();
|
||||
|
@ -189,12 +189,14 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
|
|||
]
|
||||
);
|
||||
|
||||
$shifts = [];
|
||||
$shifts = new Collection();
|
||||
foreach ($shiftsData as $shift) {
|
||||
$shifts[] = (new Shift())->forceFill($shift);
|
||||
}
|
||||
|
||||
return collect($shifts);
|
||||
$shifts->load(['location', 'shiftType']);
|
||||
|
||||
return $shifts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -354,7 +356,7 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty
|
|||
*/
|
||||
function ShiftEntries_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
|
||||
{
|
||||
return ShiftEntry::with('user')
|
||||
return ShiftEntry::with('user', 'user.state')
|
||||
->join('shifts', 'shifts.id', 'shift_entries.shift_id')
|
||||
->whereIn('shifts.location_id', $shiftsFilter->getLocations())
|
||||
->whereBetween('start', [$shiftsFilter->getStart(), $shiftsFilter->getEnd()])
|
||||
|
@ -428,14 +430,6 @@ function Shift_signup_allowed_angel(
|
|||
) {
|
||||
$free_entries = Shift_free_entries($needed_angeltype, $shift_entries);
|
||||
|
||||
if (config('signup_requires_arrival') && !$user->state->arrived) {
|
||||
return new ShiftSignupState(ShiftSignupStatus::NOT_ARRIVED, $free_entries);
|
||||
}
|
||||
|
||||
if (config('signup_advance_hours') && $shift->start->timestamp > time() + config('signup_advance_hours') * 3600) {
|
||||
return new ShiftSignupState(ShiftSignupStatus::NOT_YET, $free_entries);
|
||||
}
|
||||
|
||||
if (is_null($user_shifts) || $user_shifts->isEmpty()) {
|
||||
$user_shifts = Shifts_by_user($user->id);
|
||||
}
|
||||
|
@ -487,6 +481,14 @@ function Shift_signup_allowed_angel(
|
|||
return new ShiftSignupState(ShiftSignupStatus::COLLIDES, $free_entries);
|
||||
}
|
||||
|
||||
if (config('signup_advance_hours') && $shift->start->timestamp > time() + config('signup_advance_hours') * 3600) {
|
||||
return new ShiftSignupState(ShiftSignupStatus::NOT_YET, $free_entries);
|
||||
}
|
||||
|
||||
if (config('signup_requires_arrival') && !$user->state->arrived) {
|
||||
return new ShiftSignupState(ShiftSignupStatus::NOT_ARRIVED, $free_entries);
|
||||
}
|
||||
|
||||
// Hooray, shift is free for you!
|
||||
return new ShiftSignupState(ShiftSignupStatus::FREE, $free_entries);
|
||||
}
|
||||
|
@ -546,13 +548,12 @@ function Shift_signout_allowed(Shift $shift, AngelType $angeltype, $signout_user
|
|||
|
||||
// angeltype supporter can sign out any user at any time from their supported angeltype
|
||||
if (
|
||||
auth()->can('shiftentry_edit_angeltype_supporter')
|
||||
&& ($user->isAngelTypeSupporter($angeltype) || auth()->can('admin_user_angeltypes'))
|
||||
$user->isAngelTypeSupporter($angeltype) || auth()->can('admin_user_angeltypes')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($signout_user_id == $user->id && $shift->start->timestamp > time() + config('last_unsubscribe') * 3600) {
|
||||
if ($signout_user_id == $user->id && $shift->start->subHours(config('last_unsubscribe')) > Carbon::now()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -585,8 +586,7 @@ function Shift_signup_allowed(
|
|||
}
|
||||
|
||||
if (
|
||||
auth()->can('shiftentry_edit_angeltype_supporter')
|
||||
&& (auth()->user()->isAngelTypeSupporter($angeltype) || auth()->can('admin_user_angeltypes'))
|
||||
auth()->user()->isAngelTypeSupporter($angeltype) || auth()->can('admin_user_angeltypes')
|
||||
) {
|
||||
return Shift_signup_allowed_angeltype_supporter($needed_angeltype, $shift_entries);
|
||||
}
|
||||
|
@ -638,12 +638,13 @@ function Shifts_by_user($userId, $include_freeloaded_comments = false)
|
|||
]
|
||||
);
|
||||
|
||||
$shifts = [];
|
||||
$shifts = new Collection();
|
||||
foreach ($shiftsData as $data) {
|
||||
$shifts[] = (new Shift())->forceFill($data);
|
||||
}
|
||||
$shifts->load(['shiftType', 'location']);
|
||||
|
||||
return collect($shifts);
|
||||
return $shifts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Engelsystem\Models\Worklog;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Returns all work log entries for a user.
|
||||
*
|
||||
* @param int $userId
|
||||
* @param Carbon|null $sinceTime
|
||||
*
|
||||
* @return Worklog[]|Collection
|
||||
*/
|
||||
function UserWorkLogsForUser($userId, Carbon $sinceTime = null)
|
||||
{
|
||||
$worklogs = Worklog::whereUserId($userId);
|
||||
if ($sinceTime) {
|
||||
$worklogs = $worklogs->whereDate('worked_at', '>=', $sinceTime);
|
||||
}
|
||||
|
||||
return $worklogs->get();
|
||||
}
|
|
@ -5,7 +5,6 @@ use Engelsystem\Database\Db;
|
|||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\User\User;
|
||||
use Engelsystem\Models\Worklog;
|
||||
use Engelsystem\ValidationResult;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
@ -14,17 +13,17 @@ use Illuminate\Support\Collection;
|
|||
*/
|
||||
|
||||
/**
|
||||
* Returns the tshirt score (number of hours counted for tshirt).
|
||||
* Returns the goodie score (number of hours counted for tshirt).
|
||||
* Accounts only ended shifts.
|
||||
*
|
||||
* @param int $userId
|
||||
* @return int
|
||||
* @return float
|
||||
*/
|
||||
function User_tshirt_score($userId)
|
||||
function User_goodie_score(int $userId): float
|
||||
{
|
||||
$shift_sum_formula = User_get_shifts_sum_query();
|
||||
$result_shifts = Db::selectOne(sprintf('
|
||||
SELECT ROUND((%s) / 3600, 2) AS `tshirt_score`
|
||||
SELECT ROUND((%s) / 3600, 2) AS `goodie_score`
|
||||
FROM `users` LEFT JOIN `shift_entries` ON `users`.`id` = `shift_entries`.`user_id`
|
||||
LEFT JOIN `shifts` ON `shift_entries`.`shift_id` = `shifts`.`id`
|
||||
WHERE `users`.`id` = ?
|
||||
|
@ -33,8 +32,8 @@ function User_tshirt_score($userId)
|
|||
', $shift_sum_formula), [
|
||||
$userId,
|
||||
]);
|
||||
if (!isset($result_shifts['tshirt_score'])) {
|
||||
$result_shifts = ['tshirt_score' => 0];
|
||||
if (!isset($result_shifts['goodie_score'])) {
|
||||
$result_shifts = ['goodie_score' => 0];
|
||||
}
|
||||
|
||||
$worklogHours = Worklog::query()
|
||||
|
@ -42,7 +41,7 @@ function User_tshirt_score($userId)
|
|||
->where('worked_at', '<=', Carbon::Now())
|
||||
->sum('hours');
|
||||
|
||||
return $result_shifts['tshirt_score'] + $worklogHours;
|
||||
return $result_shifts['goodie_score'] + $worklogHours;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,76 +66,6 @@ function Users_by_angeltype_inverted(AngelType $angeltype)
|
|||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the planned arrival date
|
||||
*
|
||||
* @param int $planned_arrival_date Unix timestamp
|
||||
* @return ValidationResult
|
||||
*/
|
||||
function User_validate_planned_arrival_date($planned_arrival_date)
|
||||
{
|
||||
if (is_null($planned_arrival_date)) {
|
||||
// null is not okay
|
||||
return new ValidationResult(false, time());
|
||||
}
|
||||
|
||||
$config = config();
|
||||
$buildup = $config->get('buildup_start');
|
||||
$teardown = $config->get('teardown_end');
|
||||
|
||||
/** @var Carbon $buildup */
|
||||
if (!empty($buildup) && Carbon::createFromTimestamp($planned_arrival_date)->lessThan($buildup->setTime(0, 0))) {
|
||||
// Planned arrival can not be before buildup start date
|
||||
return new ValidationResult(false, $buildup->getTimestamp());
|
||||
}
|
||||
|
||||
/** @var Carbon $teardown */
|
||||
if (!empty($teardown) && Carbon::createFromTimestamp($planned_arrival_date)->greaterThanOrEqualTo($teardown->addDay()->setTime(0, 0))) {
|
||||
// Planned arrival can not be after teardown end date
|
||||
return new ValidationResult(false, $teardown->getTimestamp());
|
||||
}
|
||||
|
||||
return new ValidationResult(true, $planned_arrival_date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the planned departure date
|
||||
*
|
||||
* @param int $planned_arrival_date Unix timestamp
|
||||
* @param int $planned_departure_date Unix timestamp
|
||||
* @return ValidationResult
|
||||
*/
|
||||
function User_validate_planned_departure_date($planned_arrival_date, $planned_departure_date)
|
||||
{
|
||||
if (is_null($planned_departure_date)) {
|
||||
// null is okay
|
||||
return new ValidationResult(true, null);
|
||||
}
|
||||
|
||||
if ($planned_arrival_date > $planned_departure_date) {
|
||||
// departure cannot be before arrival
|
||||
return new ValidationResult(false, $planned_arrival_date);
|
||||
}
|
||||
|
||||
$config = config();
|
||||
$buildup = $config->get('buildup_start');
|
||||
$teardown = $config->get('teardown_end');
|
||||
|
||||
/** @var Carbon $buildup */
|
||||
if (!empty($buildup) && Carbon::createFromTimestamp($planned_departure_date)->lessThan($buildup->setTime(0, 0))) {
|
||||
// Planned departure can not be before buildup start date
|
||||
return new ValidationResult(false, $buildup->getTimestamp());
|
||||
}
|
||||
|
||||
/** @var Carbon $teardown */
|
||||
if (!empty($teardown) && Carbon::createFromTimestamp($planned_departure_date)->greaterThanOrEqualTo($teardown->addDay()->setTime(0, 0))) {
|
||||
// Planned departure can not be after teardown end date
|
||||
return new ValidationResult(false, $teardown->getTimestamp());
|
||||
}
|
||||
|
||||
return new ValidationResult(true, $planned_departure_date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return float
|
||||
|
@ -149,7 +78,10 @@ function User_get_eligable_voucher_count($user)
|
|||
: null;
|
||||
|
||||
$shiftEntries = ShiftEntries_finished_by_user($user, $start);
|
||||
$worklog = UserWorkLogsForUser($user->id, $start);
|
||||
$worklog = $user->worklogs()
|
||||
->whereDate('worked_at', '>=', $start ?: 0)
|
||||
->with(['user', 'creator'])
|
||||
->get();
|
||||
$shifts_done =
|
||||
count($shiftEntries)
|
||||
+ $worklog->count();
|
||||
|
@ -191,13 +123,20 @@ function User_get_shifts_sum_query()
|
|||
return 'COALESCE(SUM(UNIX_TIMESTAMP(shifts.end) - UNIX_TIMESTAMP(shifts.start)), 0)';
|
||||
}
|
||||
|
||||
/* @see \Engelsystem\Models\Shifts\Shift::isNightShift to keep it in sync */
|
||||
return sprintf(
|
||||
'
|
||||
COALESCE(SUM(
|
||||
(1 + (
|
||||
(HOUR(shifts.end) > %1$d AND HOUR(shifts.end) < %2$d)
|
||||
OR (HOUR(shifts.start) > %1$d AND HOUR(shifts.start) < %2$d)
|
||||
OR (HOUR(shifts.start) <= %1$d AND HOUR(shifts.end) >= %2$d)
|
||||
/* Starts during night */
|
||||
HOUR(shifts.start) >= %1$d AND HOUR(shifts.start) < %2$d
|
||||
/* Ends during night */
|
||||
OR (
|
||||
HOUR(shifts.end) > %1$d
|
||||
|| HOUR(shifts.end) = %1$d AND MINUTE(shifts.end) > 0
|
||||
) AND HOUR(shifts.end) <= %2$d
|
||||
/* Starts before and ends after night */
|
||||
OR HOUR(shifts.start) <= %1$d AND HOUR(shifts.end) >= %2$d
|
||||
))
|
||||
* (UNIX_TIMESTAMP(shifts.end) - UNIX_TIMESTAMP(shifts.start))
|
||||
* (1 - (%3$d + 1) * `shift_entries`.`freeloaded`)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<?php
|
||||
|
||||
use Engelsystem\Helpers\Carbon;
|
||||
use Engelsystem\Models\Shifts\ShiftEntry;
|
||||
use Engelsystem\Models\User\State;
|
||||
use Engelsystem\Models\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Engelsystem\Config\GoodieType;
|
||||
|
@ -42,7 +44,7 @@ function admin_active()
|
|||
if ($request->has('set_active')) {
|
||||
if ($request->has('count') && preg_match('/^\d+$/', $request->input('count'))) {
|
||||
$count = strip_request_item('count');
|
||||
if ($count < $forced_count) {
|
||||
if ($count < $forced_count && config('enable_force_active')) {
|
||||
error(sprintf(
|
||||
__('At least %s angels are forced to be active. The number has to be greater.'),
|
||||
$forced_count
|
||||
|
@ -56,7 +58,7 @@ function admin_active()
|
|||
|
||||
if ($request->hasPostData('ack')) {
|
||||
State::query()
|
||||
->where('got_shirt', '=', false)
|
||||
->where('got_goodie', '=', false)
|
||||
->update(['active' => false]);
|
||||
|
||||
$query = User::query()
|
||||
|
@ -78,8 +80,16 @@ function admin_active()
|
|||
->leftJoin('shifts', 'shift_entries.shift_id', '=', 'shifts.id')
|
||||
->leftJoin('users_state', 'users.id', '=', 'users_state.user_id')
|
||||
->where('users_state.arrived', '=', true)
|
||||
->groupBy('users.id')
|
||||
->orderByDesc('force_active')
|
||||
->orWhere(function (EloquentBuilder $userinfo) {
|
||||
$userinfo->where('users_state.arrived', '=', false)
|
||||
->whereNotNull('users_state.user_info')
|
||||
->whereNot('users_state.user_info', '');
|
||||
})
|
||||
->groupBy('users.id');
|
||||
if (config('enable_force_active')) {
|
||||
$query->orderByDesc('force_active');
|
||||
}
|
||||
$query
|
||||
->orderByDesc('shift_length')
|
||||
->orderByDesc('name')
|
||||
->limit($count);
|
||||
|
@ -130,7 +140,7 @@ function admin_active()
|
|||
$user_id = $request->input('tshirt');
|
||||
$user_source = User::find($user_id);
|
||||
if ($user_source) {
|
||||
$user_source->state->got_shirt = true;
|
||||
$user_source->state->got_goodie = 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);
|
||||
|
@ -141,7 +151,7 @@ function admin_active()
|
|||
$user_id = $request->input('not_tshirt');
|
||||
$user_source = User::find($user_id);
|
||||
if ($user_source) {
|
||||
$user_source->state->got_shirt = false;
|
||||
$user_source->state->got_goodie = 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);
|
||||
|
@ -151,7 +161,7 @@ function admin_active()
|
|||
}
|
||||
}
|
||||
|
||||
$query = User::with('personalData')
|
||||
$query = User::with(['personalData', 'state'])
|
||||
->selectRaw(
|
||||
sprintf(
|
||||
'
|
||||
|
@ -180,8 +190,16 @@ function admin_active()
|
|||
})
|
||||
->leftJoin('users_state', 'users.id', '=', 'users_state.user_id')
|
||||
->where('users_state.arrived', '=', true)
|
||||
->groupBy('users.id')
|
||||
->orderByDesc('force_active')
|
||||
->orWhere(function (EloquentBuilder $userinfo) {
|
||||
$userinfo->where('users_state.arrived', '=', false)
|
||||
->whereNotNull('users_state.user_info')
|
||||
->whereNot('users_state.user_info', '');
|
||||
})
|
||||
->groupBy('users.id');
|
||||
if (config('enable_force_active')) {
|
||||
$query->orderByDesc('force_active');
|
||||
}
|
||||
$query
|
||||
->orderByDesc('shift_length')
|
||||
->orderByDesc('name');
|
||||
|
||||
|
@ -212,18 +230,34 @@ function admin_active()
|
|||
}
|
||||
}
|
||||
|
||||
$timeSum = 0;
|
||||
/** @var ShiftEntry[] $shiftEntries */
|
||||
$shiftEntries = $usr->shiftEntries()
|
||||
->with('shift')
|
||||
->get();
|
||||
foreach ($shiftEntries as $entry) {
|
||||
if ($entry->freeloaded || $entry->shift->start > Carbon::now()) {
|
||||
continue;
|
||||
}
|
||||
$timeSum += ($entry->shift->end->timestamp - $entry->shift->start->timestamp);
|
||||
}
|
||||
foreach ($usr->worklogs as $worklog) {
|
||||
$timeSum += $worklog->hours * 3600;
|
||||
}
|
||||
|
||||
$shirtSize = $usr->personalData->shirt_size;
|
||||
$userData = [];
|
||||
$userData['no'] = count($matched_users) + 1;
|
||||
$userData['nick'] = User_Nick_render($usr) . User_Pronoun_render($usr);
|
||||
$userData['nick'] = User_Nick_render($usr) . User_Pronoun_render($usr) . user_info_icon($usr);
|
||||
if ($goodie_tshirt) {
|
||||
$userData['shirt_size'] = (isset($tshirt_sizes[$shirtSize]) ? $tshirt_sizes[$shirtSize] : '');
|
||||
}
|
||||
$userData['work_time'] = round($usr['shift_length'] / 60)
|
||||
$userData['work_time'] = sprintf('%.2f', round($timeSum / 3600, 2)) . ' h';
|
||||
$userData['score'] = round($usr['shift_length'] / 60)
|
||||
. ' min (' . sprintf('%.2f', $usr['shift_length'] / 3600) . ' h)';
|
||||
$userData['active'] = icon_bool($usr->state->active == 1);
|
||||
$userData['force_active'] = icon_bool($usr->state->force_active == 1);
|
||||
$userData['tshirt'] = icon_bool($usr->state->got_shirt == 1);
|
||||
$userData['active'] = icon_bool($usr->state->active);
|
||||
$userData['force_active'] = icon_bool($usr->state->force_active);
|
||||
$userData['tshirt'] = icon_bool($usr->state->got_goodie);
|
||||
$userData['shift_count'] = $usr['shift_count'];
|
||||
|
||||
$actions = [];
|
||||
|
@ -257,7 +291,7 @@ function admin_active()
|
|||
true
|
||||
);
|
||||
}
|
||||
if (!$usr->state->got_shirt) {
|
||||
if (!$usr->state->got_goodie) {
|
||||
$parametersShirt = [
|
||||
'tshirt' => $usr->id,
|
||||
'search' => $search,
|
||||
|
@ -275,7 +309,7 @@ function admin_active()
|
|||
);
|
||||
}
|
||||
}
|
||||
if ($usr->state->got_shirt) {
|
||||
if ($usr->state->got_goodie) {
|
||||
$parameters = [
|
||||
'not_tshirt' => $usr->id,
|
||||
'search' => $search,
|
||||
|
@ -309,7 +343,7 @@ function admin_active()
|
|||
$gc = State::query()
|
||||
->leftJoin('users_settings', 'users_state.user_id', '=', 'users_settings.user_id')
|
||||
->leftJoin('users_personal_data', 'users_state.user_id', '=', 'users_personal_data.user_id')
|
||||
->where('users_state.got_shirt', '=', true)
|
||||
->where('users_state.got_goodie', '=', true)
|
||||
->where('users_personal_data.shirt_size', '=', $size)
|
||||
->count();
|
||||
$goodie_statistics[] = [
|
||||
|
@ -321,7 +355,7 @@ function admin_active()
|
|||
|
||||
$goodie_statistics[] = array_merge(
|
||||
($goodie_tshirt ? ['size' => '<b>' . __('Sum') . '</b>'] : []),
|
||||
['given' => '<b>' . State::whereGotShirt(true)->count() . '</b>']
|
||||
['given' => '<b>' . State::whereGotGoodie(true)->count() . '</b>']
|
||||
);
|
||||
|
||||
return page_with_title(admin_active_title(), [
|
||||
|
@ -331,7 +365,7 @@ function 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_text('count', __('How many angels should be active?'), $count ?: $forced_count),
|
||||
form_submit('set_active', icon('eye') . __('form.preview'), 'btn-info'),
|
||||
]) : $set_active,
|
||||
$msg . msg(),
|
||||
|
@ -343,12 +377,18 @@ function admin_active()
|
|||
],
|
||||
($goodie_tshirt ? ['shirt_size' => __('Size')] : []),
|
||||
[
|
||||
'shift_count' => __('Shifts'),
|
||||
'shift_count' => __('general.shifts'),
|
||||
'work_time' => __('Length'),
|
||||
'active' => __('Active?'),
|
||||
'force_active' => __('Forced'),
|
||||
],
|
||||
($goodie_enabled ? ['tshirt' => ($goodie_tshirt ? __('T-shirt?') : __('Goodie?'))] : []),
|
||||
($goodie_enabled ? ['score' => ($goodie_tshirt
|
||||
? __('T-shirt score')
|
||||
: __('Goodie score')
|
||||
)] : []),
|
||||
[
|
||||
'active' => __('Active'),
|
||||
],
|
||||
(config('enable_force_active') ? ['force_active' => __('Forced'),] : []),
|
||||
($goodie_enabled ? ['tshirt' => ($goodie_tshirt ? __('T-shirt') : __('Goodie'))] : []),
|
||||
[
|
||||
'actions' => __('general.actions'),
|
||||
]
|
||||
|
|
|
@ -8,7 +8,7 @@ use Engelsystem\Models\User\User;
|
|||
*/
|
||||
function admin_arrive_title()
|
||||
{
|
||||
return __('Arrive angels');
|
||||
return auth()->can('admin_arrive') ? __('Arrive angels') : __('Angels');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,12 +19,14 @@ function admin_arrive()
|
|||
$msg = '';
|
||||
$search = '';
|
||||
$request = request();
|
||||
$admin_arrive = auth()->can('admin_arrive');
|
||||
|
||||
if ($request->has('search')) {
|
||||
$search = strip_request_item('search');
|
||||
$search = trim($search);
|
||||
}
|
||||
|
||||
if ($admin_arrive) {
|
||||
$action = $request->get('action');
|
||||
if (
|
||||
$action == 'reset'
|
||||
|
@ -63,9 +65,10 @@ function admin_arrive()
|
|||
$msg = error(__('Angel not found.'), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var User[] $users */
|
||||
$users = User::with('personalData')->orderBy('name')->get();
|
||||
$users = User::with(['personalData', 'state'])->orderBy('name')->get();
|
||||
$arrival_count_at_day = [];
|
||||
$planned_arrival_count_at_day = [];
|
||||
$planned_departure_count_at_day = [];
|
||||
|
@ -100,9 +103,7 @@ function admin_arrive()
|
|||
|
||||
$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>'
|
||||
: '');
|
||||
. user_info_icon($usr);
|
||||
$plannedDepartureDate = $usr->personalData->planned_departure_date;
|
||||
$arrivalDate = $usr->state->arrival_date;
|
||||
$plannedArrivalDate = $usr->personalData->planned_arrival_date;
|
||||
|
@ -208,15 +209,17 @@ function admin_arrive()
|
|||
form_text('search', __('form.search'), $search),
|
||||
form_submit('submit', icon('search') . __('form.search')),
|
||||
], url('/admin-arrive')),
|
||||
table([
|
||||
'name' => __('general.name'),
|
||||
'rendered_planned_arrival_date' => __('Planned arrival'),
|
||||
'arrived' => __('Arrived?'),
|
||||
table(array_merge(
|
||||
['name' => __('general.name'),],
|
||||
($admin_arrive ? ['rendered_planned_arrival_date' => __('Planned arrival')] : []),
|
||||
['arrived' => __('Arrived?')],
|
||||
($admin_arrive ? [
|
||||
'rendered_arrival_date' => __('Arrival date'),
|
||||
'rendered_planned_departure_date' => __('Planned departure'),
|
||||
'actions' => __('general.actions'),
|
||||
], $users_matched),
|
||||
div('row', [
|
||||
] : [])
|
||||
), $users_matched),
|
||||
div('row', $admin_arrive ? [
|
||||
div('col-md-4', [
|
||||
heading(__('Planned arrival statistics'), 3),
|
||||
BarChart::render([
|
||||
|
@ -262,6 +265,6 @@ function admin_arrive()
|
|||
'sum' => __('Sum'),
|
||||
], $planned_departure_at_day),
|
||||
]),
|
||||
]),
|
||||
] : []),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ function admin_free()
|
|||
/** @var User[] $users */
|
||||
$users = [];
|
||||
if ($request->has('submit')) {
|
||||
$query = User::with('personalData')
|
||||
$query = User::with(['personalData', 'contact', 'state'])
|
||||
->select('users.*')
|
||||
->leftJoin('shift_entries', 'users.id', 'shift_entries.user_id')
|
||||
->leftJoin('users_state', 'users.id', 'users_state.user_id')
|
||||
|
@ -99,9 +99,7 @@ function admin_free()
|
|||
$free_users_table[] = [
|
||||
'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>'
|
||||
: ''),
|
||||
. user_info_icon($usr),
|
||||
'shift_state' => User_shift_state_render($usr),
|
||||
'last_shift' => User_last_shift_render($usr),
|
||||
'dect' => sprintf('<a href="tel:%s">%1$s</a>', htmlspecialchars((string) $usr->contact->dect)),
|
||||
|
|
|
@ -21,13 +21,13 @@ function admin_groups()
|
|||
$html = '';
|
||||
$request = request();
|
||||
/** @var Group[]|Collection $groups */
|
||||
$groups = Group::query()->orderBy('name')->get();
|
||||
$groups = Group::with('privileges')->orderBy('name')->get();
|
||||
|
||||
if (!$request->has('action')) {
|
||||
$groups_table = [];
|
||||
foreach ($groups as $group) {
|
||||
/** @var Privilege[]|Collection $privileges */
|
||||
$privileges = $group->privileges()->orderBy('name')->get();
|
||||
$privileges = $group->privileges->sortBy('name');
|
||||
$privileges_html = [];
|
||||
|
||||
foreach ($privileges as $privilege) {
|
||||
|
@ -43,10 +43,9 @@ function admin_groups()
|
|||
['action' => 'edit', 'id' => $group->id]
|
||||
),
|
||||
icon('pencil'),
|
||||
'btn-sm',
|
||||
'',
|
||||
'',
|
||||
__('form.edit'),
|
||||
'btn-sm'
|
||||
__('form.edit')
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -122,10 +121,9 @@ function admin_groups()
|
|||
. ' edited: ' . join(', ', $privilege_names)
|
||||
);
|
||||
throw_redirect(url('/admin-groups'));
|
||||
} else {
|
||||
return error('No Group found.', true);
|
||||
}
|
||||
break;
|
||||
|
||||
return error('No Group found.', true);
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
|
|
|
@ -41,11 +41,13 @@ function admin_shifts()
|
|||
|
||||
// Locations laden
|
||||
$locations = Location::orderBy('name')->get();
|
||||
$no_locations = $locations->isEmpty();
|
||||
$location_array = $locations->pluck('name', 'id')->toArray();
|
||||
|
||||
// Load angeltypes
|
||||
/** @var AngelType[] $types */
|
||||
/** @var AngelType[]|Collection $types */
|
||||
$types = AngelType::all();
|
||||
$no_angeltypes = $types->isEmpty();
|
||||
$needed_angel_types = [];
|
||||
foreach ($types as $type) {
|
||||
$needed_angel_types[$type->id] = 0;
|
||||
|
@ -54,6 +56,7 @@ function admin_shifts()
|
|||
// Load shift types
|
||||
/** @var ShiftType[]|Collection $shifttypes_source */
|
||||
$shifttypes_source = ShiftType::all();
|
||||
$no_shifttypes = $shifttypes_source->isEmpty();
|
||||
$shifttypes = [];
|
||||
foreach ($shifttypes_source as $shifttype) {
|
||||
$shifttypes[$shifttype->id] = $shifttype->name;
|
||||
|
@ -184,15 +187,23 @@ function admin_shifts()
|
|||
error(sprintf(__('Please check the needed angels for team %s.'), $type->name));
|
||||
}
|
||||
}
|
||||
|
||||
if (array_sum($needed_angel_types) == 0) {
|
||||
$valid = false;
|
||||
error(__('There are 0 angels needed. Please enter the amounts of needed angels.'));
|
||||
}
|
||||
} else {
|
||||
$valid = false;
|
||||
error(__('Please select a mode for needed angels.'));
|
||||
}
|
||||
|
||||
if (
|
||||
$angelmode == 'manually' && array_sum($needed_angel_types) == 0
|
||||
|| $angelmode == 'location' && !NeededAngelType::whereLocationId($lid)
|
||||
->where('count', '>', '0')
|
||||
->count()
|
||||
|| $angelmode == 'shift_type' && !NeededAngelType::whereShiftTypeId($shifttype_id)
|
||||
->where('count', '>', '0')
|
||||
->count()
|
||||
) {
|
||||
$valid = false;
|
||||
error(__('There are 0 angels needed. Please enter the amounts of needed angels.'));
|
||||
}
|
||||
} else {
|
||||
$valid = false;
|
||||
error(__('Please select needed angels.'));
|
||||
|
@ -318,6 +329,9 @@ function admin_shifts()
|
|||
|
||||
$shifts_table = [];
|
||||
foreach ($shifts as $shift) {
|
||||
$shiftType = $shifttypes_source->find($shift['shift_type_id']);
|
||||
$location = $locations->find($shift['location_id']);
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = $shift['start'];
|
||||
/** @var Carbon $end */
|
||||
|
@ -332,9 +346,9 @@ function admin_shifts()
|
|||
. '</span>'
|
||||
. ', ' . round($end->copy()->diffInMinutes($start) / 60, 2) . 'h'
|
||||
. '<br>'
|
||||
. location_name_render(Location::find($shift['location_id'])),
|
||||
. location_name_render($location),
|
||||
'title' =>
|
||||
htmlspecialchars(ShiftType::find($shifttype_id)->name)
|
||||
htmlspecialchars($shiftType->name)
|
||||
. ($shift['title'] ? '<br />' . htmlspecialchars($shift['title']) : ''),
|
||||
'needed_angels' => '',
|
||||
];
|
||||
|
@ -414,15 +428,6 @@ function admin_shifts()
|
|||
$shift->createdBy()->associate(auth()->user());
|
||||
$shift->save();
|
||||
|
||||
engelsystem_log(
|
||||
'Shift created: ' . $shifttypes[$shift->shift_type_id]
|
||||
. ' with title ' . $shift->title
|
||||
. ' with description ' . $shift->description
|
||||
. ' from ' . $shift->start->format('Y-m-d H:i')
|
||||
. ' to ' . $shift->end->format('Y-m-d H:i')
|
||||
. ', transaction: ' . $transactionId
|
||||
);
|
||||
|
||||
$needed_angel_types_info = [];
|
||||
foreach ($session->get('admin_shifts_types', []) as $type_id => $count) {
|
||||
$angel_type_source = AngelType::find($type_id);
|
||||
|
@ -436,10 +441,21 @@ function admin_shifts()
|
|||
$needed_angel_types_info[] = $angel_type_source->name . ': ' . $count;
|
||||
}
|
||||
}
|
||||
engelsystem_log('Shift needs following angel types: ' . join(', ', $needed_angel_types_info));
|
||||
|
||||
engelsystem_log(
|
||||
'Shift created: ' . $shifttypes[$shift->shift_type_id]
|
||||
. ' (' . $shift->id . ')'
|
||||
. ' with title ' . $shift->title
|
||||
. ' and description ' . $shift->description
|
||||
. ' from ' . $shift->start->format('Y-m-d H:i')
|
||||
. ' to ' . $shift->end->format('Y-m-d H:i')
|
||||
. ' in ' . $shift->location->name
|
||||
. ' with angel types: ' . join(', ', $needed_angel_types_info)
|
||||
. ', transaction: ' . $transactionId
|
||||
);
|
||||
}
|
||||
|
||||
success('Shifts created.');
|
||||
success(__('Shifts created.'));
|
||||
throw_redirect(url('/admin-shifts'));
|
||||
} else {
|
||||
$session->remove('admin_shifts_shifts');
|
||||
|
@ -488,6 +504,9 @@ function admin_shifts()
|
|||
icon('clock-history')
|
||||
) . form([$reset], '', 'display:inline'),
|
||||
[
|
||||
$no_locations ? warning(__('admin_shifts.no_locations')) : '',
|
||||
$no_shifttypes ? warning(__('admin_shifts.no_shifttypes')) : '',
|
||||
$no_angeltypes ? warning(__('admin_shifts.no_angeltypes')) : '',
|
||||
msg(),
|
||||
form([
|
||||
div('row', [
|
||||
|
@ -528,7 +547,7 @@ function admin_shifts()
|
|||
form_radio('mode', __('Create multiple shifts'), $mode == 'multi', 'multi'),
|
||||
form_text(
|
||||
'length',
|
||||
__('Length'),
|
||||
__('Length (in minutes)'),
|
||||
$request->has('length')
|
||||
? $request->input('length')
|
||||
: '120',
|
||||
|
|
|
@ -28,8 +28,9 @@ function admin_user()
|
|||
$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');
|
||||
$user_goodie_edit = auth()->can('user.goodie.edit');
|
||||
$user_nick_edit = auth()->can('user.nick.edit');
|
||||
$admin_arrive = auth()->can('admin_arrive');
|
||||
|
||||
if (!$request->has('id')) {
|
||||
throw_redirect(users_link());
|
||||
|
@ -44,7 +45,7 @@ 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 && $user_edit_shirt) {
|
||||
if ($goodie_enabled && $user_goodie_edit) {
|
||||
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 {
|
||||
|
@ -62,7 +63,7 @@ function admin_user()
|
|||
$html .= '<table>' . "\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') . '>'
|
||||
. '" class="form-control" maxlength="24" ' . ($user_nick_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(__('general.datetime')) : '-')
|
||||
|
@ -88,7 +89,7 @@ function admin_user()
|
|||
. '<input type="email" size="40" name="eemail" value="' . htmlspecialchars($user_source->email) . '" class="form-control" maxlength="254">'
|
||||
. '</td></tr>' . "\n";
|
||||
}
|
||||
if ($goodie_tshirt && $user_edit_shirt) {
|
||||
if ($goodie_tshirt && $user_goodie_edit) {
|
||||
$html .= ' <tr><td>' . __('user.shirt_size') . '</td><td>'
|
||||
. html_select_key(
|
||||
'size',
|
||||
|
@ -121,34 +122,38 @@ function admin_user()
|
|||
|
||||
// Arrived?
|
||||
$html .= ' <tr><td>' . __('user.arrived') . '</td><td>' . "\n";
|
||||
$html .= ($user_source->state->arrived ? __('Yes') : __('No'));
|
||||
$html .= $admin_arrive
|
||||
? html_options('arrive', $options, $user_source->state->arrived)
|
||||
: icon_bool($user_source->state->arrived);
|
||||
$html .= '</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 .= $user_goodie_edit
|
||||
? html_options('eAktiv', $options, $user_source->state->active)
|
||||
: icon_bool($user_source->state->active);
|
||||
$html .= '</td></tr>' . "\n";
|
||||
|
||||
// Forced active?
|
||||
if (config('enable_force_active')) {
|
||||
$html .= ' <tr><td>' . __('Force active') . '</td><td>' . "\n";
|
||||
$html .= auth()->can('user.fa.edit')
|
||||
? html_options('force_active', $options, $user_source->state->force_active)
|
||||
: icon_bool($user_source->state->force_active);
|
||||
$html .= '</td></tr>' . "\n";
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// got goodie?
|
||||
$html .= ' <tr><td>'
|
||||
. ($goodie_tshirt ? __('T-shirt') : __('Goodie'))
|
||||
. '</td><td>' . "\n";
|
||||
$html .= $user_goodie_edit
|
||||
? html_options('eTshirt', $options, $user_source->state->got_goodie)
|
||||
: icon_bool($user_source->state->got_goodie);
|
||||
$html .= '</td></tr>' . "\n";
|
||||
}
|
||||
|
||||
if ($goodie_enabled && $user_edit_shirt) {
|
||||
// T-Shirt bekommen?
|
||||
if ($goodie_tshirt) {
|
||||
$html .= ' <tr><td>' . __('T-shirt') . '</td><td>' . "\n";
|
||||
} else {
|
||||
$html .= ' <tr><td>' . __('Goodie') . '</td><td>' . "\n";
|
||||
}
|
||||
$html .= html_options('eTshirt', $options, $user_source->state->got_shirt) . '</td></tr>' . "\n";
|
||||
}
|
||||
$html .= '</table>' . "\n" . '</td><td></td></tr>';
|
||||
|
||||
$html .= '</td></tr>' . "\n";
|
||||
|
@ -284,18 +289,26 @@ function admin_user()
|
|||
$user_source = User::find($user_id);
|
||||
|
||||
$changed_email = false;
|
||||
$email = $request->postData('eemail');
|
||||
if (($user_source->email !== $email) && User::whereEmail($email)->exists()) {
|
||||
$html .= error(__('settings.profile.email.already-taken') . "\n", true);
|
||||
break;
|
||||
}
|
||||
if ($user_source->settings->email_human) {
|
||||
$changed_email = $user_source->email !== $request->postData('eemail');
|
||||
$user_source->email = $request->postData('eemail');
|
||||
$changed_email = $user_source->email !== $email;
|
||||
$user_source->email = $email;
|
||||
}
|
||||
|
||||
$nick = trim($request->get('eNick'));
|
||||
$nickValid = (new Username())->validate($nick);
|
||||
|
||||
$changed_nick = false;
|
||||
$nick = trim((string) $request->get('eNick'));
|
||||
$nickValid = (new Username())->validate($nick);
|
||||
if (($user_source->name !== $nick) && User::whereName($nick)->exists()) {
|
||||
$html .= error(__('settings.profile.nick.already-taken') . "\n", true);
|
||||
break;
|
||||
}
|
||||
$old_nick = $user_source->name;
|
||||
if ($nickValid && $user_edit) {
|
||||
$changed_nick = $user_source->name !== $nick;
|
||||
if ($nickValid && $user_nick_edit) {
|
||||
$changed_nick = ($user_source->name !== $nick) || User::whereName($nick)->exists();
|
||||
$user_source->name = $nick;
|
||||
}
|
||||
$user_source->save();
|
||||
|
@ -304,7 +317,7 @@ function admin_user()
|
|||
$user_source->personalData->first_name = $request->postData('eVorname');
|
||||
$user_source->personalData->last_name = $request->postData('eName');
|
||||
}
|
||||
if ($goodie_tshirt && $user_edit_shirt) {
|
||||
if ($goodie_tshirt && $user_goodie_edit) {
|
||||
$user_source->personalData->shirt_size = $request->postData('eSize');
|
||||
}
|
||||
$user_source->personalData->save();
|
||||
|
@ -315,17 +328,20 @@ function admin_user()
|
|||
}
|
||||
$user_source->contact->save();
|
||||
|
||||
if ($goodie_enabled && $user_edit_shirt) {
|
||||
$user_source->state->got_shirt = $request->postData('eTshirt');
|
||||
if ($goodie_enabled && $user_goodie_edit) {
|
||||
$user_source->state->got_goodie = $request->postData('eTshirt');
|
||||
}
|
||||
if ($user_info_edit) {
|
||||
$user_source->state->user_info = $request->postData('userInfo');
|
||||
}
|
||||
if ($admin_arrive) {
|
||||
$user_source->state->arrived = $request->postData('arrive');
|
||||
}
|
||||
|
||||
if ($user_edit_shirt) {
|
||||
if ($user_goodie_edit) {
|
||||
$user_source->state->active = $request->postData('eAktiv');
|
||||
}
|
||||
if (auth()->can('admin_active')) {
|
||||
if (auth()->can('user.fa.edit') && config('enable_force_active')) {
|
||||
$user_source->state->force_active = $request->input('force_active');
|
||||
}
|
||||
$user_source->state->save();
|
||||
|
@ -337,9 +353,10 @@ function admin_user()
|
|||
. ' (' . $user_source->id . ')'
|
||||
. ($changed_email ? ', email modified' : '')
|
||||
. ($goodie_tshirt ? ', t-shirt-size: ' . $user_source->personalData->shirt_size : '')
|
||||
. ', arrived: ' . $user_source->state->arrived
|
||||
. ', active: ' . $user_source->state->active
|
||||
. ', force-active: ' . $user_source->state->force_active
|
||||
. ($goodie_tshirt ? ', t-shirt: ' : ', goodie: ' . $user_source->state->got_shirt)
|
||||
. ($goodie_tshirt ? ', t-shirt: ' : ', goodie: ' . $user_source->state->got_goodie)
|
||||
. ($user_info_edit ? ', user-info: ' . $user_source->state->user_info : '')
|
||||
);
|
||||
$html .= success(__('Changes were saved.') . "\n", true);
|
||||
|
|
|
@ -20,10 +20,18 @@ function user_myshifts()
|
|||
{
|
||||
$user = auth()->user();
|
||||
$request = request();
|
||||
$is_angeltype_supporter = false;
|
||||
if ($request->has('edit')) {
|
||||
$id = $request->input('edit');
|
||||
$shiftEntry = ShiftEntry::where('id', $id)
|
||||
->where('user_id', User::find($request->input('id'))->id)
|
||||
->first();
|
||||
$is_angeltype_supporter = $shiftEntry && auth()->user()->isAngelTypeSupporter($shiftEntry->angelType);
|
||||
}
|
||||
|
||||
if (
|
||||
$request->has('id')
|
||||
&& auth()->can('user_shifts_admin')
|
||||
&& (auth()->can('user_shifts_admin') || $is_angeltype_supporter)
|
||||
&& preg_match('/^\d+$/', $request->input('id'))
|
||||
&& User::find($request->input('id'))
|
||||
) {
|
||||
|
@ -33,21 +41,7 @@ function user_myshifts()
|
|||
}
|
||||
|
||||
$shifts_user = User::find($shift_entry_id);
|
||||
if ($request->has('reset')) {
|
||||
if ($request->input('reset') == 'ack') {
|
||||
auth()->resetApiKey($user);
|
||||
engelsystem_log(sprintf('API key resetted (%s).', User_Nick_render($user, true)));
|
||||
success(__('Key changed.'));
|
||||
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(url('/user-myshifts', ['reset' => 'ack']), __('Continue'), 'btn-danger'),
|
||||
]);
|
||||
} elseif ($request->has('edit') && preg_match('/^\d+$/', $request->input('edit'))) {
|
||||
if ($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)
|
||||
|
@ -61,7 +55,10 @@ function user_myshifts()
|
|||
|
||||
if ($request->hasPostData('submit')) {
|
||||
$valid = true;
|
||||
if (auth()->can('user_shifts_admin')) {
|
||||
if (
|
||||
auth()->can('user_shifts_admin')
|
||||
|| $is_angeltype_supporter
|
||||
) {
|
||||
$freeloaded = $request->has('freeloaded');
|
||||
$freeloaded_comment = strip_request_item_nl('freeloaded_comment');
|
||||
if ($freeloaded && $freeloaded_comment == '') {
|
||||
|
@ -91,6 +88,9 @@ function user_myshifts()
|
|||
. '. Freeloaded: ' . ($freeloaded ? 'YES Comment: ' . $freeloaded_comment : 'NO')
|
||||
);
|
||||
success(__('Shift saved.'));
|
||||
if ($is_angeltype_supporter) {
|
||||
throw_redirect(url('/shifts', ['action' => 'view', 'shift_id' => $shiftEntry->shift_id]));
|
||||
}
|
||||
throw_redirect(url('/users', ['action' => 'view', 'user_id' => $shifts_user->id]));
|
||||
}
|
||||
}
|
||||
|
@ -104,13 +104,13 @@ function user_myshifts()
|
|||
$shiftEntry->user_comment,
|
||||
$shiftEntry->freeloaded,
|
||||
$shiftEntry->freeloaded_comment,
|
||||
auth()->can('user_shifts_admin')
|
||||
auth()->can('user_shifts_admin'),
|
||||
$is_angeltype_supporter
|
||||
);
|
||||
} else {
|
||||
throw_redirect(url('/user-myshifts'));
|
||||
}
|
||||
}
|
||||
|
||||
throw_redirect(url('/users', ['action' => 'view', 'user_id' => $shifts_user->id]));
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use Illuminate\Support\Collection;
|
|||
*/
|
||||
function shifts_title()
|
||||
{
|
||||
return __('Shifts');
|
||||
return __('general.shifts');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -297,7 +297,6 @@ function view_user_shifts()
|
|||
|
||||
return page([
|
||||
div('col-md-12', [
|
||||
msg(),
|
||||
view(__DIR__ . '/../../resources/views/pages/user-shifts.html', [
|
||||
'title' => shifts_title(),
|
||||
'add_link' => auth()->can('admin_shifts') ? $link : '',
|
||||
|
@ -375,18 +374,11 @@ 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>).'),
|
||||
__('Export your own shifts formatted as <a href="%s" target="_blank">iCal</a> or <a href="%s" target="_blank">JSON</a> (please keep the link secret, otherwise you have to reset the api key <a href="%s">in your settings</a>).'),
|
||||
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"
|
||||
aria-expanded="false" aria-controls="collapseApiKey">
|
||||
' . __('Show API Key') . '
|
||||
</button>'
|
||||
. '</p>'
|
||||
. '<p id="collapseApiKey" class="collapse"><code>' . $user->api_key . '</code></p>';
|
||||
url('/settings/api')
|
||||
) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,6 +22,7 @@ function form_hidden($name, $value)
|
|||
* @param string $label
|
||||
* @param int $value
|
||||
* @param array $data_attributes
|
||||
* @param bool $isDisabled
|
||||
* @return string
|
||||
*/
|
||||
function form_spinner(string $name, string $label, int $value, array $data_attributes = [], bool $isDisabled = false)
|
||||
|
@ -141,11 +142,24 @@ function form_info($label, $text = '')
|
|||
* @param string $class
|
||||
* @param bool $wrapForm
|
||||
* @param string $buttonType
|
||||
* @param string $title
|
||||
* @param array $dataAttributes
|
||||
* @return string
|
||||
*/
|
||||
function form_submit($name, $label, $class = '', $wrapForm = true, $buttonType = 'primary', $title = '')
|
||||
{
|
||||
$button = '<button class="btn btn-' . $buttonType . ($class ? ' ' . $class : '') . '" type="submit" name="' . $name . '" title="' . $title . '">'
|
||||
function form_submit(
|
||||
$name,
|
||||
$label,
|
||||
$class = '',
|
||||
$wrapForm = true,
|
||||
$buttonType = 'primary',
|
||||
$title = '',
|
||||
array $dataAttributes = []
|
||||
) {
|
||||
$add = '';
|
||||
foreach ($dataAttributes as $dataType => $dataValue) {
|
||||
$add .= ' data-' . $dataType . '="' . htmlspecialchars($dataValue) . '"';
|
||||
}
|
||||
$button = '<button class="btn btn-' . $buttonType . ($class ? ' ' . $class : '') . '" type="submit" name="' . $name . '" title="' . $title . '"' . $add . '>'
|
||||
. $label
|
||||
. '</button>';
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ function header_render_hints()
|
|||
$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_goodie_hint(), true);
|
||||
$hints_renderer->addHint(render_user_dect_hint(), true);
|
||||
$hints_renderer->addHint(render_user_mobile_hint(), true);
|
||||
|
||||
|
@ -58,7 +58,7 @@ function make_navigation()
|
|||
$pages = [
|
||||
'news' => __('news.title'),
|
||||
'meetings' => [__('news.title.meetings'), 'user_meetings'],
|
||||
'user_shifts' => __('Shifts'),
|
||||
'user_shifts' => __('general.shifts'),
|
||||
'angeltypes' => __('angeltypes.angeltypes'),
|
||||
'questions' => [__('Ask the Heaven'), 'question.add'],
|
||||
];
|
||||
|
@ -85,12 +85,12 @@ function make_navigation()
|
|||
// path => name,
|
||||
// path => [name, permission],
|
||||
|
||||
'admin_arrive' => 'Arrive angels',
|
||||
'admin_arrive' => [admin_arrive_title(), 'users.arrive.list'],
|
||||
'admin_active' => 'Active angels',
|
||||
'users' => ['All Angels', 'admin_user'],
|
||||
'admin_free' => 'Free angels',
|
||||
'admin/questions' => ['Answer questions', 'question.edit'],
|
||||
'admin/shifttypes' => ['shifttype.shifttypes', 'shifttypes'],
|
||||
'admin/shifttypes' => ['shifttype.shifttypes', 'shifttypes.view'],
|
||||
'admin_shifts' => 'Create shifts',
|
||||
'admin/locations' => ['location.locations', 'admin_locations'],
|
||||
'admin_groups' => 'Grouprights',
|
||||
|
|
|
@ -116,7 +116,7 @@ function check_date($input, $error_message = null, $null_allowed = false, $time_
|
|||
} else {
|
||||
$time = Carbon::createFromFormat('Y-m-d', $trimmed_input);
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
} catch (InvalidArgumentException) {
|
||||
$time = null;
|
||||
}
|
||||
|
||||
|
@ -197,20 +197,3 @@ function strip_item($item)
|
|||
// Only allow letters, symbols, punctuation, separators and numbers without html tags
|
||||
return preg_replace('/([^\p{L}\p{S}\p{P}\p{Z}\p{N}+]+)/ui', '', strip_tags($item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an email address with support for IDN domain names.
|
||||
*
|
||||
* @param string $email
|
||||
* @return bool
|
||||
*/
|
||||
function check_email($email)
|
||||
{
|
||||
// Convert the domain part from idn to ascii
|
||||
if (substr_count($email, '@') == 1) {
|
||||
list($name, $domain) = explode('@', $email);
|
||||
$domain = idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
|
||||
$email = $name . '@' . $domain;
|
||||
}
|
||||
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Engelsystem\Models\User\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
|
@ -351,7 +352,7 @@ function render_table($columns, $rows, $data = true)
|
|||
* @param string $id
|
||||
* @return string
|
||||
*/
|
||||
function button($href, $label, $class = '', $id = '', $title = '')
|
||||
function button($href, $label, $class = '', $id = '', $title = '', $disabled = false)
|
||||
{
|
||||
if (!Str::contains(str_replace(['btn-sm', 'btn-xl'], '', $class), 'btn-')) {
|
||||
$class = 'btn-secondary' . ($class ? ' ' . $class : '');
|
||||
|
@ -360,7 +361,7 @@ function button($href, $label, $class = '', $id = '', $title = '')
|
|||
$idAttribute = $id ? 'id="' . $id . '"' : '';
|
||||
|
||||
return '<a ' . $idAttribute . ' href="' . $href
|
||||
. '" class="btn ' . $class . '" title="' . $title . '">' . $label . '</a>';
|
||||
. '" class="btn ' . $class . ($disabled ? ' disabled' : '') . '" title="' . $title . '">' . $label . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -386,9 +387,9 @@ function button_checkbox_selection($name, $label, $value)
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
function button_icon($href, $icon, $class = '', $title = '')
|
||||
function button_icon($href, $icon, $class = '', $title = '', $disabled = false)
|
||||
{
|
||||
return button($href, icon($icon), $class, '', $title);
|
||||
return button($href, icon($icon), $class, '', $title, $disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -421,3 +422,16 @@ function table_buttons($buttons = [], $additionalClass = '')
|
|||
{
|
||||
return '<div class="btn-group ' . $additionalClass . '" role="group">' . join('', $buttons) . '</div>';
|
||||
}
|
||||
|
||||
function user_info_icon(User $user): string
|
||||
{
|
||||
if (!auth()->can('admin_arrive') || !$user->state->user_info) {
|
||||
return '';
|
||||
}
|
||||
$infoIcon = ' <small><span class="bi bi-info-circle-fill text-info" ';
|
||||
if (auth()->can('user.info.show')) {
|
||||
$infoIcon .= 'data-bs-toggle="tooltip" title="' . htmlspecialchars($user->state->user_info) . '"';
|
||||
}
|
||||
$infoIcon .= '></span></small>';
|
||||
return $infoIcon;
|
||||
}
|
||||
|
|
|
@ -83,9 +83,39 @@ function AngelType_delete_view(AngelType $angeltype)
|
|||
*/
|
||||
function AngelType_edit_view(AngelType $angeltype, bool $supporter_mode)
|
||||
{
|
||||
$requires_ifsg = '';
|
||||
$requires_driving_license = '';
|
||||
if (config('ifsg_enabled')) {
|
||||
$requires_ifsg = $supporter_mode ?
|
||||
form_info(
|
||||
__('angeltype.ifsg.required'),
|
||||
$angeltype->requires_ifsg_certificate
|
||||
? __('Yes')
|
||||
: __('No')
|
||||
) : form_checkbox(
|
||||
'requires_ifsg_certificate',
|
||||
__('angeltype.ifsg.required'),
|
||||
$angeltype->requires_ifsg_certificate
|
||||
);
|
||||
}
|
||||
if (config('driving_license_enabled')) {
|
||||
$requires_driving_license = $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
|
||||
);
|
||||
}
|
||||
|
||||
$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 ?
|
||||
|
@ -120,30 +150,8 @@ function AngelType_edit_view(AngelType $angeltype, bool $supporter_mode)
|
|||
__('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
|
||||
),
|
||||
$requires_driving_license,
|
||||
$requires_ifsg,
|
||||
$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),
|
||||
|
@ -182,7 +190,7 @@ function AngelType_edit_view(AngelType $angeltype, bool $supporter_mode)
|
|||
* @param UserAngelType|null $user_angeltype
|
||||
* @param bool $admin_angeltypes
|
||||
* @param bool $supporter
|
||||
* @param License $user_driver_license
|
||||
* @param License $user_license
|
||||
* @param User|null $user
|
||||
* @return string
|
||||
*/
|
||||
|
@ -191,16 +199,24 @@ function AngelType_view_buttons(
|
|||
?UserAngelType $user_angeltype,
|
||||
$admin_angeltypes,
|
||||
$supporter,
|
||||
$user_driver_license,
|
||||
$user_license,
|
||||
$user
|
||||
) {
|
||||
if ($angeltype->requires_driver_license) {
|
||||
if (
|
||||
config('driving_license_enabled')
|
||||
&& $angeltype->requires_driver_license
|
||||
&& $user_angeltype
|
||||
) {
|
||||
$buttons[] = button(
|
||||
url('/settings/certificates'),
|
||||
icon('person-vcard') . __('my driving license')
|
||||
icon('person-vcard') . __('My driving license')
|
||||
);
|
||||
}
|
||||
if (config('isfg_enabled') && $angeltype->requires_ifsg_certificate) {
|
||||
if (
|
||||
config('ifsg_enabled')
|
||||
&& $angeltype->requires_ifsg_certificate
|
||||
&& $user_angeltype
|
||||
) {
|
||||
$buttons[] = button(
|
||||
url('/settings/certificates'),
|
||||
icon('card-checklist') . __('angeltype.ifsg.own')
|
||||
|
@ -216,7 +232,7 @@ function AngelType_view_buttons(
|
|||
($admin_angeltypes ? 'Join' : ''),
|
||||
);
|
||||
} else {
|
||||
if ($angeltype->requires_driver_license && !$user_driver_license->wantsToDrive()) {
|
||||
if (config('driving_license_enabled') && $angeltype->requires_driver_license && !$user_license->wantsToDrive()) {
|
||||
error(__('This angeltype requires a driver license. Please enter your driver license information!'));
|
||||
}
|
||||
|
||||
|
@ -265,6 +281,13 @@ function AngelType_view_buttons(
|
|||
return buttons($buttons);
|
||||
}
|
||||
|
||||
function certificateIcon($confirmed, $certificate)
|
||||
{
|
||||
return ($confirmed && $certificate)
|
||||
? icon('check2-all', 'text-success')
|
||||
: icon_bool($certificate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders and sorts the members of an angeltype into supporters, members and unconfirmed members.
|
||||
*
|
||||
|
@ -282,26 +305,52 @@ 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'] = htmlspecialchars((string) $member->contact->dect);
|
||||
$member['dect'] =
|
||||
sprintf('<a href="tel:%s">%1$s</a>', htmlspecialchars((string) $member->contact->dect));
|
||||
}
|
||||
if ($angeltype->requires_driver_license) {
|
||||
$member['wants_to_drive'] = icon_bool($member->license->wantsToDrive());
|
||||
if (config('driving_license_enabled') && $angeltype->requires_driver_license) {
|
||||
$drive_confirmed = $member->license->drive_confirmed;
|
||||
$member['wants_to_drive'] = certificateIcon($drive_confirmed, $member->license->wantsToDrive());
|
||||
$member['has_car'] = icon_bool($member->license->has_car);
|
||||
$member['has_license_car'] = icon_bool($member->license->drive_car);
|
||||
$member['has_license_3_5t_transporter'] = icon_bool($member->license->drive_3_5t);
|
||||
$member['has_license_7_5t_truck'] = icon_bool($member->license->drive_7_5t);
|
||||
$member['has_license_12t_truck'] = icon_bool($member->license->drive_12t);
|
||||
$member['has_license_forklift'] = icon_bool($member->license->drive_forklift);
|
||||
$member['has_license_car'] = certificateIcon($drive_confirmed, $member->license->drive_car);
|
||||
$member['has_license_3_5t_transporter'] = certificateIcon($drive_confirmed, $member->license->drive_3_5t);
|
||||
$member['has_license_7_5t_truck'] = certificateIcon($drive_confirmed, $member->license->drive_7_5t);
|
||||
$member['has_license_12t_truck'] = certificateIcon($drive_confirmed, $member->license->drive_12t);
|
||||
$member['has_license_forklift'] = certificateIcon($drive_confirmed, $member->license->drive_forklift);
|
||||
}
|
||||
if ($angeltype->requires_ifsg_certificate && config('ifsg_enabled')) {
|
||||
$member['ifsg_certificate'] = icon_bool($member->license->ifsg_certificate);
|
||||
if (config('ifsg_enabled') && $angeltype->requires_ifsg_certificate) {
|
||||
$ifsg_confirmed = $member->license->ifsg_confirmed;
|
||||
$member['ifsg_certificate'] = certificateIcon($ifsg_confirmed, $member->license->ifsg_certificate);
|
||||
if (config('ifsg_light_enabled')) {
|
||||
$member['ifsg_certificate_light'] = icon_bool($member->license->ifsg_certificate_light);
|
||||
$member['ifsg_certificate_light'] = certificateIcon($ifsg_confirmed, $member->license->ifsg_certificate_light);
|
||||
}
|
||||
}
|
||||
|
||||
$edit_certificates = '';
|
||||
if (
|
||||
(
|
||||
config('driving_license_enabled')
|
||||
&& $angeltype->requires_driver_license
|
||||
&& ($admin_user_angeltypes || auth()->can('user.drive.edit'))
|
||||
)
|
||||
|| (
|
||||
config('ifsg_enabled')
|
||||
&& $angeltype->requires_ifsg_certificate
|
||||
&& ($admin_user_angeltypes || auth()->can('user.ifsg.edit'))
|
||||
)
|
||||
) {
|
||||
$edit_certificates =
|
||||
button(
|
||||
url('/users/' . $member->id . '/certificates'),
|
||||
icon('card-checklist'),
|
||||
'btn-sm',
|
||||
'',
|
||||
__('Edit certificates'),
|
||||
);
|
||||
}
|
||||
if ($angeltype->restricted && empty($member->pivot->confirm_user_id)) {
|
||||
$member['actions'] = table_buttons([
|
||||
$edit_certificates,
|
||||
button(
|
||||
url(
|
||||
'/user-angeltypes',
|
||||
|
@ -321,8 +370,9 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
|
|||
]);
|
||||
$members_unconfirmed[] = $member;
|
||||
} elseif ($member->pivot->supporter) {
|
||||
if ($admin_angeltypes) {
|
||||
if ($admin_angeltypes || ($admin_user_angeltypes && config('supporters_can_promote'))) {
|
||||
$member['actions'] = table_buttons([
|
||||
$edit_certificates,
|
||||
button(
|
||||
url('/user-angeltypes', [
|
||||
'action' => 'update',
|
||||
|
@ -336,13 +386,16 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
|
|||
),
|
||||
]);
|
||||
} else {
|
||||
$member['actions'] = '';
|
||||
$member['actions'] = $edit_certificates
|
||||
? table_buttons([$edit_certificates,])
|
||||
: '';
|
||||
}
|
||||
$supporters[] = $member;
|
||||
} else {
|
||||
if ($admin_user_angeltypes) {
|
||||
$member['actions'] = table_buttons([
|
||||
$admin_angeltypes ?
|
||||
$edit_certificates,
|
||||
($admin_angeltypes || config('supporters_can_promote')) ?
|
||||
button(
|
||||
url('/user-angeltypes', [
|
||||
'action' => 'update',
|
||||
|
@ -366,6 +419,10 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
|
|||
__('Remove'),
|
||||
),
|
||||
]);
|
||||
} elseif ($edit_certificates) {
|
||||
$member['actions'] = table_buttons([
|
||||
$edit_certificates,
|
||||
]);
|
||||
}
|
||||
$members_confirmed[] = $member;
|
||||
}
|
||||
|
@ -396,7 +453,10 @@ function AngelType_view_table_headers(AngelType $angeltype, $supporter, $admin_a
|
|||
$headers['dect'] = __('general.dect');
|
||||
}
|
||||
|
||||
if ($angeltype->requires_driver_license && ($supporter || $admin_angeltypes)) {
|
||||
if (
|
||||
config('driving_license_enabled') && $angeltype->requires_driver_license
|
||||
&& ($supporter || $admin_angeltypes || auth()->can('user.drive.edit'))
|
||||
) {
|
||||
$headers = array_merge($headers, [
|
||||
'wants_to_drive' => __('Driver'),
|
||||
'has_car' => __('Has car'),
|
||||
|
@ -408,7 +468,10 @@ function AngelType_view_table_headers(AngelType $angeltype, $supporter, $admin_a
|
|||
]);
|
||||
}
|
||||
|
||||
if (config('ifsg_enabled') && $angeltype->requires_ifsg_certificate && ($supporter || $admin_angeltypes)) {
|
||||
if (
|
||||
config('ifsg_enabled') && $angeltype->requires_ifsg_certificate
|
||||
&& ($supporter || $admin_angeltypes || auth()->can('user.ifsg.edit'))
|
||||
) {
|
||||
if (config('ifsg_light_enabled')) {
|
||||
$headers['ifsg_certificate_light'] = __('ifsg.certificate_light');
|
||||
}
|
||||
|
@ -429,7 +492,7 @@ function AngelType_view_table_headers(AngelType $angeltype, $supporter, $admin_a
|
|||
* @param bool $admin_user_angeltypes
|
||||
* @param bool $admin_angeltypes
|
||||
* @param bool $supporter
|
||||
* @param License $user_driver_license
|
||||
* @param License $user_license
|
||||
* @param User $user
|
||||
* @param ShiftsFilterRenderer $shiftsFilterRenderer
|
||||
* @param ShiftCalendarRenderer $shiftCalendarRenderer
|
||||
|
@ -443,7 +506,7 @@ function AngelType_view(
|
|||
$admin_user_angeltypes,
|
||||
$admin_angeltypes,
|
||||
$supporter,
|
||||
$user_driver_license,
|
||||
$user_license,
|
||||
$user,
|
||||
ShiftsFilterRenderer $shiftsFilterRenderer,
|
||||
ShiftCalendarRenderer $shiftCalendarRenderer,
|
||||
|
@ -453,7 +516,7 @@ function AngelType_view(
|
|||
return page_with_title(
|
||||
$link . ' ' . sprintf(__('Team %s'), htmlspecialchars($angeltype->name)),
|
||||
[
|
||||
AngelType_view_buttons($angeltype, $user_angeltype, $admin_angeltypes, $supporter, $user_driver_license, $user),
|
||||
AngelType_view_buttons($angeltype, $user_angeltype, $admin_angeltypes, $supporter, $user_license, $user),
|
||||
msg(),
|
||||
tabs([
|
||||
__('Info') => AngelType_view_info(
|
||||
|
@ -463,7 +526,7 @@ function AngelType_view(
|
|||
$admin_angeltypes,
|
||||
$supporter
|
||||
),
|
||||
__('Shifts') => AngelType_view_shifts(
|
||||
__('general.shifts') => AngelType_view_shifts(
|
||||
$angeltype,
|
||||
$shiftsFilterRenderer,
|
||||
$shiftCalendarRenderer
|
||||
|
@ -506,6 +569,13 @@ function AngelType_view_info(
|
|||
$admin_angeltypes,
|
||||
$supporter
|
||||
) {
|
||||
$required_info_show = !auth()->user()
|
||||
->userAngelTypes()
|
||||
->where('angel_types.id', $angeltype->id)
|
||||
->count()
|
||||
&& !$admin_angeltypes
|
||||
&& !$admin_user_angeltypes
|
||||
&& !$supporter;
|
||||
$info = [];
|
||||
if ($angeltype->hasContactInfo()) {
|
||||
$info[] = AngelTypes_render_contact_info($angeltype);
|
||||
|
@ -516,6 +586,12 @@ function AngelType_view_info(
|
|||
if ($angeltype->description != '') {
|
||||
$info[] = $parsedown->parse(htmlspecialchars($angeltype->description));
|
||||
}
|
||||
if ($angeltype->requires_ifsg_certificate && $required_info_show) {
|
||||
$info[] = info(__('angeltype.ifsg.required.info.preview'), true);
|
||||
}
|
||||
if ($angeltype->requires_driver_license && $required_info_show) {
|
||||
$info[] = info(__('angeltype.driving_license.required.info.preview'), true);
|
||||
}
|
||||
|
||||
list($supporters, $members_confirmed, $members_unconfirmed) = AngelType_view_members(
|
||||
$angeltype,
|
||||
|
|
|
@ -27,6 +27,22 @@ function location_view(Location $location, ShiftsFilterRenderer $shiftsFilterRen
|
|||
$description .= $parsedown->parse(htmlspecialchars($location->description));
|
||||
}
|
||||
|
||||
$neededAngelTypes = '';
|
||||
if (auth()->can('admin_shifts') && $location->neededAngelTypes->isNotEmpty()) {
|
||||
$neededAngelTypes .= '<h3>' . __('location.required_angels') . '</h3><ul>';
|
||||
foreach ($location->neededAngelTypes as $neededAngelType) {
|
||||
if ($neededAngelType->count) {
|
||||
$neededAngelTypes .= '<li><a href="'
|
||||
. url('angeltypes', ['action' => 'view', 'angeltype_id' => $neededAngelType->angelType->id])
|
||||
. '">' . $neededAngelType->angelType->name
|
||||
. '</a>: '
|
||||
. $neededAngelType->count
|
||||
. '</li>';
|
||||
}
|
||||
}
|
||||
$neededAngelTypes .= '</ul>';
|
||||
}
|
||||
|
||||
$dect = '';
|
||||
if (config('enable_dect') && $location->dect) {
|
||||
$dect = heading(__('Contact'), 3)
|
||||
|
@ -40,13 +56,13 @@ function location_view(Location $location, ShiftsFilterRenderer $shiftsFilterRen
|
|||
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>'
|
||||
. '<iframe style="width: 100%%; min-height: 75vh; border: 0 none;" src="%s"></iframe>'
|
||||
. '</div>',
|
||||
htmlspecialchars($location->map_url)
|
||||
);
|
||||
}
|
||||
|
||||
$tabs[__('Shifts')] = div('first', [
|
||||
$tabs[__('general.shifts')] = div('first', [
|
||||
$shiftsFilterRenderer->render(url('/locations', [
|
||||
'action' => 'view',
|
||||
'location_id' => $location->id,
|
||||
|
@ -77,6 +93,7 @@ function location_view(Location $location, ShiftsFilterRenderer $shiftsFilterRen
|
|||
]) : '',
|
||||
$dect,
|
||||
$description,
|
||||
$neededAngelTypes,
|
||||
tabs($tabs, $selected_tab),
|
||||
],
|
||||
true
|
||||
|
|
|
@ -215,6 +215,12 @@ class ShiftCalendarRenderer
|
|||
{
|
||||
$time = Carbon::createFromTimestamp($time);
|
||||
$class = $label ? 'tick bg-' . theme_type() : 'tick ';
|
||||
|
||||
$diffNow = $time->diffInMinutes(null, false) * 60;
|
||||
if ($diffNow >= 0 && $diffNow < self::SECONDS_PER_ROW) {
|
||||
$class .= ' now';
|
||||
}
|
||||
|
||||
if ($time->isStartOfDay()) {
|
||||
if (!$label) {
|
||||
return div($class . ' day');
|
||||
|
@ -296,7 +302,7 @@ class ShiftCalendarRenderer
|
|||
*/
|
||||
private function calcBlocksPerSlot()
|
||||
{
|
||||
return ceil(
|
||||
return (int) ceil(
|
||||
($this->getLastBlockEndTime() - $this->getFirstBlockStartTime())
|
||||
/ ShiftCalendarRenderer::SECONDS_PER_ROW
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Engelsystem;
|
||||
|
||||
use Engelsystem\Config\GoodieType;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Shifts\Shift;
|
||||
use Engelsystem\Models\Shifts\ShiftEntry;
|
||||
|
@ -169,6 +170,15 @@ class ShiftCalendarShiftRenderer
|
|||
$angeltype,
|
||||
$shift_entries
|
||||
);
|
||||
$shift_can_signup = Shift_signup_allowed_angel(
|
||||
$user,
|
||||
$shift,
|
||||
$angeltype,
|
||||
null,
|
||||
null,
|
||||
$angeltype,
|
||||
$shift_entries
|
||||
);
|
||||
$freeEntriesCount = $shift_signup_state->getFreeEntries();
|
||||
$inner_text = _e('%d helper needed', '%d helpers needed', $freeEntriesCount, [$freeEntriesCount]);
|
||||
|
||||
|
@ -215,7 +225,7 @@ class ShiftCalendarShiftRenderer
|
|||
$shifts_row .= join(', ', $entry_list);
|
||||
$shifts_row .= '</li>';
|
||||
return [
|
||||
$shift_signup_state,
|
||||
$shift_can_signup,
|
||||
$shifts_row,
|
||||
];
|
||||
}
|
||||
|
@ -243,9 +253,12 @@ class ShiftCalendarShiftRenderer
|
|||
*/
|
||||
private function renderShiftHead(Shift $shift, $class, $needed_angeltypes_count)
|
||||
{
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
|
||||
$header_buttons = '';
|
||||
if (auth()->can('admin_shifts')) {
|
||||
$header_buttons = '<div class="ms-auto d-print-none">' . table_buttons([
|
||||
$header_buttons = div('ms-auto d-print-none d-flex', [
|
||||
button(
|
||||
url('/user-shifts', ['edit_shift' => $shift->id]),
|
||||
icon('pencil'),
|
||||
|
@ -253,18 +266,38 @@ class ShiftCalendarShiftRenderer
|
|||
'',
|
||||
__('form.edit')
|
||||
),
|
||||
button(
|
||||
url('/user-shifts', ['delete_shift' => $shift->id]),
|
||||
form([
|
||||
form_hidden('delete_shift', $shift->id),
|
||||
form_submit(
|
||||
'delete',
|
||||
icon('trash'),
|
||||
'btn-' . $class . ' btn-sm border-light text-white',
|
||||
'',
|
||||
__('form.delete')
|
||||
'btn-' . $class . ' btn-sm border-light text-white ms-1',
|
||||
false,
|
||||
'danger',
|
||||
__('form.delete'),
|
||||
[
|
||||
'confirm_submit_title' => __('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')),
|
||||
]),
|
||||
'confirm_button_text' => icon('trash') . __('form.delete'),
|
||||
]
|
||||
),
|
||||
]) . '</div>';
|
||||
], url('/user-shifts', ['delete_shift' => $shift->id])),
|
||||
]);
|
||||
}
|
||||
$shift_heading = $shift->start->format('H:i') . ' ‐ '
|
||||
$night_shift = '';
|
||||
if ($shift->isNightShift() && $goodie_enabled) {
|
||||
$night_shift = ' <i class="bi-moon-stars"></i>';
|
||||
}
|
||||
|
||||
$shift_heading = '<span>'
|
||||
. $shift->start->format('H:i') . ' ‐ '
|
||||
. $shift->end->format('H:i') . ' — '
|
||||
. htmlspecialchars($shift->shiftType->name);
|
||||
. htmlspecialchars($shift->shiftType->name)
|
||||
. $night_shift
|
||||
. '</span>';
|
||||
|
||||
if ($needed_angeltypes_count > 0) {
|
||||
$shift_heading = '<span class="badge bg-light text-danger me-1">' . $needed_angeltypes_count . '</span> ' . $shift_heading;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Engelsystem\Config\GoodieType;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Location;
|
||||
use Engelsystem\Models\Shifts\Shift;
|
||||
|
@ -205,15 +206,29 @@ function ShiftEntry_edit_view(
|
|||
$comment,
|
||||
$freeloaded,
|
||||
$freeloaded_comment,
|
||||
$user_admin_shifts = false
|
||||
$user_admin_shifts = false,
|
||||
$angeltype_supporter = false
|
||||
) {
|
||||
$freeload_form = [];
|
||||
if ($user_admin_shifts) {
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
$goodie_tshirt = $goodie === GoodieType::Tshirt;
|
||||
|
||||
if ($user_admin_shifts || $angeltype_supporter) {
|
||||
if (!$goodie_enabled) {
|
||||
$freeload_info = __('freeload.freeloaded.info', [config('max_freeloadable_shifts')]);
|
||||
} else {
|
||||
$freeload_info = __('freeload.freeloaded.info.goodie', [($goodie_tshirt
|
||||
? __('T-shirt score')
|
||||
: __('Goodie score')),
|
||||
config('max_freeloadable_shifts')]);
|
||||
}
|
||||
$freeload_form = [
|
||||
form_checkbox('freeloaded', __('Freeloaded'), $freeloaded),
|
||||
form_checkbox('freeloaded', __('Freeloaded') . ' <span class="bi bi-info-circle-fill text-info" data-bs-toggle="tooltip" title="' .
|
||||
$freeload_info . '"></span>', $freeloaded),
|
||||
form_textarea(
|
||||
'freeloaded_comment',
|
||||
__('Freeload comment (Only for shift coordination):'),
|
||||
__('Freeload comment (Only for shift coordination and supporters):'),
|
||||
$freeloaded_comment
|
||||
),
|
||||
];
|
||||
|
|
|
@ -87,14 +87,4 @@ class ShiftsFilterRenderer
|
|||
$this->daySelectionEnabled = true;
|
||||
$this->days = $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the filter display a day selection.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDaySelectionEnabled()
|
||||
{
|
||||
return $this->daySelectionEnabled;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Engelsystem\Config\GoodieType;
|
||||
use Engelsystem\Helpers\Carbon;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Location;
|
||||
use Engelsystem\Models\Shifts\Shift;
|
||||
|
@ -72,6 +74,35 @@ function Shift_editor_info_render(Shift $shift)
|
|||
User_Nick_render($shift->updatedBy)
|
||||
);
|
||||
}
|
||||
if ($shift->transaction_id) {
|
||||
$info[] = sprintf(
|
||||
icon('clock-history') . __('History ID: %s'),
|
||||
$shift->transaction_id
|
||||
);
|
||||
}
|
||||
if ($shift->schedule) {
|
||||
$angeltypeSource = $shift->schedule->needed_from_shift_type
|
||||
? __(
|
||||
'shift.angeltype_source.shift_type',
|
||||
[
|
||||
'<a href="' . url('/admin/schedule/edit/' . $shift->schedule->id) . '">'
|
||||
. htmlspecialchars($shift->schedule->name)
|
||||
. '</a>',
|
||||
'<a href="' . url('/admin/shifttypes/' . $shift->shift_type_id) . '">'
|
||||
. htmlspecialchars($shift->shiftType->name)
|
||||
. '</a>',
|
||||
]
|
||||
)
|
||||
: __('shift.angeltype_source.location', [
|
||||
'<a href="' . url('/admin/schedule/edit/' . $shift->schedule->id) . '">'
|
||||
. htmlspecialchars($shift->schedule->name)
|
||||
. '</a>',
|
||||
location_name_render($shift->location),
|
||||
]);
|
||||
} else {
|
||||
$angeltypeSource = __('Shift');
|
||||
}
|
||||
$info[] = sprintf(__('shift.angeltype_source'), $angeltypeSource);
|
||||
return join('<br />', $info);
|
||||
}
|
||||
|
||||
|
@ -124,7 +155,11 @@ function Shift_view(
|
|||
$shift_admin = auth()->can('admin_shifts');
|
||||
$user_shift_admin = auth()->can('user_shifts_admin');
|
||||
$admin_locations = auth()->can('admin_locations');
|
||||
$admin_shifttypes = auth()->can('shifttypes');
|
||||
$admin_shifttypes = auth()->can('shifttypes.view');
|
||||
$nightShiftsConfig = config('night_shifts');
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
$goodie_tshirt = $goodie === GoodieType::Tshirt;
|
||||
|
||||
$parsedown = new Parsedown();
|
||||
|
||||
|
@ -160,7 +195,10 @@ function Shift_view(
|
|||
}
|
||||
|
||||
if ($shift_signup_state->getState() === ShiftSignupStatus::SIGNED_UP) {
|
||||
$content[] = info(__('You are signed up for this shift.'), true);
|
||||
$content[] = info(__('You are signed up for this shift.')
|
||||
. (($shift->start->subHours(config('last_unsubscribe')) < Carbon::now() && $shift->end > Carbon::now())
|
||||
? ' ' . __('shift.sign_out.hint', [config('last_unsubscribe')])
|
||||
: ''), true);
|
||||
}
|
||||
|
||||
if (config('signup_advance_hours') && $shift->start->timestamp > time() + config('signup_advance_hours') * 3600) {
|
||||
|
@ -174,7 +212,25 @@ function Shift_view(
|
|||
if ($shift_admin || $admin_shifttypes || $admin_locations) {
|
||||
$buttons = [
|
||||
$shift_admin ? button(shift_edit_link($shift), icon('pencil'), '', '', __('form.edit')) : '',
|
||||
$shift_admin ? button(shift_delete_link($shift), icon('trash'), 'btn-danger', '', __('form.delete')) : '',
|
||||
$shift_admin ? form([
|
||||
form_hidden('delete_shift', $shift->id),
|
||||
form_submit(
|
||||
'delete',
|
||||
icon('trash'),
|
||||
'',
|
||||
false,
|
||||
'danger',
|
||||
__('form.delete'),
|
||||
[
|
||||
'confirm_submit_title' => __('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')),
|
||||
]),
|
||||
'confirm_button_text' => icon('trash') . __('form.delete'),
|
||||
]
|
||||
),
|
||||
], url('/user-shifts', ['delete_shift' => $shift->id])) : '',
|
||||
$admin_shifttypes
|
||||
? button(url('/admin/shifttypes/' . $shifttype->id), htmlspecialchars($shifttype->name))
|
||||
: '',
|
||||
|
@ -211,11 +267,22 @@ function Shift_view(
|
|||
|
||||
$start = $shift->start->format(__('general.datetime'));
|
||||
|
||||
$night_shift_hint = '';
|
||||
if ($shift->isNightShift() && $goodie_enabled) {
|
||||
$night_shift_hint = ' <small><span class="bi bi-moon-stars text-info" data-bs-toggle="tooltip" title="'
|
||||
. __('Night shifts between %d and %d am are multiplied by %d for the %s score.', [
|
||||
$nightShiftsConfig['start'],
|
||||
$nightShiftsConfig['end'],
|
||||
$nightShiftsConfig['multiplier'],
|
||||
($goodie_tshirt ? __('T-shirt') : __('goodie'))])
|
||||
. '"></span></small>';
|
||||
}
|
||||
$link = button(url('/user-shifts'), icon('chevron-left'), 'btn-sm', '', __('general.back'));
|
||||
return page_with_title(
|
||||
$link . ' '
|
||||
. htmlspecialchars($shift->shiftType->name)
|
||||
. ' <small title="' . $start . '" data-countdown-ts="' . $shift->start->timestamp . '">%c</small>',
|
||||
. ' <small title="' . $start . '" data-countdown-ts="' . $shift->start->timestamp . '">%c</small>'
|
||||
. $night_shift_hint,
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
@ -286,17 +353,21 @@ function Shift_view_render_shift_entry(ShiftEntry $shift_entry, $user_shift_admi
|
|||
$isUser = $shift_entry->user_id == auth()->user()->id;
|
||||
if ($user_shift_admin || $angeltype_supporter || $isUser) {
|
||||
$entry .= ' <div class="btn-group m-1">';
|
||||
if ($user_shift_admin || $isUser) {
|
||||
$entry .= button_icon(
|
||||
url('/user-myshifts', ['edit' => $shift_entry->id, 'id' => $shift_entry->user_id]),
|
||||
'pencil',
|
||||
'btn-sm',
|
||||
__('form.edit')
|
||||
);
|
||||
}
|
||||
$angeltype = $shift_entry->angelType;
|
||||
$disabled = Shift_signout_allowed($shift, $angeltype, $shift_entry->user_id) ? '' : ' btn-disabled';
|
||||
$entry .= button_icon(shift_entry_delete_link($shift_entry), 'trash', 'btn-sm btn-danger' . $disabled, __('form.delete'));
|
||||
$entry .= button_icon(
|
||||
shift_entry_delete_link($shift_entry),
|
||||
'trash',
|
||||
'btn-sm btn-danger' . $disabled,
|
||||
__('form.delete'),
|
||||
!Shift_signout_allowed($shift, $angeltype, $shift_entry->user_id)
|
||||
);
|
||||
$entry .= '</div>';
|
||||
}
|
||||
return $entry;
|
||||
|
|
|
@ -108,14 +108,15 @@ function UserAngelType_confirm_view(UserAngelType $user_angeltype, User $user, A
|
|||
* @param UserAngelType $user_angeltype
|
||||
* @param User $user
|
||||
* @param AngelType $angeltype
|
||||
* @param bool $isOwnAngelType
|
||||
* @return string
|
||||
*/
|
||||
function UserAngelType_delete_view(UserAngelType $user_angeltype, User $user, AngelType $angeltype)
|
||||
function UserAngelType_delete_view(UserAngelType $user_angeltype, User $user, AngelType $angeltype, bool $isOwnAngelType)
|
||||
{
|
||||
return page_with_title(__('Remove angeltype'), [
|
||||
return page_with_title(__('Leave angeltype'), [
|
||||
msg(),
|
||||
info(sprintf(
|
||||
__('Do you really want to delete %s from %s?'),
|
||||
$isOwnAngelType ? __('Do you really want to leave "%2$s"?') : __('Do you really want to remove "%s" from "%s"?'),
|
||||
$user->displayName,
|
||||
$angeltype->name
|
||||
), true),
|
||||
|
@ -151,7 +152,9 @@ function UserAngelType_add_view(AngelType $angeltype, $users_source, $user_id)
|
|||
msg(),
|
||||
form([
|
||||
form_info(__('Angeltype'), htmlspecialchars($angeltype->name)),
|
||||
form_checkbox('auto_confirm_user', __('Confirm user'), true),
|
||||
$angeltype->restricted
|
||||
? form_checkbox('auto_confirm_user', __('Confirm user'), true)
|
||||
: '',
|
||||
form_select('user_id', __('general.user'), $users, $user_id),
|
||||
form_submit('submit', icon('plus-lg') . __('Add')),
|
||||
]),
|
||||
|
@ -165,10 +168,11 @@ function UserAngelType_add_view(AngelType $angeltype, $users_source, $user_id)
|
|||
*/
|
||||
function UserAngelType_join_view($user, AngelType $angeltype)
|
||||
{
|
||||
$isOther = $user->id != auth()->user()->id;
|
||||
return page_with_title(sprintf(__('Become a %s'), htmlspecialchars($angeltype->name)), [
|
||||
msg(),
|
||||
info(sprintf(
|
||||
__('Do you really want to add %s to %s?'),
|
||||
$isOther ? __('Do you really want to add %s to %s?') : __('Do you want to become a %2$s?'),
|
||||
$user->displayName,
|
||||
$angeltype->name
|
||||
), true),
|
||||
|
|
|
@ -6,6 +6,7 @@ use Engelsystem\Models\AngelType;
|
|||
use Engelsystem\Models\Group;
|
||||
use Engelsystem\Models\Shifts\Shift;
|
||||
use Engelsystem\Models\Shifts\ShiftEntry;
|
||||
use Engelsystem\Models\User\PasswordReset;
|
||||
use Engelsystem\Models\User\User;
|
||||
use Engelsystem\Models\Worklog;
|
||||
use Illuminate\Support\Collection;
|
||||
|
@ -47,7 +48,7 @@ function User_edit_vouchers_view($user)
|
|||
[
|
||||
msg(),
|
||||
info(sprintf(
|
||||
$user->state->force_active
|
||||
$user->state->force_active && config('enable_force_active')
|
||||
? __('Angel can receive another %d vouchers and is FA.')
|
||||
: __('Angel can receive another %d vouchers.'),
|
||||
User_get_eligable_voucher_count($user)
|
||||
|
@ -70,7 +71,7 @@ function User_edit_vouchers_view($user)
|
|||
* @param int $active_count
|
||||
* @param int $force_active_count
|
||||
* @param int $freeloads_count
|
||||
* @param int $tshirts_count
|
||||
* @param int $goodies_count
|
||||
* @param int $voucher_count
|
||||
* @return string
|
||||
*/
|
||||
|
@ -81,7 +82,7 @@ function Users_view(
|
|||
$active_count,
|
||||
$force_active_count,
|
||||
$freeloads_count,
|
||||
$tshirts_count,
|
||||
$goodies_count,
|
||||
$voucher_count
|
||||
) {
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
|
@ -92,9 +93,7 @@ function Users_view(
|
|||
$u = [];
|
||||
$u['name'] = User_Nick_render($user)
|
||||
. User_Pronoun_render($user)
|
||||
. ($user->state->user_info
|
||||
? ' <small><span class="bi bi-info-circle-fill text-info"></span></small>'
|
||||
: '');
|
||||
. user_info_icon($user);
|
||||
$u['first_name'] = htmlspecialchars((string) $user->personalData->first_name);
|
||||
$u['last_name'] = htmlspecialchars((string) $user->personalData->last_name);
|
||||
$u['dect'] = sprintf('<a href="tel:%s">%1$s</a>', htmlspecialchars((string) $user->contact->dect));
|
||||
|
@ -106,7 +105,7 @@ function Users_view(
|
|||
$u['active'] = icon_bool($user->state->active);
|
||||
$u['force_active'] = icon_bool($user->state->force_active);
|
||||
if ($goodie_enabled) {
|
||||
$u['got_shirt'] = icon_bool($user->state->got_shirt);
|
||||
$u['got_goodie'] = icon_bool($user->state->got_goodie);
|
||||
if ($goodie_tshirt) {
|
||||
$u['shirt_size'] = $user->personalData->shirt_size;
|
||||
}
|
||||
|
@ -122,8 +121,9 @@ function Users_view(
|
|||
'/admin-user',
|
||||
['id' => $user->id]
|
||||
),
|
||||
'pencil',
|
||||
icon('pencil'),
|
||||
'btn-sm',
|
||||
'',
|
||||
__('form.edit')
|
||||
),
|
||||
]);
|
||||
|
@ -136,7 +136,7 @@ function Users_view(
|
|||
'active' => $active_count,
|
||||
'force_active' => $force_active_count,
|
||||
'freeloads' => $freeloads_count,
|
||||
'got_shirt' => $tshirts_count,
|
||||
'got_goodie' => $goodies_count,
|
||||
'actions' => '<strong>' . count($usersList) . '</strong>',
|
||||
];
|
||||
|
||||
|
@ -158,13 +158,15 @@ function Users_view(
|
|||
}
|
||||
$user_table_headers['freeloads'] = Users_table_header_link('freeloads', __('Freeloads'), $order_by);
|
||||
$user_table_headers['active'] = Users_table_header_link('active', __('user.active'), $order_by);
|
||||
if (config('enable_force_active')) {
|
||||
$user_table_headers['force_active'] = Users_table_header_link('force_active', __('Forced'), $order_by);
|
||||
}
|
||||
if ($goodie_enabled) {
|
||||
if ($goodie_tshirt) {
|
||||
$user_table_headers['got_shirt'] = Users_table_header_link('got_shirt', __('T-Shirt'), $order_by);
|
||||
$user_table_headers['got_goodie'] = Users_table_header_link('got_goodie', __('T-Shirt'), $order_by);
|
||||
$user_table_headers['shirt_size'] = Users_table_header_link('shirt_size', __('Size'), $order_by);
|
||||
} else {
|
||||
$user_table_headers['got_shirt'] = Users_table_header_link('got_shirt', __('Goodie'), $order_by);
|
||||
$user_table_headers['got_goodie'] = Users_table_header_link('got_goodie', __('Goodie'), $order_by);
|
||||
}
|
||||
}
|
||||
$user_table_headers['arrival_date'] = Users_table_header_link(
|
||||
|
@ -308,6 +310,12 @@ function User_view_shiftentries($needed_angel_type)
|
|||
*/
|
||||
function User_view_myshift(Shift $shift, $user_source, $its_me)
|
||||
{
|
||||
$nightShiftsConfig = config('night_shifts');
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
$goodie_tshirt = $goodie === GoodieType::Tshirt;
|
||||
$supporter = auth()->user()->isAngelTypeSupporter(AngelType::findOrFail($shift->angel_type_id));
|
||||
|
||||
$shift_info = '<a href="' . shift_link($shift) . '">' . htmlspecialchars($shift->shiftType->name) . '</a>';
|
||||
if ($shift->title) {
|
||||
$shift_info .= '<br /><a href="' . shift_link($shift) . '">' . htmlspecialchars($shift->title) . '</a>';
|
||||
|
@ -316,6 +324,17 @@ function User_view_myshift(Shift $shift, $user_source, $its_me)
|
|||
$shift_info .= User_view_shiftentries($needed_angel_type);
|
||||
}
|
||||
|
||||
$night_shift = '';
|
||||
if ($shift->isNightShift() && $goodie_enabled) {
|
||||
$night_shift = ' <span class="bi bi-moon-stars text-info" data-bs-toggle="tooltip" title="'
|
||||
. __('Night shifts between %d and %d am are multiplied by %d for the %s score.', [
|
||||
$nightShiftsConfig['start'],
|
||||
$nightShiftsConfig['end'],
|
||||
$nightShiftsConfig['multiplier'],
|
||||
($goodie_tshirt ? __('T-shirt') : __('goodie')),
|
||||
])
|
||||
. '"></span>';
|
||||
}
|
||||
$myshift = [
|
||||
'date' => icon('calendar-event')
|
||||
. $shift->start->format(__('general.date')) . '<br>'
|
||||
|
@ -323,6 +342,7 @@ function User_view_myshift(Shift $shift, $user_source, $its_me)
|
|||
. ' - '
|
||||
. $shift->end->format(__('H:i')),
|
||||
'duration' => sprintf('%.2f', ($shift->end->timestamp - $shift->start->timestamp) / 3600) . ' h',
|
||||
'hints' => $night_shift,
|
||||
'location' => location_name_render($shift->location),
|
||||
'shift_info' => $shift_info,
|
||||
'comment' => '',
|
||||
|
@ -336,23 +356,35 @@ function User_view_myshift(Shift $shift, $user_source, $its_me)
|
|||
}
|
||||
|
||||
if ($shift->freeloaded) {
|
||||
$myshift['duration'] = '<p class="text-danger">'
|
||||
. sprintf('%.2f', -($shift->end->timestamp - $shift->start->timestamp) / 3600 * 2) . ' h'
|
||||
. '</p>';
|
||||
if (auth()->can('user_shifts_admin')) {
|
||||
$myshift['duration'] = '<p class="text-danger"><s>'
|
||||
. sprintf('%.2f', ($shift->end->timestamp - $shift->start->timestamp) / 3600) . ' h'
|
||||
. '</s></p>';
|
||||
if (auth()->can('user_shifts_admin') || $supporter) {
|
||||
$myshift['comment'] .= '<br />'
|
||||
. '<p class="text-danger">'
|
||||
. __('Freeloaded') . ': ' . htmlspecialchars($shift->freeloaded_comment)
|
||||
. '</p>';
|
||||
} else {
|
||||
$myshift['comment'] .= '<br /><p class="text-danger">' . __('Freeloaded') . '</p>';
|
||||
$myshift['comment'] .= '<br /><p class="text-danger">'
|
||||
. __('Freeloaded')
|
||||
. '</p>';
|
||||
}
|
||||
if (!$goodie_enabled) {
|
||||
$freeload_info = __('freeload.info');
|
||||
} else {
|
||||
$freeload_info = __('freeload.info.goodie', [($goodie_tshirt
|
||||
? __('T-shirt score')
|
||||
: __('Goodie score'))]);
|
||||
}
|
||||
$myshift['hints'] .= ' <span class="bi bi-info-circle-fill text-danger" data-bs-toggle="tooltip" title="'
|
||||
. $freeload_info
|
||||
. '"></span>';
|
||||
}
|
||||
|
||||
$myshift['actions'] = [
|
||||
button(shift_link($shift), icon('eye'), 'btn-sm btn-info', '', __('View')),
|
||||
];
|
||||
if ($its_me || auth()->can('user_shifts_admin')) {
|
||||
if ($its_me || auth()->can('user_shifts_admin') || $supporter) {
|
||||
$myshift['actions'][] = button(
|
||||
url('/user-myshifts', ['edit' => $shift->shift_entry_id, 'id' => $user_source->id]),
|
||||
icon('pencil'),
|
||||
|
@ -382,8 +414,8 @@ function User_view_myshift(Shift $shift, $user_source, $its_me)
|
|||
* @param Shift[]|Collection $shifts
|
||||
* @param User $user_source
|
||||
* @param bool $its_me
|
||||
* @param int $tshirt_score
|
||||
* @param bool $tshirt_admin
|
||||
* @param string $goodie_score
|
||||
* @param bool $goodie_admin
|
||||
* @param Worklog[]|Collection $user_worklogs
|
||||
* @param bool $admin_user_worklog_privilege
|
||||
*
|
||||
|
@ -393,18 +425,29 @@ function User_view_myshifts(
|
|||
$shifts,
|
||||
$user_source,
|
||||
$its_me,
|
||||
$tshirt_score,
|
||||
$tshirt_admin,
|
||||
$goodie_score,
|
||||
$goodie_admin,
|
||||
$user_worklogs,
|
||||
$admin_user_worklog_privilege
|
||||
) {
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
$goodie_tshirt = $goodie === GoodieType::Tshirt;
|
||||
$supported_angeltypes = auth()->user()
|
||||
->userAngelTypes()
|
||||
->where('supporter', true)
|
||||
->pluck('angel_types.id');
|
||||
$show_sum = true;
|
||||
|
||||
$myshifts_table = [];
|
||||
$timeSum = 0;
|
||||
foreach ($shifts as $shift) {
|
||||
$key = $shift->start->timestamp . '-shift-' . $shift->shift_entry_id . $shift->id;
|
||||
$supporter = $supported_angeltypes->contains($shift->angel_type_id);
|
||||
if (!auth()->can('user_shifts_admin') && !$supporter && !$its_me) {
|
||||
$show_sum = false;
|
||||
continue;
|
||||
}
|
||||
$myshifts_table[$key] = User_view_myshift($shift, $user_source, $its_me);
|
||||
if (!$shift->freeloaded) {
|
||||
$timeSum += ($shift->end->timestamp - $shift->start->timestamp);
|
||||
|
@ -435,18 +478,22 @@ function User_view_myshifts(
|
|||
$shift['row-class'] = 'border-bottom border-info';
|
||||
}
|
||||
}
|
||||
if ($show_sum) {
|
||||
$myshifts_table[] = [
|
||||
'date' => '<b>' . __('Sum:') . '</b>',
|
||||
'duration' => '<b>' . sprintf('%.2f', round($timeSum / 3600, 2)) . ' h</b>',
|
||||
'hints' => '',
|
||||
'location' => '',
|
||||
'shift_info' => '',
|
||||
'comment' => '',
|
||||
'actions' => '',
|
||||
];
|
||||
if ($goodie_enabled && ($its_me || $tshirt_admin)) {
|
||||
}
|
||||
if ($goodie_enabled && ($its_me || $goodie_admin || auth()->can('admin_user'))) {
|
||||
$myshifts_table[] = [
|
||||
'date' => '<b>' . ($goodie_tshirt ? __('Your T-shirt score') : __('Your goodie score')) . '™:</b>',
|
||||
'duration' => '<b>' . $tshirt_score . '</b>',
|
||||
'date' => '<b>' . ($goodie_tshirt ? __('T-shirt score') : __('Goodie score')) . '™:</b>',
|
||||
'duration' => '<b>' . $goodie_score . '</b>',
|
||||
'hints' => '',
|
||||
'location' => '',
|
||||
'shift_info' => '',
|
||||
'comment' => '',
|
||||
|
@ -489,6 +536,7 @@ function User_view_worklog(Worklog $worklog, $admin_user_worklog_privilege)
|
|||
return [
|
||||
'date' => icon('calendar-event') . date(__('general.date'), $worklog->worked_at->timestamp),
|
||||
'duration' => sprintf('%.2f', $worklog->hours) . ' h',
|
||||
'hints' => '',
|
||||
'location' => '',
|
||||
'shift_info' => __('Work log entry'),
|
||||
'comment' => htmlspecialchars($worklog->comment) . '<br>'
|
||||
|
@ -514,10 +562,11 @@ function User_view_worklog(Worklog $worklog, $admin_user_worklog_privilege)
|
|||
* @param Group[] $user_groups
|
||||
* @param Shift[]|Collection $shifts
|
||||
* @param bool $its_me
|
||||
* @param int $tshirt_score
|
||||
* @param bool $tshirt_admin
|
||||
* @param string $goodie_score
|
||||
* @param bool $goodie_admin
|
||||
* @param bool $admin_user_worklog_privilege
|
||||
* @param Worklog[]|Collection $user_worklogs
|
||||
* @param bool $admin_certificates
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
@ -529,10 +578,11 @@ function User_view(
|
|||
$user_groups,
|
||||
$shifts,
|
||||
$its_me,
|
||||
$tshirt_score,
|
||||
$tshirt_admin,
|
||||
$goodie_score,
|
||||
$goodie_admin,
|
||||
$admin_user_worklog_privilege,
|
||||
$user_worklogs
|
||||
$user_worklogs,
|
||||
$admin_certificates
|
||||
) {
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
|
@ -541,15 +591,20 @@ function User_view(
|
|||
$nightShiftsConfig = config('night_shifts');
|
||||
$user_name = htmlspecialchars((string) $user_source->personalData->first_name) . ' '
|
||||
. htmlspecialchars((string) $user_source->personalData->last_name);
|
||||
$user_info_show = auth()->can('user.info.show');
|
||||
$myshifts_table = '';
|
||||
if ($its_me || $admin_user_privilege || $tshirt_admin) {
|
||||
$user_angeltypes_supporter = false;
|
||||
foreach ($user_source->userAngelTypes as $user_angeltype) {
|
||||
$user_angeltypes_supporter = $user_angeltypes_supporter
|
||||
|| $auth->user()->isAngelTypeSupporter($user_angeltype);
|
||||
}
|
||||
|
||||
if ($its_me || $admin_user_privilege || $goodie_admin || $user_angeltypes_supporter) {
|
||||
$my_shifts = User_view_myshifts(
|
||||
$shifts,
|
||||
$user_source,
|
||||
$its_me,
|
||||
$tshirt_score,
|
||||
$tshirt_admin,
|
||||
$goodie_score,
|
||||
$goodie_admin,
|
||||
$user_worklogs,
|
||||
$admin_user_worklog_privilege
|
||||
);
|
||||
|
@ -557,13 +612,17 @@ function User_view(
|
|||
$myshifts_table = div('table-responsive', table([
|
||||
'date' => __('Day & Time'),
|
||||
'duration' => __('Duration'),
|
||||
'hints' => '',
|
||||
'location' => __('Location'),
|
||||
'shift_info' => __('Name & Workmates'),
|
||||
'comment' => __('worklog.comment'),
|
||||
'actions' => __('general.actions'),
|
||||
], $my_shifts));
|
||||
} elseif ($user_source->state->force_active) {
|
||||
$myshifts_table = success(__('You have done enough.'), true);
|
||||
} elseif ($user_source->state->force_active && config('enable_force_active')) {
|
||||
$myshifts_table = success(
|
||||
($its_me ? __('You have done enough.') : (__('%s has done enough.', [$user_source->name]))),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -579,32 +638,20 @@ function User_view(
|
|||
|
||||
return page_with_title(
|
||||
'<span class="icon-icon_angel"></span> '
|
||||
. (
|
||||
(config('enable_pronoun') && $user_source->personalData->pronoun)
|
||||
? '<small>' . htmlspecialchars($user_source->personalData->pronoun) . '</small> '
|
||||
: ''
|
||||
)
|
||||
. htmlspecialchars($user_source->name)
|
||||
. (config('enable_user_name') ? ' <small>' . $user_name . '</small>' : '')
|
||||
. (
|
||||
(($user_info_show || auth()->can('admin_arrive')) && $user_source->state->user_info)
|
||||
? (
|
||||
' <small><span class="bi bi-info-circle-fill text-info" '
|
||||
. ($user_info_show
|
||||
? 'data-bs-toggle="tooltip" title="' . htmlspecialchars($user_source->state->user_info)
|
||||
. ((config('enable_pronoun') && $user_source->personalData->pronoun)
|
||||
? ' <small>(' . htmlspecialchars($user_source->personalData->pronoun) . ')</small> '
|
||||
: '')
|
||||
. '"></span></small>'
|
||||
)
|
||||
: ''
|
||||
),
|
||||
. user_info_icon($user_source),
|
||||
[
|
||||
msg(),
|
||||
div('row', [
|
||||
div('col-md-12', [
|
||||
table_buttons([
|
||||
$auth->can('user.edit.shirt') && $goodie_enabled ? button(
|
||||
$auth->can('user.goodie.edit') && $goodie_enabled ? button(
|
||||
url('/admin/user/' . $user_source->id . '/goodie'),
|
||||
icon('person') . ($goodie_tshirt ? __('Shirt') : __('Goodie'))
|
||||
icon('person') . ($goodie_tshirt ? __('T-shirt') : __('Goodie'))
|
||||
) : '',
|
||||
$admin_user_privilege ? button(
|
||||
url('/admin-user', ['id' => $user_source->id]),
|
||||
|
@ -625,6 +672,13 @@ function User_view(
|
|||
icon('valentine') . __('Vouchers')
|
||||
)
|
||||
: '',
|
||||
(
|
||||
$admin_certificates
|
||||
&& (config('ifsg_enabled') || config('driving_license_enabled'))
|
||||
) ? button(
|
||||
url('/users/' . $user_source->id . '/certificates'),
|
||||
icon('card-checklist') . __('settings.certificates')
|
||||
) : '',
|
||||
$admin_user_worklog_privilege ? button(
|
||||
url('/admin/user/' . $user_source->id . '/worklog'),
|
||||
icon('clock-history') . __('worklog.add')
|
||||
|
@ -643,13 +697,9 @@ function User_view(
|
|||
url('/shifts-json-export', ['key' => $user_source->api_key]),
|
||||
icon('braces') . __('JSON Export')
|
||||
) : '',
|
||||
(
|
||||
$auth->can('shifts_json_export')
|
||||
|| $auth->can('ical')
|
||||
|| $auth->can('atom')
|
||||
) ? button(
|
||||
url('/user-myshifts', ['reset' => 1]),
|
||||
icon('arrow-repeat') . __('Reset API key')
|
||||
$auth->canAny(['api', 'shifts_json_export', 'ical', 'atom']) ? button(
|
||||
url('/settings/api'),
|
||||
icon('arrow-repeat') . __('API Settings')
|
||||
) : '',
|
||||
], 'mb-2') : '',
|
||||
]),
|
||||
|
@ -687,14 +737,15 @@ function User_view(
|
|||
User_groups_render($user_groups),
|
||||
$admin_user_privilege ? User_oauth_render($user_source) : '',
|
||||
]),
|
||||
($its_me || $admin_user_privilege) ? '<h2>' . __('Shifts') . '</h2>' : '',
|
||||
($its_me || $admin_user_privilege) ? '<h2>' . __('general.shifts') . '</h2>' : '',
|
||||
$myshifts_table,
|
||||
($its_me && $nightShiftsConfig['enabled'] && $goodie_enabled) ? info(
|
||||
sprintf(
|
||||
icon('info-circle') . __('Your night shifts between %d and %d am count twice for the %s score.'),
|
||||
icon('moon-stars') . __('Night shifts between %d and %d am are multiplied by %d for the %s score.', [
|
||||
$nightShiftsConfig['start'],
|
||||
$nightShiftsConfig['end'],
|
||||
($goodie_tshirt ? __('T-shirt') : __('goodie'))
|
||||
$nightShiftsConfig['multiplier'],
|
||||
($goodie_tshirt ? __('T-shirt') : __('goodie'))])
|
||||
),
|
||||
true,
|
||||
true
|
||||
|
@ -767,6 +818,9 @@ function User_view_state_admin($freeloader, $user_source)
|
|||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
$goodie_tshirt = $goodie === GoodieType::Tshirt;
|
||||
$password_reset = PasswordReset::whereUserId($user_source->id)
|
||||
->where('created_at', '>', $user_source->last_login_at ?: '')
|
||||
->count();
|
||||
|
||||
if ($freeloader) {
|
||||
$state[] = '<span class="text-danger">' . icon('exclamation-circle') . __('Freeloader') . '</span>';
|
||||
|
@ -782,12 +836,12 @@ function User_view_state_admin($freeloader, $user_source)
|
|||
)
|
||||
. '</span>';
|
||||
|
||||
if ($user_source->state->force_active) {
|
||||
if ($user_source->state->force_active && config('enable_force_active')) {
|
||||
$state[] = '<span class="text-success">' . __('user.force_active') . '</span>';
|
||||
} elseif ($user_source->state->active) {
|
||||
$state[] = '<span class="text-success">' . __('user.active') . '</span>';
|
||||
}
|
||||
if ($user_source->state->got_shirt && $goodie_enabled) {
|
||||
if ($user_source->state->got_goodie && $goodie_enabled) {
|
||||
$state[] = '<span class="text-success">' . ($goodie_tshirt ? __('T-shirt') : __('Goodie')) . '</span>';
|
||||
}
|
||||
} else {
|
||||
|
@ -817,6 +871,10 @@ function User_view_state_admin($freeloader, $user_source)
|
|||
}
|
||||
}
|
||||
|
||||
if ($password_reset) {
|
||||
$state[] = __('Password reset in progress');
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
|
@ -968,7 +1026,7 @@ function render_user_freeloader_hint()
|
|||
{
|
||||
if (auth()->user()->isFreeloader()) {
|
||||
return sprintf(
|
||||
__('You freeloaded at least %s shifts. Shift signup is locked. Please go to heavens desk to be unlocked again.'),
|
||||
__('freeload.freeloader.info'),
|
||||
config('max_freeloadable_shifts')
|
||||
);
|
||||
}
|
||||
|
@ -1002,7 +1060,7 @@ function render_user_arrived_hint(bool $is_sys_menu = false)
|
|||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
function render_user_tshirt_hint()
|
||||
function render_user_goodie_hint()
|
||||
{
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_tshirt = $goodie === GoodieType::Tshirt;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"css-minimizer-webpack-plugin": "^4.2.2",
|
||||
"editorconfig-checker": "^5.1.1",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-editorconfig": "^4.0.3",
|
||||
"mini-css-extract-plugin": "^2.7.2",
|
||||
"postcss": "^8.4.31",
|
||||
|
|
|
@ -8,3 +8,8 @@ parameters:
|
|||
- tests
|
||||
scanDirectories:
|
||||
- includes
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#.*#'
|
||||
path: config/config.php
|
||||
reportUnmatched: false
|
||||
|
|
|
@ -6,6 +6,7 @@ use Engelsystem\Application;
|
|||
use Engelsystem\Middleware\Dispatcher;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
// Include app bootstrapping
|
||||
require_once realpath(__DIR__ . '/../includes/engelsystem.php');
|
||||
|
||||
/** @var Application $app */
|
||||
|
@ -18,4 +19,5 @@ $middleware = $app->getMiddleware();
|
|||
$dispatcher = new Dispatcher($middleware);
|
||||
$dispatcher->setContainer($app);
|
||||
|
||||
// Handle the request
|
||||
$dispatcher->handle($request);
|
||||
|
|
|
@ -36,12 +36,20 @@ tags:
|
|||
- name: user
|
||||
description: User information
|
||||
|
||||
security:
|
||||
- bearer-auth: [ ]
|
||||
- api-key-header: [ ]
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
bearer-auth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: API key from settings
|
||||
api-key-header:
|
||||
type: apiKey
|
||||
name: x-api-key
|
||||
in: header
|
||||
|
||||
responses:
|
||||
UnauthorizedError: # 401
|
||||
|
@ -391,12 +399,9 @@ components:
|
|||
properties:
|
||||
start:
|
||||
$ref: '#/components/schemas/DateTimeOptional'
|
||||
end:
|
||||
$ref: '#/components/schemas/DateTimeOptional'
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
teardown:
|
||||
event:
|
||||
type: object
|
||||
properties:
|
||||
start:
|
||||
|
@ -406,6 +411,13 @@ components:
|
|||
required:
|
||||
- start
|
||||
- end
|
||||
teardown:
|
||||
type: object
|
||||
properties:
|
||||
end:
|
||||
$ref: '#/components/schemas/DateTimeOptional'
|
||||
required:
|
||||
- end
|
||||
required:
|
||||
- api
|
||||
- spec
|
||||
|
@ -414,11 +426,9 @@ components:
|
|||
- url
|
||||
- timezone
|
||||
- buildup
|
||||
- event
|
||||
- teardown
|
||||
|
||||
security:
|
||||
- bearerAuth: [ ]
|
||||
|
||||
paths:
|
||||
/angeltypes:
|
||||
get:
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { ready } from './ready';
|
||||
|
||||
ready(() => {
|
||||
[...document.getElementsByClassName('prevent-default')].forEach((element) => {
|
||||
let preventDefault = (e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
element.addEventListener('submit', preventDefault);
|
||||
element.addEventListener('click', preventDefault);
|
||||
});
|
||||
|
||||
document.getElementById('delete-form')?.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
console.log('Delete confirmed');
|
||||
});
|
||||
});
|
|
@ -318,6 +318,7 @@ ready(() => {
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="${element.className}"
|
||||
autofocus
|
||||
title="${element.title}" data-submit="">
|
||||
${element.dataset.confirm_button_text ?? element.innerHTML}
|
||||
</button>
|
||||
|
@ -328,15 +329,25 @@ ready(() => {
|
|||
`
|
||||
);
|
||||
|
||||
let modal = document.getElementById('confirmation-modal');
|
||||
const modal = document.getElementById('confirmation-modal');
|
||||
modal.addEventListener('hide.bs.modal', () => {
|
||||
modalOpen = false;
|
||||
});
|
||||
modal.querySelector('[data-submit]').addEventListener('click', (event) => {
|
||||
|
||||
const modalSubmitButton = modal.querySelector('[data-submit]');
|
||||
modalSubmitButton.addEventListener('click', () => {
|
||||
element.type = oldType;
|
||||
element.click();
|
||||
});
|
||||
|
||||
/**
|
||||
* After the modal has been shown, focus on the "Submit" button in the modal
|
||||
* so that it can be confirmed with "Enter".
|
||||
*/
|
||||
modal.addEventListener('shown.bs.modal', () => {
|
||||
modalSubmitButton.focus();
|
||||
});
|
||||
|
||||
modalOpen = true;
|
||||
let bootstrapModal = new bootstrap.Modal(modal);
|
||||
bootstrapModal.show();
|
||||
|
@ -349,7 +360,7 @@ ready(() => {
|
|||
*/
|
||||
ready(() => {
|
||||
[
|
||||
['welcome-title', '.btn-group .btn.d-none'],
|
||||
['welcome-title', '.registration .d-none'],
|
||||
['settings-title', '.user-settings .nav-item'],
|
||||
['oauth-settings-title', 'table tr.d-none'],
|
||||
].forEach(([id, selector]) => {
|
||||
|
|
|
@ -3,3 +3,4 @@ window.bootstrap = require('bootstrap');
|
|||
import './forms';
|
||||
import './countdown';
|
||||
import './dashboard';
|
||||
import './design';
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
&-40 {
|
||||
// >= 40 bars
|
||||
--barchart-bar-margin: 1px;
|
||||
--barchart-bar-margin: 0.5px;
|
||||
--barchart-group-margin: 0.5%;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
|
|||
|
||||
$form-label-font-weight: $font-weight-bold;
|
||||
|
||||
$choices-guttering: auto;
|
||||
|
||||
@import '~bootstrap/scss/utilities';
|
||||
|
||||
@import '~bootstrap/scss/root';
|
||||
|
@ -100,6 +102,10 @@ a .icon-icon_angel {
|
|||
background-color: $link-color;
|
||||
}
|
||||
|
||||
a .disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navbar .icon-icon_angel {
|
||||
background-color: $nav-link-disabled-color;
|
||||
}
|
||||
|
@ -245,6 +251,10 @@ table .border-bottom {
|
|||
font-size: 0.9em;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.tick.now {
|
||||
border-top: 2px solid $info;
|
||||
}
|
||||
}
|
||||
|
||||
.lane.time {
|
||||
|
|
|
@ -10,6 +10,7 @@ $choices-bg-color-dropdown: $input-bg;
|
|||
$choices-text-color: $input-color;
|
||||
|
||||
$es-choices-highlight-color: $choices-text-color !default;
|
||||
$choices-bg-color-disabled: $input-disabled-bg;
|
||||
|
||||
@import '~choices.js/src/styles/choices.scss';
|
||||
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
// cccamp23
|
||||
|
||||
// Variables
|
||||
// --------------------------------------------------
|
||||
|
||||
@import 'dark';
|
||||
|
||||
//== changed Colors
|
||||
$gray-dark: #231f20;
|
||||
$gray-darker: darken($gray-dark, 30%);
|
||||
$gray: lighten($gray-dark, 30%);
|
||||
$gray-light: lighten($gray, 30%);
|
||||
$gray-lighter: lighten($gray-light, 30%);
|
||||
$dark: $gray-dark;
|
||||
|
||||
$primary: #de37ff;
|
||||
$secondary: #28ffff;
|
||||
$success: #79ff5e;
|
||||
$info: #3dbddf;
|
||||
$warning: #f6b345;
|
||||
$danger: #de4040;
|
||||
|
||||
$text-muted: $gray-light;
|
||||
|
||||
$btn-link-disabled-color: $gray-light;
|
||||
|
||||
$dropdown-bg: #212529;
|
||||
$dropdown-link-hover-color: #000000;
|
||||
|
||||
//== changed Forms
|
||||
|
||||
$input-bg: $gray-darker;
|
||||
$input-bg-disabled: lighten($gray-lighter, 15%);
|
||||
$input-border-color: $secondary;
|
||||
$input-group-addon-bg: $input-bg;
|
||||
|
||||
$form-check-input-border: 1px solid $gray;
|
||||
|
||||
//== changed Pagination
|
||||
|
||||
$pagination-hover-color: $gray-lighter;
|
||||
$pagination-active-color: $gray-lighter;
|
||||
|
||||
//== changed Form states and alerts
|
||||
|
||||
$state-success-text: #fff;
|
||||
$state-success-bg: $success;
|
||||
$state-success-border: darken($state-success-bg, 5%);
|
||||
|
||||
$state-info-text: #fff;
|
||||
$state-info-bg: $info;
|
||||
$state-info-border: darken($state-info-bg, 7%);
|
||||
|
||||
$state-warning-text: #fff;
|
||||
$state-warning-bg: $warning;
|
||||
$state-warning-border: darken($state-warning-bg, 3%);
|
||||
|
||||
$state-danger-text: #fff;
|
||||
$state-danger-bg: $danger;
|
||||
$state-danger-border: darken($state-danger-bg, 3%);
|
||||
|
||||
$headings-small-color: $gray-light;
|
||||
|
||||
code {
|
||||
background-color: $state-info-bg;
|
||||
color: $state-info-text;
|
||||
}
|
||||
|
||||
$alert-bg-scale: 0%;
|
||||
$alert-border-scale: 0%;
|
||||
$alert-color-scale: 0%;
|
||||
|
||||
// Navs =======================================================================
|
||||
|
||||
$nav-tabs-link-active-border-color: $gray-dark;
|
||||
|
||||
$nav-tabs-link-active-color: $gray-darker;
|
||||
$nav-pills-link-active-color: $gray-darker;
|
||||
|
||||
//== Pagination
|
||||
//
|
||||
//##
|
||||
|
||||
$pagination-color: $gray-lighter;
|
||||
$pagination-border-color: $gray-dark;
|
||||
|
||||
$pagination-hover-color: #000;
|
||||
$pagination-hover-border-color: $gray-dark;
|
||||
|
||||
$pagination-active-color: #000;
|
||||
$pagination-active-border-color: $gray-dark;
|
||||
|
||||
$pagination-disabled-color: $gray-light;
|
||||
$pagination-disabled-border-color: $gray-dark;
|
||||
|
||||
// dark
|
||||
@import 'cyborg_variables';
|
||||
@import 'cyborg_styles';
|
||||
|
||||
//== Typography
|
||||
|
||||
@font-face {
|
||||
font-family: 'VCR OCD Faux';
|
||||
src: url('theme17/VCROCDFaux.ttf') format('truetype'), url('theme17/VCROCDFaux.woff') format('woff'),
|
||||
url('theme17/VCROCDFaux.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Gabriella;
|
||||
src: url('theme17/GabriellaHeavy.otf') format('opentype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
$headings-font-family: Gabriella, $font-family-sans-serif;
|
||||
$font-family-monospace: 'VCR OCD Faux', SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||
monospace;
|
||||
|
||||
h1,
|
||||
.h1,
|
||||
h2,
|
||||
.h2,
|
||||
h3,
|
||||
.h3,
|
||||
h4,
|
||||
.h4,
|
||||
h5,
|
||||
.h5 {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
font-family: $headings-font-family;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent !important;
|
||||
border-color: $secondary !important;
|
||||
color: $secondary !important;
|
||||
}
|
||||
|
||||
// Specials ===================================================================
|
||||
|
||||
.bg-success a,
|
||||
.bg-primary a,
|
||||
.bg-warning a,
|
||||
.bg-info a,
|
||||
.bg-light a {
|
||||
color: $gray-darker !important;
|
||||
}
|
||||
|
||||
.bg-body a,
|
||||
.bg-danger a,
|
||||
.bg-secondary a,
|
||||
.bg-dark a {
|
||||
color: $state-danger-text !important;
|
||||
}
|
||||
|
||||
.bg-primary,
|
||||
.bg-success,
|
||||
.bg-warning,
|
||||
.bg-info,
|
||||
.bg-light {
|
||||
color: $gray-darker;
|
||||
}
|
||||
|
||||
.bg-body,
|
||||
.bg-danger,
|
||||
.bg-secondary,
|
||||
.bg-dark {
|
||||
color: $state-danger-text;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
color: $primary;
|
||||
font-family: $font-family-monospace;
|
||||
}
|
||||
|
||||
.nav-tabs,
|
||||
.nav-pills,
|
||||
.pager {
|
||||
a {
|
||||
color: $gray-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
.alert a {
|
||||
color: $gray-darker;
|
||||
}
|
||||
|
||||
.alert.alert-danger,
|
||||
.alert.alert-danger a {
|
||||
color: $gray-lighter;
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -85,9 +85,6 @@ msgstr "Das Programm konnte nicht abgerufen werden."
|
|||
msgid "schedule.import.read-error"
|
||||
msgstr "Das Programm konnte nicht gelesen werden."
|
||||
|
||||
msgid "schedule.import.invalid-shift-type"
|
||||
msgstr "Der Schichttyp konnte nicht gefunden werden."
|
||||
|
||||
msgid "schedule.import.success"
|
||||
msgstr "Das Programm wurde erfolgreich importiert."
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ msgstr ""
|
|||
msgid "password.email.message"
|
||||
msgstr "Um dein Passwort zurückzusetzen, besuche %s"
|
||||
|
||||
msgid "page.error.title"
|
||||
msgstr "Fehler %s"
|
||||
|
||||
msgid "page.403.title"
|
||||
msgstr "Nicht erlaubt"
|
||||
|
||||
|
@ -87,7 +90,7 @@ msgid "form.submit"
|
|||
msgstr "Absenden"
|
||||
|
||||
msgid "form.send_notification"
|
||||
msgstr "Benachrichtigungen versenden"
|
||||
msgstr "%d Benachrichtigungen versenden"
|
||||
|
||||
msgid "credits.source"
|
||||
msgstr "Quellcode"
|
||||
|
@ -174,6 +177,9 @@ msgstr "Lösche Engeltyp %s"
|
|||
msgid "Please check the name. Maybe it already exists."
|
||||
msgstr "Bitte überprüfe den Namen. Vielleicht ist er bereits vergeben."
|
||||
|
||||
msgid "Angel type saved."
|
||||
msgstr "Engeltyp wurde gespeichert."
|
||||
|
||||
msgid "Create angeltype"
|
||||
msgstr "Engeltyp erstellen"
|
||||
|
||||
|
@ -318,8 +324,8 @@ msgstr "Benötigte Engel"
|
|||
msgid "Shift deleted."
|
||||
msgstr "Schicht gelöscht."
|
||||
|
||||
msgid "Do you want to delete the shift %s from %s to %s?"
|
||||
msgstr "Möchtest Du die Schicht %s von %s bis %s löschen?"
|
||||
msgid "Do you want to delete the shift \"%s\" from %s to %s?"
|
||||
msgstr "Möchtest Du die Schicht \"%s\" von %s bis %s löschen?"
|
||||
|
||||
msgid "Shift could not be found."
|
||||
msgstr "Schicht konnte nicht gefunden werden."
|
||||
|
@ -362,11 +368,17 @@ msgstr "Engeltyp für Benutzer bestätigen"
|
|||
msgid "You are not allowed to delete this users angeltype."
|
||||
msgstr "Du darfst diesen Benutzer nicht von diesem Engeltyp entfernen."
|
||||
|
||||
msgid "User %s removed from %s."
|
||||
msgstr "Benutzer %s von %s entfernt."
|
||||
msgid "User \"%s\" removed from \"%s\"."
|
||||
msgstr "Benutzer \"%s\" von \"%s\" entfernt."
|
||||
|
||||
msgid "Remove angeltype"
|
||||
msgstr "Engeltyp löschen"
|
||||
msgid "Edit certificates"
|
||||
msgstr "Zertifikate bearbeiten"
|
||||
|
||||
msgid "You successfully left \"%2$s\"."
|
||||
msgstr "Du hast erfolgreich \"%2$s\" verlassen."
|
||||
|
||||
msgid "Leave angeltype"
|
||||
msgstr "Engeltyp verlassen"
|
||||
|
||||
msgid "You are not allowed to set supporter rights."
|
||||
msgstr "Du darfst keine Supporterrechte bearbeiten."
|
||||
|
@ -515,7 +527,7 @@ msgstr "Suche Engel:"
|
|||
msgid "Show all shifts"
|
||||
msgstr "Alle Schichten anzeigen"
|
||||
|
||||
msgid "How much angels should be active?"
|
||||
msgid "How many angels should be active?"
|
||||
msgstr "Wie viele Engel sollten aktiv sein?"
|
||||
|
||||
msgid "Size"
|
||||
|
@ -524,20 +536,23 @@ msgstr "Größe"
|
|||
msgid "No."
|
||||
msgstr "Nr."
|
||||
|
||||
msgid "Shifts"
|
||||
msgid "general.shifts"
|
||||
msgstr "Schichten"
|
||||
|
||||
msgid "Length"
|
||||
msgstr "Länge"
|
||||
|
||||
msgid "Active?"
|
||||
msgstr "Aktiv?"
|
||||
msgid "Length (in minutes)"
|
||||
msgstr "Länge (in Minuten)"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "Aktiv"
|
||||
|
||||
msgid "Forced"
|
||||
msgstr "Erzwungen"
|
||||
|
||||
msgid "T-shirt?"
|
||||
msgstr "T-Shirt?"
|
||||
msgid "T-shirt"
|
||||
msgstr "T-Shirt"
|
||||
|
||||
msgid "T-shirt statistic"
|
||||
msgstr "T-Shirt Statistik"
|
||||
|
@ -593,6 +608,18 @@ msgstr "Engeltyp"
|
|||
msgid "shift.next"
|
||||
msgstr "Nächste Schicht"
|
||||
|
||||
msgid "general.shift"
|
||||
msgstr "Schicht"
|
||||
|
||||
msgid "shift.angeltype_source"
|
||||
msgstr "Benötigte Engel von: %s"
|
||||
|
||||
msgid "shift.angeltype_source.shift_type"
|
||||
msgstr "Programm %s via Schichttyp %s"
|
||||
|
||||
msgid "shift.angeltype_source.location"
|
||||
msgstr "Programm %s via Ort %s"
|
||||
|
||||
msgid "Last shift"
|
||||
msgstr "Letzte Schicht"
|
||||
|
||||
|
@ -734,22 +761,8 @@ msgstr "User bearbeiten"
|
|||
msgid "general.datetime"
|
||||
msgstr "d.m.Y H:i"
|
||||
|
||||
msgid "Key changed."
|
||||
msgstr "Key geändert."
|
||||
|
||||
msgid "Reset API key"
|
||||
msgstr "API-Key zurücksetzen"
|
||||
|
||||
msgid ""
|
||||
"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."
|
||||
msgstr ""
|
||||
"Wenn du den API-Key zurücksetzt, ändert sich die URL zu deinem iCal-, JSON-"
|
||||
"Export und Atom/RSS Feed! Du musst diesen überall ändern, wo er in Benutzung ist."
|
||||
|
||||
msgid "Continue"
|
||||
msgstr "Fortfahren"
|
||||
msgid "API Settings"
|
||||
msgstr "API Einstellungen"
|
||||
|
||||
msgid "Please enter a freeload comment!"
|
||||
msgstr "Gib bitte einen Schwänz-Kommentar ein!"
|
||||
|
@ -819,16 +832,13 @@ msgid "iCal export and API"
|
|||
msgstr "iCal Export und API"
|
||||
|
||||
msgid ""
|
||||
"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>)."
|
||||
"Export your own shifts formatted as <a href=\"%s\" target=\"_blank\">iCal</a> or "
|
||||
"<a href=\"%s\" target=\"_blank\">JSON</a> (please keep the link secret, otherwise you have to reset the api key "
|
||||
"<a href=\"%s\">in your settings</a>)."
|
||||
msgstr ""
|
||||
"Exportiere Deine Schichten. <a href=\"%s\">iCal Format</a> oder <a href=\"%s"
|
||||
"\">JSON Format</a> verfügbar (Link bitte geheimhalten, sonst <a href=\"%s"
|
||||
"\">API-Key zurücksetzen</a>)."
|
||||
|
||||
msgid "Show API Key"
|
||||
msgstr "API Key anzeigen"
|
||||
"Exportiere Deine Schichten im <a href=\"%s\" target=\"_blank\">iCal</a> oder <a href=\"%s"
|
||||
"\" target=\"_blank\">JSON</a> Format (Link bitte geheimhalten, sonst musst du den API-Key in "
|
||||
"<a href=\"%s\">deinen Einstellungen</a> zurücksetzen)."
|
||||
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
@ -893,9 +903,15 @@ msgstr "Kontakt"
|
|||
msgid "Primary contact person/desk for user questions."
|
||||
msgstr "Ansprechpartner für Fragen."
|
||||
|
||||
msgid "my driving license"
|
||||
msgid "My driving license"
|
||||
msgstr "Meine Führerschein-Infos"
|
||||
|
||||
msgid "angeltype.ifsg.required.info.preview"
|
||||
msgstr "Dieser Engeltyp benötigt eine Gesundheitsbelehrung."
|
||||
|
||||
msgid "angeltype.driving_license.required.info.preview"
|
||||
msgstr "Dieser Engeltyp benötigt Führerschein-Infos."
|
||||
|
||||
msgid ""
|
||||
"This angeltype requires a driver license. Please enter your driver license "
|
||||
"information!"
|
||||
|
@ -1062,8 +1078,8 @@ msgstr "Schicht Anmeldung"
|
|||
msgid "Freeloaded"
|
||||
msgstr "Geschwänzt"
|
||||
|
||||
msgid "Freeload comment (Only for shift coordination):"
|
||||
msgstr "Schwänzer Kommentar (Nur für die Schicht-Koordination):"
|
||||
msgid "Freeload comment (Only for shift coordination and supporters):"
|
||||
msgstr "Schwänzer Kommentar (Nur für Supporter und die Schicht-Koordination):"
|
||||
|
||||
msgid "Edit shift entry"
|
||||
msgstr "Schichteintrag bearbeiten"
|
||||
|
@ -1086,6 +1102,9 @@ msgstr "erstellt am %s von %s"
|
|||
msgid "edited at %s by %s"
|
||||
msgstr "bearbeitet am %s von %s"
|
||||
|
||||
msgid "History ID: %s"
|
||||
msgstr "Historien-ID: %s"
|
||||
|
||||
msgid "This shift is in the far future and becomes available for signup at %s."
|
||||
msgstr ""
|
||||
"Diese Schicht liegt in der fernen Zukunft und du kannst dich ab %s eintragen."
|
||||
|
@ -1105,12 +1124,18 @@ msgstr "Möchtest Du wirklich alle Benutzer als %s bestätigen?"
|
|||
msgid "Do you really want to confirm %s for %s?"
|
||||
msgstr "Möchtest Du wirklich %s für %s bestätigen?"
|
||||
|
||||
msgid "Do you really want to delete %s from %s?"
|
||||
msgstr "Möchtest Du wirklich %s von %s entfernen?"
|
||||
msgid "Do you really want to remove \"%s\" from \"%s\"?"
|
||||
msgstr "Möchtest Du wirklich \"%s\" von \"%s\" entfernen?"
|
||||
|
||||
msgid "Do you really want to leave \"%2$s\"?"
|
||||
msgstr "Möchtest Du \"%2$s\" wirklich verlassen?"
|
||||
|
||||
msgid "Do you really want to add %s to %s?"
|
||||
msgstr "Möchtest Du wirklich %s zu %s hinzufügen?"
|
||||
|
||||
msgid "Do you want to become a %2$s?"
|
||||
msgstr "Möchtest Du ein %2$s werden?"
|
||||
|
||||
msgid "Confirm user"
|
||||
msgstr "Benutzer bestätigen"
|
||||
|
||||
|
@ -1145,9 +1170,6 @@ msgstr "Gutschein"
|
|||
msgid "Freeloads"
|
||||
msgstr "Schwänzereien"
|
||||
|
||||
msgid "T-shirt"
|
||||
msgstr "T-Shirt"
|
||||
|
||||
msgid "Last login"
|
||||
msgstr "Letzter Login"
|
||||
|
||||
|
@ -1169,8 +1191,8 @@ msgstr "Austragen"
|
|||
msgid "Sum:"
|
||||
msgstr "Summe:"
|
||||
|
||||
msgid "Your T-shirt score"
|
||||
msgstr "Dein T-Shirt Score"
|
||||
msgid "T-shirt score"
|
||||
msgstr "T-Shirt Score"
|
||||
|
||||
msgid "Work log entry"
|
||||
msgstr "Arbeitseinsatz"
|
||||
|
@ -1190,6 +1212,9 @@ msgstr "Name & Kollegen"
|
|||
msgid "You have done enough."
|
||||
msgstr "Du hast genug gemacht."
|
||||
|
||||
msgid "%s has done enough."
|
||||
msgstr "%s hat genug gemacht."
|
||||
|
||||
msgid "Vouchers"
|
||||
msgstr "Gutscheine"
|
||||
|
||||
|
@ -1199,8 +1224,8 @@ msgstr "iCal Export"
|
|||
msgid "JSON Export"
|
||||
msgstr "JSON Export"
|
||||
|
||||
msgid "Your night shifts between %d and %d am count twice for the %s score."
|
||||
msgstr "Deine Nachtschichten zwischen %d und %d Uhr zählen für den %s Score doppelt."
|
||||
msgid "Night shifts between %d and %d am are multiplied by %d for the %s score."
|
||||
msgstr "Nachtschichten zwischen %d und %d Uhr werden für den %4$s Score mit %3$d multipliziert."
|
||||
|
||||
msgid ""
|
||||
"Go to the <a href=\"%s\">shifts table</a> to sign yourself up for some "
|
||||
|
@ -1250,13 +1275,6 @@ msgstr ""
|
|||
"Bitte gib Dein geplantes Abreisedatum an, damit wir ein Gefühl für die Abbau-"
|
||||
"Planung bekommen."
|
||||
|
||||
msgid ""
|
||||
"You freeloaded at least %s shifts. Shift signup is locked. Please go to "
|
||||
"heavens desk to be unlocked again."
|
||||
msgstr ""
|
||||
"Du hast mindestens %s Schichten geschwänzt. Schicht-Registrierung ist "
|
||||
"gesperrt. Bitte gehe zum Himmelsschreibtisch um wieder entsperrt zu werden."
|
||||
|
||||
msgid "tshirt.required.hint"
|
||||
msgstr "Bitte gib eine T-Shirt-Größe in deinen Einstellungen an."
|
||||
|
||||
|
@ -1330,8 +1348,8 @@ msgstr "Goodie"
|
|||
msgid "goodie"
|
||||
msgstr "Goodie"
|
||||
|
||||
msgid "Your goodie score"
|
||||
msgstr "Dein Goodie Score"
|
||||
msgid "Goodie score"
|
||||
msgstr "Goodie Score"
|
||||
|
||||
msgid "Given goodies"
|
||||
msgstr "Ausgegebene Goodies"
|
||||
|
@ -1345,9 +1363,6 @@ msgstr "Goodie entfernen"
|
|||
msgid "Got goodie"
|
||||
msgstr "Goodie bekommen"
|
||||
|
||||
msgid "Goodie?"
|
||||
msgstr "Goodie?"
|
||||
|
||||
msgid "Angel has got a goodie."
|
||||
msgstr "Engel hat ein Goodie bekommen."
|
||||
|
||||
|
@ -1441,6 +1456,9 @@ msgstr "Minuten vor Talk beginn hinzufügen"
|
|||
msgid "schedule.minutes-after"
|
||||
msgstr "Minuten nach Talk ende hinzufügen"
|
||||
|
||||
msgid "schedule.for_locations"
|
||||
msgstr "Für Orte"
|
||||
|
||||
msgid "schedule.import.locations.add"
|
||||
msgstr "Neue Orte"
|
||||
|
||||
|
@ -1520,7 +1538,7 @@ msgid "news.comments.delete.title"
|
|||
msgstr "Kommentar \"%s\" löschen"
|
||||
|
||||
msgid "notification.news.updated.introduction"
|
||||
msgstr "Die News %1$s wurde aktualisiert"
|
||||
msgstr "Die News \"%1$s\" wurde aktualisiert"
|
||||
|
||||
msgid "notification.news.updated.text"
|
||||
msgstr "Du kannst sie dir unter %3$s anschauen."
|
||||
|
@ -1600,7 +1618,11 @@ msgstr "Benachrichtige mich bei neuen privaten Nachrichten."
|
|||
msgid "settings.profile.email_by_human_allowed"
|
||||
msgstr "Erlaube Himmel-Engeln mich per E-Mail zu kontaktieren."
|
||||
|
||||
msgid "settings.profile.email_goody"
|
||||
msgid "settings.profile.email_goodie"
|
||||
msgstr "Um gegebenenfalls Voucher für das nächste gleichartige Event zu erhalten stimme ich zu, "
|
||||
"dass mein Nick, E-Mail-Adresse und geleistete Arbeit solange gespeichert werden."
|
||||
|
||||
msgid "settings.profile.email_tshirt"
|
||||
msgstr "Um gegebenenfalls Voucher für das nächste gleichartige Event zu erhalten stimme ich zu, "
|
||||
"dass mein Nick, E-Mail-Adresse, geleistete Arbeit und T-Shirt-Größe solange gespeichert werden."
|
||||
|
||||
|
@ -1610,6 +1632,16 @@ msgstr "Dies kann jederzeit durch eine E-Mail an <a href=\"mailto:%s\">%1$s</a>
|
|||
msgid "settings.profile.shirt_size"
|
||||
msgstr "T-Shirt-Größe"
|
||||
|
||||
msgid "settings.profile.shirt_size.hint"
|
||||
msgstr "Ein straight-cut T-Shirt hat breite Schultern und einen fast quadratischen Körper. "
|
||||
"Ein fitted-cut (tailliertes) T-Shirt hat eine geschwungene Seitennaht, die an der Taille schmaler "
|
||||
"ist und einen größeren Brust- sowie Hüft-Umfang hat. "
|
||||
"Normalerweise fallen fitted-cut (taillierte) T-Shirts kleiner aus als straight-cut T-Shirts in der gleichen Größe, "
|
||||
"außerdem sind die Größen von Marke zu Marke unterscheiden."
|
||||
|
||||
msgid "settings.profile.shirt.link"
|
||||
msgstr "Mehr Informationen"
|
||||
|
||||
msgid "settings.profile.angeltypes.info"
|
||||
msgstr "Du kannst deine Engeltypen <a href=\"%s\">auf der Engeltypen-Seite</a> verwalten."
|
||||
|
||||
|
@ -1693,11 +1725,31 @@ msgstr "Gabelstapler"
|
|||
msgid "settings.certificates.ifsg_light"
|
||||
msgstr "Ich wurde vor Ort nach IfSG §43 (Frikadellendiplom light) belehrt."
|
||||
|
||||
msgid "settings.certificates.ifsg_light_admin"
|
||||
msgstr "Wurde vor Ort nach IfSG §43 (Frikadellendiplom light) belehrt."
|
||||
|
||||
msgid "settings.certificates.ifsg"
|
||||
msgstr "Ich habe eine Belehrung nach §43 IfSG (Frikadellendiplom) bei meinem Gesundheitsamt "
|
||||
"erhalten und innerhalb von 3 Monaten die Zweitbelehrung durch uns oder meinen Arbeitgeber/Koch/Verein bekommen. "
|
||||
"Zusätzlich ist die Zweitbelehrung nicht älter als zwei Jahre."
|
||||
|
||||
msgid "settings.certificates.ifsg_admin"
|
||||
msgstr "Hat eine Belehrung nach §43 IfSG (Frikadellendiplom) vom Gesundheitsamt "
|
||||
"erhalten und innerhalb von 3 Monaten die Zweitbelehrung durch uns oder einen Arbeitgeber/Koch/Verein bekommen. "
|
||||
"Zusätzlich ist die Zweitbelehrung nicht älter als zwei Jahre."
|
||||
|
||||
msgid "settings.certificates.confirmed"
|
||||
msgstr "Zertifikat bestätigt"
|
||||
|
||||
msgid "settings.certificates.ifsg_confirmed.hint"
|
||||
msgstr "Deine Gesundheitsbelehrung wurde bestätigt, du kannst deine Angaben nicht mehr selber ändern."
|
||||
|
||||
msgid "settings.certificates.drive_confirmed.hint"
|
||||
msgstr "Dein Führerschein wurde bestätigt, du kannst deine Angaben nicht mehr selber ändern."
|
||||
|
||||
msgid "settings.certificates.confirmation.info"
|
||||
msgstr "Du hast persönlich überprüft, dass die Zertifizierung / Bescheinigung den Anforderungen genügt."
|
||||
|
||||
msgid "settings.certificates.success"
|
||||
msgstr "Zertifikate wurden erfolgreich aktualisiert."
|
||||
|
||||
|
@ -1745,9 +1797,12 @@ msgstr "API"
|
|||
|
||||
msgid "settings.api.about"
|
||||
msgstr ""
|
||||
"Die API erlaubt es dir, über externe Programme, mit dem Engelsystem zu interagieren. "
|
||||
"Die API erlaubt es dir, über externe Programme, mit dem %s zu interagieren. "
|
||||
"Sie ist noch nicht vollständig, wir arbeiten aber daran sie zu erweitern.\n"
|
||||
"Der API Einstiegspunkt befindet sich unter `%s` und ist in der [OpenAPI Spezifikation](%s) beschrieben.\n"
|
||||
"Der Einstiegspunkt der API befindet sich unter `%s` und ist in der [OpenAPI Spezifikation](%s) beschrieben."
|
||||
|
||||
msgid "settings.api.about.warning"
|
||||
msgstr ""
|
||||
"Teile deinen persönlichen API Key mit niemandem, er erlaubt es deine persönlichen Daten einzusehen "
|
||||
"und Änderungen in deinem Namen durch zu führen!"
|
||||
|
||||
|
@ -1811,6 +1866,9 @@ msgstr "FAQ \"%s\" löschen"
|
|||
msgid "question.questions"
|
||||
msgstr "Fragen"
|
||||
|
||||
msgid "question.admin"
|
||||
msgstr "Fragen beantworten"
|
||||
|
||||
msgid "question.faq_link"
|
||||
msgstr "Hast du eine generelle Frage? Vielleicht ist diese schon in den <a href=\"%s\">FAQ</a> beantwortet."
|
||||
|
||||
|
@ -1829,6 +1887,9 @@ msgstr "Antwort"
|
|||
msgid "question.delete.title"
|
||||
msgstr "Frage \"%s\" löschen"
|
||||
|
||||
msgid "question.contact_options"
|
||||
msgstr "Weitere Kontaktmöglichkeiten: "
|
||||
|
||||
msgid "user.edit.shirt"
|
||||
msgstr "T-Shirt bearbeiten"
|
||||
|
||||
|
@ -1911,9 +1972,6 @@ msgstr "Dieser Engeltyp benötigt eine Einweisung bei einem Einführungstreffen.
|
|||
msgid "angeltypes.can-change-later"
|
||||
msgstr "Du kannst Deine Auswahl später in den Einstellungen ändern."
|
||||
|
||||
msgid "angeltypes.email"
|
||||
msgstr "E-Mail"
|
||||
|
||||
msgid "angeltypes.hide_on_shift_view"
|
||||
msgstr "Auf Schicht-Ansicht ausblenden"
|
||||
|
||||
|
@ -1921,6 +1979,9 @@ msgid "angeltypes.hide_on_shift_view.info"
|
|||
msgstr "Wenn ausgewählt, können nur Admins und Mitglieder des Engeltyps auf der "
|
||||
"Schicht Seite die Filteroption für diesen Engeltyp sehen."
|
||||
|
||||
msgid "location.location"
|
||||
msgstr "Ort"
|
||||
|
||||
msgid "location.locations"
|
||||
msgstr "Orte"
|
||||
|
||||
|
@ -2046,3 +2107,44 @@ msgstr "Schicht Ort wurde von %s nach %s verschoben"
|
|||
|
||||
msgid "notification.shift.updated.shift"
|
||||
msgstr "Die aktualisierte Schicht:"
|
||||
|
||||
msgid "admin_shifts.no_locations"
|
||||
msgstr "Es wurde noch kein Ort erstellt. Ohne können keine Schichten erstellt werden."
|
||||
|
||||
msgid "admin_shifts.no_shifttypes"
|
||||
msgstr "Es wurde noch kein Schichttyp erstellt. Ohne können keine Schichten erstellt werden."
|
||||
|
||||
msgid "admin_shifts.no_angeltypes"
|
||||
msgstr "Es wurde noch kein Engeltyp erstellt. Ohne können keine Schichten erstellt werden."
|
||||
|
||||
msgid "shift.sign_out.hint"
|
||||
msgstr "Du kannst dich bis %s Stunden vor dem Start der Schicht austragen. "
|
||||
"Wenn du nicht zu deiner Schicht kommen kannst, lass dich vom Himmel austragen."
|
||||
|
||||
msgid "Password reset in progress"
|
||||
msgstr "Passwort zurücksetzen aktiv"
|
||||
|
||||
msgid "freeload.info.goodie"
|
||||
msgstr "Du warst bei dieser Schicht nicht anwesend. "
|
||||
"Die doppelte Länge der Schicht wurde von deinem %s abgezogen. "
|
||||
"Bitte wende dich bei Fragen an den Himmel."
|
||||
|
||||
msgid "freeload.info"
|
||||
msgstr "Du warst bei dieser Schicht nicht anwesend. "
|
||||
"Bitte wende dich bei Fragen an den Himmel."
|
||||
|
||||
msgid "freeload.freeloader.info"
|
||||
msgstr ""
|
||||
"Du warst bei mindestens %s Schichten nicht anwesend. Deshalb ist die Schicht-Registrierung "
|
||||
"gesperrt. Bitte gehe zum Himmel um wieder entsperrt zu werden."
|
||||
|
||||
msgid "freeload.freeloaded.info.goodie"
|
||||
msgstr ""
|
||||
"War ein Engel bei einer Schicht nicht anwesend, "
|
||||
"wird die doppelte Länge der Schicht von dem %s abgezogen. "
|
||||
"Bei %s geschwänzten Schichten wird die Schicht-Registration für den Engel gesperrt."
|
||||
|
||||
msgid "freeload.freeloaded.info"
|
||||
msgstr ""
|
||||
"Der Engel war bei einer Schicht nicht anwesend. "
|
||||
"Bei %s geschwänzten Schichten wird die Schicht-Registration für den Engel gesperrt."
|
||||
|
|
|
@ -83,9 +83,6 @@ msgstr "The schedule could not be requested."
|
|||
msgid "schedule.import.read-error"
|
||||
msgstr "Unable to parse schedule."
|
||||
|
||||
msgid "schedule.import.invalid-shift-type"
|
||||
msgstr "The shift type can't not be found."
|
||||
|
||||
msgid "schedule.import.success"
|
||||
msgstr "Schedule import successful."
|
||||
|
||||
|
@ -120,7 +117,7 @@ msgid "news.comment-delete.success"
|
|||
msgstr "Comment successfully deleted."
|
||||
|
||||
msgid "news.edit.duplicate"
|
||||
msgstr "Diese News wurde bereits erstellt."
|
||||
msgstr "This news has already been created."
|
||||
|
||||
msgid "news.edit.success"
|
||||
msgstr "News successfully updated."
|
||||
|
|
|
@ -23,7 +23,7 @@ msgid "form.submit"
|
|||
msgstr "Submit"
|
||||
|
||||
msgid "form.send_notification"
|
||||
msgstr "Send notifications"
|
||||
msgstr "Send %d notifications"
|
||||
|
||||
msgid "general.login"
|
||||
msgstr "Login"
|
||||
|
@ -34,6 +34,9 @@ msgstr "DECT"
|
|||
msgid "general.name"
|
||||
msgstr "Name"
|
||||
|
||||
msgid "general.shifts"
|
||||
msgstr "Shifts"
|
||||
|
||||
msgid "general.description"
|
||||
msgstr "Description"
|
||||
|
||||
|
@ -46,6 +49,9 @@ msgstr "You are not allowed to access this page"
|
|||
msgid "page.403.login"
|
||||
msgstr "Please log in."
|
||||
|
||||
msgid "page.error.title"
|
||||
msgstr "Error %s"
|
||||
|
||||
msgid "page.404.title"
|
||||
msgstr "Page not found"
|
||||
|
||||
|
@ -163,6 +169,9 @@ msgstr "Add minutes before talk begins"
|
|||
msgid "schedule.minutes-after"
|
||||
msgstr "Add minutes after talk ends"
|
||||
|
||||
msgid "schedule.for_locations"
|
||||
msgstr "For locations"
|
||||
|
||||
msgid "schedule.import.request_error"
|
||||
msgstr "Unable to load schedule."
|
||||
|
||||
|
@ -248,7 +257,7 @@ msgid "news.comments.delete.title"
|
|||
msgstr "Delete comment \"%s\""
|
||||
|
||||
msgid "notification.news.updated.introduction"
|
||||
msgstr "The news %1$s was updated"
|
||||
msgstr "The news \"%1$s\" was updated"
|
||||
|
||||
msgid "notification.news.updated.text"
|
||||
msgstr "You can view it at %3$s"
|
||||
|
@ -328,7 +337,11 @@ msgstr "Notify me on new private messages."
|
|||
msgid "settings.profile.email_by_human_allowed"
|
||||
msgstr "Allow heaven angels to contact me by e-mail."
|
||||
|
||||
msgid "settings.profile.email_goody"
|
||||
msgid "settings.profile.email_goodie"
|
||||
msgstr "To possibly receive vouchers for the next similar event, I consent "
|
||||
"that my nick, e-mail address and worked hours will be stored until then."
|
||||
|
||||
msgid "settings.profile.email_tshirt"
|
||||
msgstr "To possibly receive vouchers for the next similar event, I consent "
|
||||
"that my nick, e-mail address, worked hours and T-shirt size will be stored until then."
|
||||
|
||||
|
@ -338,6 +351,16 @@ msgstr "To withdraw your approval, send an e-mail to <a href=\"mailto:%s\">%1$s<
|
|||
msgid "settings.profile.shirt_size"
|
||||
msgstr "T-shirt size"
|
||||
|
||||
msgid "settings.profile.shirt_size.hint"
|
||||
msgstr "A straight-cut shirt has wide shoulders and a body which is almost square. "
|
||||
"A fitted-cut t-shirt has a curved side seam which comes in at the waist "
|
||||
"and goes out at the upper and lower ends. "
|
||||
"Normally fitted-cut shirts are smaller then same size straight-cut shirts, "
|
||||
"and sizes differ between brands."
|
||||
|
||||
msgid "settings.profile.shirt.link"
|
||||
msgstr "More information"
|
||||
|
||||
msgid "settings.profile.angeltypes.info"
|
||||
msgstr "You can manage your Angeltypes <a href=\"%s\">on the Angeltypes page</a>."
|
||||
|
||||
|
@ -421,11 +444,31 @@ msgstr "Forklift"
|
|||
msgid "settings.certificates.ifsg_light"
|
||||
msgstr "I was instructed about IfSG §43 (aka Frikadellendiplom light) on site."
|
||||
|
||||
msgid "settings.certificates.ifsg_light_admin"
|
||||
msgstr "Was instructed about IfSG §43 (aka Frikadellendiplom light) on site."
|
||||
|
||||
msgid "settings.certificates.ifsg"
|
||||
msgstr "I have gotten the instruction about §43 IfSG (aka Frikadellendiplom) from my Health Department "
|
||||
"and a second instruction from us or my employer/chef/assosiation within 3 months. "
|
||||
"and a second instruction from us or my employer/chef/association within 3 months. "
|
||||
"Additionally my second instruction is not older than 2 years."
|
||||
|
||||
msgid "settings.certificates.ifsg_admin"
|
||||
msgstr "Got the instruction about §43 IfSG (aka Frikadellendiplom) from a Health Department "
|
||||
"and a second instruction from us or his employer/chef/association within 3 months. "
|
||||
"Additionally the second instruction is not older than 2 years."
|
||||
|
||||
msgid "settings.certificates.confirmed"
|
||||
msgstr "Certificate confirmed"
|
||||
|
||||
msgid "settings.certificates.ifsg_confirmed.hint"
|
||||
msgstr "Your health instruction has been confirmed, you can no longer change it by yourself."
|
||||
|
||||
msgid "settings.certificates.drive_confirmed.hint"
|
||||
msgstr "Your driving license has been confirmed, you can no longer change it by yourself."
|
||||
|
||||
msgid "settings.certificates.confirmation.info"
|
||||
msgstr "You personally checked that the certificate / license meets the requirements."
|
||||
|
||||
msgid "settings.certificates.success"
|
||||
msgstr "Certificates were updated successfully."
|
||||
|
||||
|
@ -433,13 +476,16 @@ msgid "angeltype.ifsg.required"
|
|||
msgstr "Requires health instruction"
|
||||
|
||||
msgid "ifsg.certificate"
|
||||
msgstr "health instruction"
|
||||
msgstr "Health instruction"
|
||||
|
||||
msgid "ifsg.certificate_light"
|
||||
msgstr "health instruction on site"
|
||||
msgstr "Health instruction on site"
|
||||
|
||||
msgid "angeltype.ifsg.own"
|
||||
msgstr "my health instruction"
|
||||
msgstr "My health instruction"
|
||||
|
||||
msgid "angeltype.ifsg.required.info.preview"
|
||||
msgstr "This angeltype requires a health instruction."
|
||||
|
||||
msgid "angeltype.ifsg.required.info"
|
||||
msgstr "This angeltype requires a health instruction. Please enter your health instruction information!"
|
||||
|
@ -453,6 +499,9 @@ msgstr ""
|
|||
"You joined an angeltype which requires a driving license. "
|
||||
"Please edit your driving license information here: %s."
|
||||
|
||||
msgid "angeltype.driving_license.required.info.preview"
|
||||
msgstr "This angeltype requires a driver's license."
|
||||
|
||||
msgid "ifsg.info"
|
||||
msgstr "Health instruction information"
|
||||
|
||||
|
@ -473,11 +522,14 @@ msgstr "API"
|
|||
|
||||
msgid "settings.api.about"
|
||||
msgstr ""
|
||||
"The API allows you to interact with the Engelsystem by using external programs. "
|
||||
"The API allows you to interact with the %s by using external programs. "
|
||||
"It's not complete but we are working on extending it.\n"
|
||||
"The API endpoint is located at `%s` and described in the [OpenAPI specification](%s).\n"
|
||||
"The endpoint of the API is located at `%s` and described in the [OpenAPI specification](%s)."
|
||||
|
||||
msgid "settings.api.about.warning"
|
||||
msgstr ""
|
||||
"Don't share your personal API key with anyone as it can be used to view your personal data "
|
||||
"and do changes your behalf!"
|
||||
"and do changes on your behalf!"
|
||||
|
||||
msgid "settings.api.shifts_json_show"
|
||||
msgstr "Show JSON shifts export"
|
||||
|
@ -539,6 +591,9 @@ msgstr "Delete FAQ \"%s\""
|
|||
msgid "question.questions"
|
||||
msgstr "Questions"
|
||||
|
||||
msgid "question.admin"
|
||||
msgstr "Answer questions"
|
||||
|
||||
msgid "question.faq_link"
|
||||
msgstr "For general questions, have a look at our <a href=\"%s\">FAQ</a>."
|
||||
|
||||
|
@ -557,15 +612,15 @@ msgstr "Answer"
|
|||
msgid "question.delete.title"
|
||||
msgstr "Delete question \"%s\""
|
||||
|
||||
msgid "question.contact_options"
|
||||
msgstr "Other contact options: "
|
||||
|
||||
msgid "user.edit.shirt"
|
||||
msgstr "Edit T-shirt"
|
||||
|
||||
msgid "user.edit.goodie"
|
||||
msgstr "Edit goodie"
|
||||
|
||||
msgid "form.shirt"
|
||||
msgstr "T-shirt"
|
||||
|
||||
msgid "user.shirt_size"
|
||||
msgstr "T-shirt size"
|
||||
|
||||
|
@ -646,11 +701,8 @@ msgstr "This angeltype requires the attendance at an introduction meeting. "
|
|||
msgid "angeltypes.can-change-later"
|
||||
msgstr "You can change your selection later in the settings."
|
||||
|
||||
msgid "angeltypes.email"
|
||||
msgstr "E-mail"
|
||||
|
||||
msgid "angeltypes.shift.self_signup.info"
|
||||
msgstr "Angel types which have shift self signup enabled allow angels to self sign up for there shifts, "
|
||||
msgstr "Angel types which have shift self signup enabled allow angels to self sign up for their shifts, "
|
||||
"if shift self signup is disabled only supporters and admins can sign angels into shifts of these angel types."
|
||||
|
||||
msgid "shift.self_signup"
|
||||
|
@ -669,6 +721,9 @@ msgstr "If checked only admins and members of the angeltype "
|
|||
msgid "registration.register"
|
||||
msgstr "Register"
|
||||
|
||||
msgid "location.location"
|
||||
msgstr "Location"
|
||||
|
||||
msgid "location.locations"
|
||||
msgstr "Locations"
|
||||
|
||||
|
@ -765,7 +820,7 @@ msgid "general.created_at"
|
|||
msgstr "Created at"
|
||||
|
||||
msgid "shifts.random"
|
||||
msgstr "Zufällige Schicht"
|
||||
msgstr "Random shift"
|
||||
|
||||
msgid "shifts.history"
|
||||
msgstr "Shifts history"
|
||||
|
@ -834,6 +889,18 @@ msgstr "Register"
|
|||
msgid "shift.next"
|
||||
msgstr "Next shift"
|
||||
|
||||
msgid "general.shift"
|
||||
msgstr "Shift"
|
||||
|
||||
msgid "shift.angeltype_source"
|
||||
msgstr "Needed angels from: %s"
|
||||
|
||||
msgid "shift.angeltype_source.shift_type"
|
||||
msgstr "Schedule %s via shift type %s"
|
||||
|
||||
msgid "shift.angeltype_source.location"
|
||||
msgstr "Schedule %s via location %s"
|
||||
|
||||
msgid "general.logout"
|
||||
msgstr "Logout"
|
||||
|
||||
|
@ -934,3 +1001,38 @@ msgstr "Actions"
|
|||
|
||||
msgid "general.back"
|
||||
msgstr "Back"
|
||||
|
||||
msgid "admin_shifts.no_locations"
|
||||
msgstr "No location has been created yet. Shifts can't be created without one."
|
||||
|
||||
msgid "admin_shifts.no_shifttypes"
|
||||
msgstr "No shift type has been created yet. Shifts can't be created without one."
|
||||
|
||||
msgid "admin_shifts.no_angeltypes"
|
||||
msgstr "No angeltype has been created yet. Shifts can't be created without one."
|
||||
|
||||
msgid "shift.sign_out.hint"
|
||||
msgstr "You can self sign out up to %s hours before the start of the shift. "
|
||||
"If you can't attend your shift, ask heaven to sign you out."
|
||||
|
||||
msgid "freeload.info.goodie"
|
||||
msgstr "You were not present for this shift. "
|
||||
"The double length of the shift was deducted from your %s. Please contact heaven if you have questions."
|
||||
|
||||
msgid "freeload.info"
|
||||
msgstr "You were not present for this shift. "
|
||||
"Please contact heaven if you have questions."
|
||||
|
||||
msgid "freeload.freeloader.info"
|
||||
msgstr "You were not present for at least %s shifts. Therefore, shift registration is blocked. "
|
||||
"Please go to heaven to be unlocked again."
|
||||
|
||||
msgid "freeload.freeloaded.info.goodie"
|
||||
msgstr ""
|
||||
"If an angel was not present for a shift, the double length of the shift is deducted from the %s. "
|
||||
"If %s shifts are freeloaded, the shift registration is blocked for the angel."
|
||||
|
||||
msgid "freeload.freeloaded.info"
|
||||
msgstr ""
|
||||
"The angel was not present for a shift. "
|
||||
"If %s shifts are freeloaded, the shift registration is blocked for the angel."
|
||||
|
|
|
@ -8,15 +8,15 @@
|
|||
<div class="container">
|
||||
<h1>
|
||||
{% if not is_index|default(false) %}
|
||||
{{ m.button(m.icon('chevron-left'), location
|
||||
{{ m.back(location
|
||||
? url('/locations', {'action': 'view', 'location_id': location.id})
|
||||
: url('/admin/locations'), 'secondary', 'sm', __('general.back')) }}
|
||||
: url('/admin/locations')) }}
|
||||
{% endif %}
|
||||
|
||||
{{ block('title') }}
|
||||
|
||||
{% if is_index|default(false) %}
|
||||
{{ m.button(m.icon('plus-lg'), url('/admin/locations/edit'), 'secondary') }}
|
||||
{{ m.button(m.icon('plus-lg'), url('/admin/locations/edit')) }}
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
|
@ -33,6 +33,7 @@
|
|||
<th>{{ __('general.name') }}</th>
|
||||
<th>{{ __('general.dect') }}</th>
|
||||
<th>{{ __('location.map_url') }}</th>
|
||||
<th>{{ __('general.shifts') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -51,10 +52,12 @@
|
|||
|
||||
<td>{{ m.iconBool(location.map_url) }}</td>
|
||||
|
||||
<td>{{ m.iconBool(location.shifts.count) }}</td>
|
||||
|
||||
<td>
|
||||
<div class="d-flex ms-auto">
|
||||
|
||||
{{ m.button(m.icon('pencil'), url('/admin/locations/edit/' ~ location.id), null, 'sm', __('form.edit')) }}
|
||||
{{ m.edit(url('/admin/locations/edit/' ~ location.id)) }}
|
||||
|
||||
<form method="post" class="ps-1">
|
||||
{{ csrf() }}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
}) }}
|
||||
</div>
|
||||
|
||||
{% if has_permission_to('logs.all') %}
|
||||
{% if can('logs.all') %}
|
||||
<div class="col-md-4">
|
||||
{{ f.select('search_user_id', __('general.user'), users, {
|
||||
'default_option': __('form.user_select'),
|
||||
|
@ -36,7 +36,7 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
{% if not has_permission_to('logs.all') %}
|
||||
{% if not can('logs.all') %}
|
||||
<div class="mb-3">
|
||||
{{ m.alert(__('log.only_own')) }}
|
||||
</div>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue