Implement new sign up page
This commit is contained in:
parent
c2dd25fc7c
commit
4329ee4af9
|
@ -8,6 +8,8 @@ use FastRoute\RouteCollector;
|
|||
|
||||
// Pages
|
||||
$route->get('/', 'HomeController@index');
|
||||
$route->get('/sign-up', 'SignUpController@view');
|
||||
$route->post('/sign-up', 'SignUpController@save');
|
||||
$route->get('/credits', 'CreditsController@index');
|
||||
$route->get('/health', 'HealthController@index');
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories\Engelsystem\Models;
|
||||
|
||||
use Engelsystem\Models\OAuth;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class OAuthFactory extends Factory
|
||||
{
|
||||
/** @var class-string */
|
||||
protected $model = OAuth::class; // phpcs:ignore
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'provider' => $this->faker->unique()->word(),
|
||||
'identifier' => $this->faker->unique()->word(),
|
||||
'access_token' => $this->faker->unique()->word(),
|
||||
'refresh_token' => $this->faker->unique()->word(),
|
||||
'expires_at' => '2099-12-31',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -62,7 +62,6 @@ $includeFiles = [
|
|||
__DIR__ . '/../includes/pages/admin_groups.php',
|
||||
__DIR__ . '/../includes/pages/admin_shifts.php',
|
||||
__DIR__ . '/../includes/pages/admin_user.php',
|
||||
__DIR__ . '/../includes/pages/guest_login.php',
|
||||
__DIR__ . '/../includes/pages/user_myshifts.php',
|
||||
__DIR__ . '/../includes/pages/user_shifts.php',
|
||||
|
||||
|
|
|
@ -1,543 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Engelsystem\Database\Database;
|
||||
use Engelsystem\Events\Listener\OAuth2;
|
||||
use Engelsystem\Config\GoodieType;
|
||||
use Engelsystem\Http\Validation\Rules\Username;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Group;
|
||||
use Engelsystem\Models\OAuth;
|
||||
use Engelsystem\Models\User\Contact;
|
||||
use Engelsystem\Models\User\PersonalData;
|
||||
use Engelsystem\Models\User\Settings;
|
||||
use Engelsystem\Models\User\State;
|
||||
use Engelsystem\Models\User\User;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function register_title()
|
||||
{
|
||||
return __('Register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Engel registrieren
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function guest_register()
|
||||
{
|
||||
$authUser = auth()->user();
|
||||
$tshirt_sizes = config('tshirt_sizes');
|
||||
$goodie = GoodieType::from(config('goodie_type'));
|
||||
$goodie_enabled = $goodie !== GoodieType::None;
|
||||
$goodie_tshirt = $goodie === GoodieType::Tshirt;
|
||||
$enable_user_name = config('enable_user_name');
|
||||
$enable_dect = config('enable_dect');
|
||||
$enable_planned_arrival = config('enable_planned_arrival');
|
||||
$min_password_length = config('min_password_length');
|
||||
$enable_password = config('enable_password');
|
||||
$enable_pronoun = config('enable_pronoun');
|
||||
$enable_mobile_show = config('enable_mobile_show');
|
||||
$config = config();
|
||||
$request = request();
|
||||
$session = session();
|
||||
/** @var Connection $db */
|
||||
$db = app(Database::class)->getConnection();
|
||||
$is_oauth = $session->has('oauth2_connect_provider');
|
||||
|
||||
$msg = '';
|
||||
$nick = '';
|
||||
$lastName = '';
|
||||
$preName = '';
|
||||
$dect = '';
|
||||
$mobile = '';
|
||||
$mobile_show = false;
|
||||
$email = '';
|
||||
$pronoun = '';
|
||||
$email_shiftinfo = false;
|
||||
$email_by_human_allowed = false;
|
||||
$email_messages = false;
|
||||
$email_news = false;
|
||||
$email_goody = false;
|
||||
$tshirt_size = '';
|
||||
$password_hash = '';
|
||||
$selected_angel_types = [];
|
||||
$planned_arrival_date = null;
|
||||
|
||||
/** @var AngelType[]|Collection $angel_types_source */
|
||||
$angel_types_source = AngelType::all();
|
||||
$angel_types = [];
|
||||
if (!empty($session->get('oauth2_groups'))) {
|
||||
/** @var OAuth2 $oauth */
|
||||
$oauth = app()->get(OAuth2::class);
|
||||
$ssoTeams = $oauth->getSsoTeams($session->get('oauth2_connect_provider'));
|
||||
foreach ($ssoTeams as $name => $team) {
|
||||
if (in_array($name, $session->get('oauth2_groups'))) {
|
||||
$selected_angel_types[] = $team['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($angel_types_source as $angel_type) {
|
||||
if ($angel_type->hide_register) {
|
||||
continue;
|
||||
}
|
||||
$angel_types[$angel_type->id] = $angel_type->name
|
||||
. ($angel_type->restricted ? ' (' . __('Requires introduction') . ')' : '');
|
||||
if (!$angel_type->restricted) {
|
||||
$selected_angel_types[] = $angel_type->id;
|
||||
}
|
||||
}
|
||||
|
||||
$oauth_enable_password = $session->get('oauth2_enable_password');
|
||||
if (!is_null($oauth_enable_password)) {
|
||||
$enable_password = $oauth_enable_password;
|
||||
}
|
||||
|
||||
if (
|
||||
!auth()->can('register') // No registration permission
|
||||
// Not authenticated and
|
||||
|| (!$authUser && !config('registration_enabled') && !$session->get('oauth2_allow_registration')) // Registration disabled
|
||||
|| (!$authUser && !$enable_password && !$is_oauth) // Password disabled and not oauth
|
||||
) {
|
||||
error(__('Registration is disabled.'));
|
||||
|
||||
return page_with_title(register_title(), [
|
||||
msg(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->hasPostData('submit')) {
|
||||
$valid = true;
|
||||
|
||||
if ($request->has('username')) {
|
||||
$nick = trim($request->get('username'));
|
||||
$nickValid = (new Username())->validate($nick);
|
||||
|
||||
if (!$nickValid) {
|
||||
$valid = false;
|
||||
$msg .= error(sprintf(
|
||||
__('Please enter a valid nick.') . ' ' . __('Use up to 24 letters, numbers or connecting punctuations for your nickname.'),
|
||||
$nick
|
||||
), true);
|
||||
}
|
||||
if (User::whereName($nick)->count() > 0) {
|
||||
$valid = false;
|
||||
$msg .= error(sprintf(__('Your nick "%s" already exists.'), htmlspecialchars($nick)), true);
|
||||
}
|
||||
} else {
|
||||
$valid = false;
|
||||
$msg .= error(__('Please enter a nickname.'), true);
|
||||
}
|
||||
|
||||
if ($request->has('mobile_show') && $enable_mobile_show) {
|
||||
$mobile_show = true;
|
||||
}
|
||||
|
||||
if ($request->has('email') && strlen(strip_request_item('email')) > 0) {
|
||||
$email = strip_request_item('email');
|
||||
if (!check_email($email)) {
|
||||
$valid = false;
|
||||
$msg .= error(__('E-mail address is not correct.'), true);
|
||||
}
|
||||
if (User::whereEmail($email)->first()) {
|
||||
$valid = false;
|
||||
$msg .= error(__('E-mail address is already used by another user.'), true);
|
||||
}
|
||||
} else {
|
||||
$valid = false;
|
||||
$msg .= error(__('Please enter your e-mail.'), true);
|
||||
}
|
||||
|
||||
if ($request->has('email_shiftinfo')) {
|
||||
$email_shiftinfo = true;
|
||||
}
|
||||
|
||||
if ($request->has('email_by_human_allowed')) {
|
||||
$email_by_human_allowed = true;
|
||||
}
|
||||
|
||||
if ($request->has('email_messages')) {
|
||||
$email_messages = true;
|
||||
}
|
||||
|
||||
if ($request->has('email_news')) {
|
||||
$email_news = true;
|
||||
}
|
||||
|
||||
if ($request->has('email_goody')) {
|
||||
$email_goody = true;
|
||||
}
|
||||
|
||||
if ($goodie_tshirt) {
|
||||
if ($request->has('tshirt_size') && isset($tshirt_sizes[$request->input('tshirt_size')])) {
|
||||
$tshirt_size = $request->input('tshirt_size');
|
||||
} else {
|
||||
$valid = false;
|
||||
$msg .= error(__('Please select your shirt size.'), true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($enable_password && $request->has('password') && strlen($request->postData('password')) >= $min_password_length) {
|
||||
if ($request->postData('password') != $request->postData('password2')) {
|
||||
$valid = false;
|
||||
$msg .= error(__('Your passwords don\'t match.'), true);
|
||||
}
|
||||
} elseif ($enable_password) {
|
||||
$valid = false;
|
||||
$msg .= error(sprintf(
|
||||
__('Your password is too short (please use at least %s characters).'),
|
||||
$min_password_length
|
||||
), true);
|
||||
}
|
||||
|
||||
if ($request->has('planned_arrival_date') && $enable_planned_arrival) {
|
||||
$tmp = parse_date('Y-m-d H:i', $request->input('planned_arrival_date') . ' 00:00');
|
||||
$result = User_validate_planned_arrival_date($tmp);
|
||||
$planned_arrival_date = $result->getValue();
|
||||
if (!$result->isValid()) {
|
||||
$valid = false;
|
||||
error(__('Please enter your planned date of arrival. It should be after the buildup start date and before teardown end date.'));
|
||||
}
|
||||
} elseif ($enable_planned_arrival) {
|
||||
$valid = false;
|
||||
error(__('Please enter your planned date of arrival. It should be after the buildup start date and before teardown end date.'));
|
||||
}
|
||||
|
||||
$selected_angel_types = [];
|
||||
foreach (array_keys($angel_types) as $angel_type_id) {
|
||||
if ($request->has('angel_types_' . $angel_type_id)) {
|
||||
$selected_angel_types[] = $angel_type_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Trivia
|
||||
if ($enable_user_name && $request->has('lastname')) {
|
||||
$lastName = strip_request_item('lastname');
|
||||
}
|
||||
if ($enable_user_name && $request->has('prename')) {
|
||||
$preName = strip_request_item('prename');
|
||||
}
|
||||
if ($enable_pronoun && $request->has('pronoun')) {
|
||||
$pronoun = strip_request_item('pronoun');
|
||||
}
|
||||
if ($enable_dect && $request->has('dect')) {
|
||||
if (strlen(strip_request_item('dect')) <= 40) {
|
||||
$dect = strip_request_item('dect');
|
||||
} else {
|
||||
$valid = false;
|
||||
error(__('For dect numbers are only 40 digits allowed.'));
|
||||
}
|
||||
}
|
||||
if ($request->has('mobile')) {
|
||||
$mobile = strip_request_item('mobile');
|
||||
}
|
||||
|
||||
if ($valid) {
|
||||
// Safeguard against partially created user data
|
||||
$db->beginTransaction();
|
||||
|
||||
$user = new User([
|
||||
'name' => $nick,
|
||||
'password' => $password_hash,
|
||||
'email' => $email,
|
||||
'api_key' => '',
|
||||
'last_login_at' => null,
|
||||
]);
|
||||
$user->save();
|
||||
|
||||
$contact = new Contact([
|
||||
'dect' => $dect,
|
||||
'mobile' => $mobile,
|
||||
]);
|
||||
$contact->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$personalData = new PersonalData([
|
||||
'first_name' => $preName,
|
||||
'last_name' => $lastName,
|
||||
'pronoun' => $pronoun,
|
||||
'shirt_size' => $tshirt_size,
|
||||
'planned_arrival_date' => $enable_planned_arrival ? Carbon::createFromTimestamp($planned_arrival_date) : null,
|
||||
]);
|
||||
$personalData->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$settings = new Settings([
|
||||
'language' => $session->get('locale'),
|
||||
'theme' => config('theme'),
|
||||
'email_human' => $email_by_human_allowed,
|
||||
'email_messages' => $email_messages,
|
||||
'email_goody' => $email_goody,
|
||||
'email_shiftinfo' => $email_shiftinfo,
|
||||
'email_news' => $email_news,
|
||||
'mobile_show' => $mobile_show,
|
||||
]);
|
||||
$settings->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$state = new State([]);
|
||||
if (config('autoarrive')) {
|
||||
$state->arrived = true;
|
||||
$state->arrival_date = new Carbon();
|
||||
}
|
||||
$state->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
if ($session->has('oauth2_connect_provider') && $session->has('oauth2_user_id')) {
|
||||
$oauth = new OAuth([
|
||||
'provider' => $session->get('oauth2_connect_provider'),
|
||||
'identifier' => $session->get('oauth2_user_id'),
|
||||
'access_token' => $session->get('oauth2_access_token'),
|
||||
'refresh_token' => $session->get('oauth2_refresh_token'),
|
||||
'expires_at' => $session->get('oauth2_expires_at'),
|
||||
]);
|
||||
$oauth->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$session->remove('oauth2_connect_provider');
|
||||
$session->remove('oauth2_user_id');
|
||||
$session->remove('oauth2_access_token');
|
||||
$session->remove('oauth2_refresh_token');
|
||||
$session->remove('oauth2_expires_at');
|
||||
}
|
||||
|
||||
// Assign user-group and set password
|
||||
$defaultGroup = Group::find(auth()->getDefaultRole());
|
||||
$user->groups()->attach($defaultGroup);
|
||||
if ($enable_password) {
|
||||
auth()->setPassword($user, $request->postData('password'));
|
||||
}
|
||||
|
||||
// Assign angel-types
|
||||
$user_angel_types_info = [];
|
||||
foreach ($selected_angel_types as $selected_angel_type_id) {
|
||||
$angelType = AngelType::findOrFail($selected_angel_type_id);
|
||||
$user->userAngelTypes()->attach($angelType);
|
||||
$user_angel_types_info[] = $angelType->name;
|
||||
}
|
||||
|
||||
// Commit complete user data
|
||||
$db->commit();
|
||||
|
||||
engelsystem_log(
|
||||
'User ' . User_Nick_render($user, true)
|
||||
. ' signed up as: ' . join(', ', $user_angel_types_info)
|
||||
);
|
||||
success(__('Angel registration successful!'));
|
||||
|
||||
// User is already logged in - that means a supporter has registered an angel. Return to register page.
|
||||
if ($authUser) {
|
||||
throw_redirect(page_link_to('register'));
|
||||
}
|
||||
|
||||
// If a welcome message is present, display it on the next page
|
||||
if ($config->get('welcome_msg')) {
|
||||
$session->set('show_welcome', true);
|
||||
}
|
||||
|
||||
// Login the user
|
||||
if ($user->oauth->count()) {
|
||||
/** @var OAuth $provider */
|
||||
$provider = $user->oauth->first();
|
||||
throw_redirect(url('/oauth/' . $provider->provider));
|
||||
}
|
||||
|
||||
throw_redirect(page_link_to('/'));
|
||||
}
|
||||
}
|
||||
|
||||
$buildup_start_date = time();
|
||||
$teardown_end_date = null;
|
||||
if ($buildup = $config->get('buildup_start')) {
|
||||
/** @var Carbon $buildup */
|
||||
$buildup_start_date = $buildup->getTimestamp();
|
||||
}
|
||||
|
||||
if ($teardown = $config->get('teardown_end')) {
|
||||
/** @var Carbon $teardown */
|
||||
$teardown_end_date = $teardown->getTimestamp();
|
||||
}
|
||||
|
||||
$form_data = $session->get('form_data');
|
||||
$session->remove('form_data');
|
||||
if (!$nick && !empty($form_data['name'])) {
|
||||
$nick = $form_data['name'];
|
||||
}
|
||||
|
||||
if (!$email && !empty($form_data['email'])) {
|
||||
$email = $form_data['email'];
|
||||
}
|
||||
|
||||
if (!$preName && !empty($form_data['first_name'])) {
|
||||
$preName = $form_data['first_name'];
|
||||
}
|
||||
|
||||
if (!$lastName && !empty($form_data['last_name'])) {
|
||||
$lastName = $form_data['last_name'];
|
||||
}
|
||||
|
||||
return page_with_title(register_title(), [
|
||||
__('By completing this form you\'re registering as a Chaos-Angel. This script will create you an account in the angel task scheduler.'),
|
||||
form_info(entry_required() . ' = ' . __('Entry required!')),
|
||||
$msg,
|
||||
msg(),
|
||||
form([
|
||||
div('row', [
|
||||
div('col', [
|
||||
form_text(
|
||||
'username',
|
||||
__('Nick') . ' ' . entry_required(),
|
||||
$nick,
|
||||
false,
|
||||
24,
|
||||
'nickname'
|
||||
),
|
||||
form_info(
|
||||
'',
|
||||
__('Use up to 24 letters, numbers or connecting punctuations for your nickname.')
|
||||
),
|
||||
]),
|
||||
|
||||
$enable_pronoun ? div('col', [
|
||||
form_text('pronoun', __('Pronoun'), $pronoun, false, 15),
|
||||
]) : '',
|
||||
]),
|
||||
|
||||
$enable_user_name ? div('row', [
|
||||
div('col', [
|
||||
form_text('prename', __('First name'), $preName, false, 64, 'given-name'),
|
||||
]),
|
||||
div('col', [
|
||||
form_text('lastname', __('Last name'), $lastName, false, 64, 'family-name'),
|
||||
]),
|
||||
]) : '',
|
||||
|
||||
div('row', [
|
||||
div('col', [
|
||||
form_email(
|
||||
'email',
|
||||
__('E-Mail') . ' ' . entry_required(),
|
||||
$email,
|
||||
false,
|
||||
'email',
|
||||
254
|
||||
),
|
||||
form_checkbox(
|
||||
'email_shiftinfo',
|
||||
__(
|
||||
'settings.profile.email_shiftinfo',
|
||||
[config('app_name')]
|
||||
),
|
||||
$email_shiftinfo
|
||||
),
|
||||
form_checkbox(
|
||||
'email_news',
|
||||
__('Notify me of new news'),
|
||||
$email_news
|
||||
),
|
||||
form_checkbox(
|
||||
'email_messages',
|
||||
__('settings.profile.email_messages'),
|
||||
$email_messages
|
||||
),
|
||||
form_checkbox(
|
||||
'email_by_human_allowed',
|
||||
__('Allow heaven angels to contact you by e-mail.'),
|
||||
$email_by_human_allowed
|
||||
),
|
||||
$goodie_enabled ?
|
||||
form_checkbox(
|
||||
'email_goody',
|
||||
__('To receive vouchers, give consent that nick, email address, worked hours and shirt size will be stored until the next similar event.')
|
||||
. (config('privacy_email') ? ' ' . __('To withdraw your approval, send an email to <a href="mailto:%s">%1$s</a>.', [config('privacy_email')]) : ''),
|
||||
$email_goody
|
||||
) : '',
|
||||
]),
|
||||
|
||||
$enable_dect ? div('col', [
|
||||
form_text('dect', __('DECT'), $dect, false, 40, 'tel-local'),
|
||||
]) : '',
|
||||
|
||||
div('col', [
|
||||
form_text('mobile', __('Mobile'), $mobile, false, 40, 'tel-national'),
|
||||
$enable_mobile_show ? form_checkbox(
|
||||
'mobile_show',
|
||||
__('Show mobile number to other users to contact me'),
|
||||
$mobile_show
|
||||
) : '',
|
||||
]),
|
||||
]),
|
||||
|
||||
div('row', [
|
||||
$enable_password ? div('col', [
|
||||
form_password('password', __('Password') . ' ' . entry_required(), 'new-password'),
|
||||
]) : '',
|
||||
|
||||
$enable_planned_arrival ? div('col', [
|
||||
form_date(
|
||||
'planned_arrival_date',
|
||||
__('Planned date of arrival') . ' ' . entry_required(),
|
||||
$planned_arrival_date,
|
||||
$buildup_start_date,
|
||||
$teardown_end_date
|
||||
),
|
||||
]) : '',
|
||||
]),
|
||||
|
||||
div('row', [
|
||||
$enable_password ? div('col', [
|
||||
form_password('password2', __('Confirm password') . ' ' . entry_required(), 'new-password'),
|
||||
]) : '',
|
||||
|
||||
div('col', [
|
||||
$goodie_tshirt ? form_select(
|
||||
'tshirt_size',
|
||||
__('Shirt size') . ' ' . entry_required(),
|
||||
$tshirt_sizes,
|
||||
$tshirt_size,
|
||||
__('form.select_placeholder')
|
||||
) : '',
|
||||
]),
|
||||
]),
|
||||
|
||||
div('row', [
|
||||
div('col', [
|
||||
form_checkboxes(
|
||||
'angel_types',
|
||||
__('What do you want to do?') . sprintf(
|
||||
' (<a href="%s">%s</a>)',
|
||||
url('/angeltypes/about'),
|
||||
__('Description of job types')
|
||||
),
|
||||
$angel_types,
|
||||
$selected_angel_types
|
||||
),
|
||||
form_info(
|
||||
'',
|
||||
__('Some angel types have to be confirmed later by a supporter at an introduction meeting. You can change your selection in the options section.')
|
||||
),
|
||||
]),
|
||||
]),
|
||||
|
||||
form_submit('submit', __('Register')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function entry_required()
|
||||
{
|
||||
return icon('exclamation-triangle', 'text-info');
|
||||
}
|
|
@ -107,6 +107,10 @@ table,
|
|||
}
|
||||
}
|
||||
|
||||
.list-group .form-check-input {
|
||||
border-color: $list-group-form-check-input-border-color;
|
||||
}
|
||||
|
||||
// Navs =======================================================================
|
||||
|
||||
.nav-tabs,
|
||||
|
|
|
@ -150,6 +150,9 @@ $input-border-focus: #66afe9 !default;
|
|||
//** Placeholder text color
|
||||
$input-color-placeholder: $gray-light !default;
|
||||
|
||||
$form-check-input-border: 1px solid $input-border-color !default;
|
||||
$list-group-form-check-input-border-color: $input-border-color !default;
|
||||
|
||||
$legend-color: $text-color !default;
|
||||
$legend-border-color: $gray-dark !default;
|
||||
|
||||
|
@ -513,10 +516,11 @@ $progress-bar-info-bg: $info !default;
|
|||
//
|
||||
//##
|
||||
|
||||
$list-group-color: $input-color !default;
|
||||
//** Background color on `.list-group-item`
|
||||
$list-group-bg: $gray-darker !default;
|
||||
//** `.list-group-item` border color
|
||||
$list-group-border: $gray-dark !default;
|
||||
$list-group-border-color: $input-border-color !default;
|
||||
//** List group border radius
|
||||
$list-group-border-radius: $border-radius-base !default;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ $input-disabled-bg: #111;
|
|||
$alert-bg-scale: 70%;
|
||||
$secondary: #222;
|
||||
$table-striped-bg: rgba(#fff, 0.05);
|
||||
$list-group-form-check-input-border-color: #999;
|
||||
|
||||
$es-choices-highlight-color: #000;
|
||||
|
||||
|
|
|
@ -17,19 +17,45 @@ msgstr ""
|
|||
msgid "validation.password.required"
|
||||
msgstr "Bitte gib ein Passwort an."
|
||||
|
||||
msgid "validation.password.length"
|
||||
msgstr "Das angegebene Passwort ist zu kurz."
|
||||
|
||||
msgid "validation.login.required"
|
||||
msgstr "Bitte gib einen Loginnamen an."
|
||||
|
||||
msgid "validation.pronoun.optional"
|
||||
msgstr "Das Pronomen, das Du eingegeben hast, ist zu lang. Verwende maximal 15 Zeichen."
|
||||
|
||||
msgid "validation.firstname.optional"
|
||||
msgstr "Der von dir eingegebene Vorname ist zu lang. Verwende maximal 64 Zeichen."
|
||||
|
||||
msgid "validation.lastname.optional"
|
||||
msgstr "Der von dir eingegebene Vorname ist zu lang. Verwende maximal 64 Zeichen."
|
||||
|
||||
msgid "validation.mobile.optional"
|
||||
msgstr "Der von dir eingegebene Handynummer ist zu lang. Verwende maximal 40 Zeichen."
|
||||
|
||||
msgid "validation.dect.optional"
|
||||
msgstr "Der von dir eingegebene DECT-Nummer ist zu lang. Verwende maximal 40 Zeichen."
|
||||
|
||||
msgid "validation.username.required"
|
||||
msgstr "Bitte gebe deinen Nick an."
|
||||
|
||||
msgid "validation.username.username"
|
||||
msgstr ""
|
||||
"Bitte gebe einen gültigen Nick ein: "
|
||||
"Verwende bis zu 24 Buchstaben, Zahlen oder verbindende Schriftzeichen (.-_) für deinen Nick."
|
||||
|
||||
msgid "validation.email.required"
|
||||
msgstr "Bitte gib eine E-Mail-Adresse an."
|
||||
|
||||
msgid "validation.email.email"
|
||||
msgstr "Die E-Mail-Adresse ist nicht gültig."
|
||||
|
||||
msgid "validation.password.min"
|
||||
msgid "validation.password.length"
|
||||
msgstr "Dein neues Passwort ist zu kurz."
|
||||
|
||||
msgid "validation.new_password.min"
|
||||
msgid "validation.new_password.length"
|
||||
msgstr "Dein neues Passwort ist zu kurz."
|
||||
|
||||
msgid "validation.password.confirmed"
|
||||
|
@ -38,6 +64,21 @@ msgstr "Deine Passwörter stimmen nicht überein."
|
|||
msgid "validation.password_confirmation.required"
|
||||
msgstr "Du musst dein Passwort bestätigen."
|
||||
|
||||
msgid "validation.tshirt_size.required"
|
||||
msgstr "Bitte wähle deine T-Shirt-Größe aus."
|
||||
|
||||
msgid "validation.tshirt_size.shirtSize"
|
||||
msgstr "Bitte wähle eine gültige T-Shirt-Größe aus."
|
||||
|
||||
msgid "validation.planned_arrival_date.required"
|
||||
msgstr "Bitte gebe dein geplantes Ankunftsdatum an."
|
||||
|
||||
msgid "validation.planned_arrival_date.min"
|
||||
msgstr "Das geplante Ankunftsdatum darf nicht vor Aufbaubeginn liegen."
|
||||
|
||||
msgid "validation.planned_arrival_date.between"
|
||||
msgstr "Das geplante Ankunftsdatum muss zwischen Aufbaubeginn und Abbauende liegen."
|
||||
|
||||
msgid "schedule.edit.success"
|
||||
msgstr "Das Programm wurde erfolgreich konfiguriert."
|
||||
|
||||
|
|
|
@ -1020,15 +1020,6 @@ msgstr ""
|
|||
msgid "Edit user"
|
||||
msgstr "User bearbeiten"
|
||||
|
||||
msgid "Please enter a valid nick."
|
||||
msgstr "Gib bitte einen erlaubten Nick an."
|
||||
|
||||
msgid "Use up to 24 letters, numbers or connecting punctuations for your nickname."
|
||||
msgstr "Verwende bis zu 24 Buchstaben, Zahlen oder verbindende Schriftzeichen (.-_) für deinen Nick."
|
||||
|
||||
msgid "Your nick \"%s\" already exists."
|
||||
msgstr "Der Nick \"%s\" existiert bereits."
|
||||
|
||||
msgid "Please enter a nickname."
|
||||
msgstr "Gib bitte einen Nick an."
|
||||
|
||||
|
@ -1067,9 +1058,6 @@ msgstr ""
|
|||
"Mit diesem Formular registrierst Du Dich als Engel. Du bekommst ein Konto in "
|
||||
"der Engel-Aufgabenverwaltung."
|
||||
|
||||
msgid "Notify me of new news"
|
||||
msgstr "Benachrichtige mich bei neuen News"
|
||||
|
||||
msgid "Allow heaven angels to contact you by e-mail."
|
||||
msgstr "Erlaube Himmel-Engeln dich per Mail zu kontaktieren."
|
||||
|
||||
|
@ -1082,25 +1070,6 @@ msgstr "Um Voucher zu erhalten, stimme zu, dass Nick, E-Mail-Adresse, geleistete
|
|||
msgid "To withdraw your approval, send an email to <a href=\"mailto:%s\">%1$s</a>."
|
||||
msgstr "Dies kann jederzeit durch eine E-Mail an <a href=\"mailto:%s\">%1$s</a> widerrufen werden."
|
||||
|
||||
msgid "Planned date of arrival"
|
||||
msgstr "Geplanter Ankunftstag"
|
||||
|
||||
msgid "Shirt size"
|
||||
msgstr "T-Shirt Größe"
|
||||
|
||||
msgid "What do you want to do?"
|
||||
msgstr "Was möchtest Du machen?"
|
||||
|
||||
msgid "Description of job types"
|
||||
msgstr "Beschreibung der Aufgaben"
|
||||
|
||||
msgid ""
|
||||
"Some angel types have to be confirmed later by a supporter at an "
|
||||
"introduction meeting. You can change your selection in the options section."
|
||||
msgstr ""
|
||||
"Engeltypen welche eine Einführung benötigen, werden bei einem Einführungstreffen von "
|
||||
"einem Supporter freigeschaltet. Du kannst Deine Auswahl später in den Einstellungen ändern."
|
||||
|
||||
msgid "Mobile"
|
||||
msgstr "Handy"
|
||||
|
||||
|
@ -2134,6 +2103,9 @@ msgstr "Pflichtfeld"
|
|||
msgid "settings.profile.nick"
|
||||
msgstr "Nick"
|
||||
|
||||
msgid "settings.profile.nick.already-taken"
|
||||
msgstr "Der Nick ist bereits vergeben."
|
||||
|
||||
msgid "settings.profile.pronoun"
|
||||
msgstr "Pronomen"
|
||||
|
||||
|
@ -2161,9 +2133,15 @@ msgstr "Handy"
|
|||
msgid "settings.profile.mobile_show"
|
||||
msgstr "Mache meine Handynummer für andere Benutzer sichtbar."
|
||||
|
||||
msgid "settings.profile.email-preferences"
|
||||
msgstr "E-Mail Einstellungen"
|
||||
|
||||
msgid "settings.profile.email"
|
||||
msgstr "E-Mail"
|
||||
|
||||
msgid "settings.profile.email.already-taken"
|
||||
msgstr "Die E-Mail-Adresse ist bereits vergeben."
|
||||
|
||||
msgid "settings.profile.email_shiftinfo"
|
||||
msgstr "Das %s darf mir E-Mails senden (z.B. wenn sich meine Schichten ändern)."
|
||||
|
||||
|
@ -2195,6 +2173,9 @@ msgstr "Passwort"
|
|||
msgid "settings.password.info"
|
||||
msgstr "Hier kannst Du Dein Passwort ändern."
|
||||
|
||||
msgid "settings.password.confirmation-does-not-match"
|
||||
msgstr "Passwort und Passwortwiederholung stimmen nicht überein."
|
||||
|
||||
msgid "settings.password.password"
|
||||
msgstr "Altes Passwort"
|
||||
|
||||
|
@ -2412,6 +2393,9 @@ msgid "angeltypes.restricted.hint"
|
|||
msgstr "Dieser Engeltyp benötigt eine Einweisung bei einem Einführungstreffen. "
|
||||
"Weitere Informationen findest du möglicherweise in der Beschreibung."
|
||||
|
||||
msgid "angeltypes.can-change-later"
|
||||
msgstr "Du kannst Deine Auswahl später in den Einstellungen ändern."
|
||||
|
||||
msgid "angeltypes.name"
|
||||
msgstr "Name"
|
||||
|
||||
|
@ -2456,3 +2440,27 @@ msgstr "Tag %1$d"
|
|||
|
||||
msgid "dashboard.day"
|
||||
msgstr "Tag"
|
||||
|
||||
msgid "page.sign-up.title"
|
||||
msgstr "Engelregistrierung"
|
||||
|
||||
msgid "page.sign-up.login-data"
|
||||
msgstr "Anmeldedaten"
|
||||
|
||||
msgid "page.sign-up.name"
|
||||
msgstr "Name"
|
||||
|
||||
msgid "page.sign-up.event-data"
|
||||
msgstr "Eventdaten"
|
||||
|
||||
msgid "page.sign-up.what-do-you-want-to-do"
|
||||
msgstr "Was möchtest Du machen?"
|
||||
|
||||
msgid "page.sign-up.sign-up"
|
||||
msgstr "Registrieren"
|
||||
|
||||
msgid "pages.sign-up.disabled"
|
||||
msgstr "Die Engelregistrierung ist deaktiviert"
|
||||
|
||||
msgid "pages.sign-up.successful"
|
||||
msgstr "Engelregistrierung erfolgreich! Du kannst dich jetzt anmelden!"
|
||||
|
|
|
@ -15,19 +15,45 @@ msgstr "No user was found or password is wrong. Please try again. If you are sti
|
|||
msgid "validation.password.required"
|
||||
msgstr "The password is required."
|
||||
|
||||
msgid "validation.password.length"
|
||||
msgstr "The password entered is too short."
|
||||
|
||||
msgid "validation.login.required"
|
||||
msgstr "The login name is required."
|
||||
|
||||
msgid "validation.pronoun.length"
|
||||
msgstr "The pronoun you have entered is too long. Use a maximum of 15 characters."
|
||||
|
||||
msgid "validation.firstname.length"
|
||||
msgstr "The first name you have entered is too long. Use a maximum of 64 characters."
|
||||
|
||||
msgid "validation.lastname.length"
|
||||
msgstr "The last name you have entered is too long. Use a maximum of 64 characters."
|
||||
|
||||
msgid "validation.mobile.optional"
|
||||
msgstr "The mobile number you have entered is too long. Use a maximum of 40 characters."
|
||||
|
||||
msgid "validation.dect.optional"
|
||||
msgstr "The DECT number you have entered is too long. Use a maximum of 40 characters."
|
||||
|
||||
msgid "validation.username.required"
|
||||
msgstr "Please enter your nick."
|
||||
|
||||
msgid "validation.username.username"
|
||||
msgstr ""
|
||||
"Please enter a valid nick:"
|
||||
"Use up to 24 letters, numbers or connecting punctuations (.-_) for your nickname."
|
||||
|
||||
msgid "validation.email.required"
|
||||
msgstr "The email address is required."
|
||||
|
||||
msgid "validation.email.email"
|
||||
msgstr "This email address is not valid."
|
||||
|
||||
msgid "validation.password.min"
|
||||
msgid "validation.password.length"
|
||||
msgstr "Your new password is too short."
|
||||
|
||||
msgid "validation.new_password.min"
|
||||
msgid "validation.new_password.length"
|
||||
msgstr "Your new password is too short."
|
||||
|
||||
msgid "validation.password.confirmed"
|
||||
|
@ -36,6 +62,21 @@ msgstr "Your passwords are not equal."
|
|||
msgid "validation.password_confirmation.required"
|
||||
msgstr "You have to confirm your password."
|
||||
|
||||
msgid "validation.tshirt_size.required"
|
||||
msgstr "Please choose your t-shirt size."
|
||||
|
||||
msgid "validation.tshirt_size.shirtSize"
|
||||
msgstr "Please choose a valid t-shirt size."
|
||||
|
||||
msgid "validation.planned_arrival_date.required"
|
||||
msgstr "Please enter your planned date of arrival."
|
||||
|
||||
msgid "validation.planned_arrival_date.min"
|
||||
msgstr "The planned date of arrival must not be before the buil-up start date."
|
||||
|
||||
msgid "validation.planned_arrival_date.between"
|
||||
msgstr "The planned date of arrival must be between the build-up and tear-down date."
|
||||
|
||||
msgid "schedule.edit.success"
|
||||
msgstr "The schedule was configured successfully."
|
||||
|
||||
|
|
|
@ -229,6 +229,9 @@ msgstr "Entry required"
|
|||
msgid "settings.profile.nick"
|
||||
msgstr "Nick"
|
||||
|
||||
msgid "settings.profile.nick.already-taken"
|
||||
msgstr "The nick is already taken."
|
||||
|
||||
msgid "settings.profile.pronoun"
|
||||
msgstr "Pronoun"
|
||||
|
||||
|
@ -256,9 +259,15 @@ msgstr "Mobile"
|
|||
msgid "settings.profile.mobile_show"
|
||||
msgstr "Show mobile number to other users to contact me."
|
||||
|
||||
msgid "settings.profile.email-preferences"
|
||||
msgstr "E-Mail preferences"
|
||||
|
||||
msgid "settings.profile.email"
|
||||
msgstr "E-Mail"
|
||||
|
||||
msgid "settings.profile.email.already-taken"
|
||||
msgstr "The email is already taken."
|
||||
|
||||
msgid "settings.profile.email_shiftinfo"
|
||||
msgstr "The %s is allowed to send me an e-mail (e.g. when my shifts change)."
|
||||
|
||||
|
@ -290,6 +299,9 @@ msgstr "Password"
|
|||
msgid "settings.password.info"
|
||||
msgstr "Here you can change your password."
|
||||
|
||||
msgid "settings.password.confirmation-does-not-match"
|
||||
msgstr "Password and password confirmation do not match."
|
||||
|
||||
msgid "settings.password.password"
|
||||
msgstr "Old password"
|
||||
|
||||
|
@ -507,6 +519,9 @@ msgid "angeltypes.restricted.hint"
|
|||
msgstr "This angeltype requires the attendance at an introduction meeting. "
|
||||
"You might find additional information in the description."
|
||||
|
||||
msgid "angeltypes.can-change-later"
|
||||
msgstr "You can change your selection later in the settings."
|
||||
|
||||
msgid "angeltypes.name"
|
||||
msgstr "Name"
|
||||
|
||||
|
@ -551,3 +566,27 @@ msgstr "Day %1$d"
|
|||
|
||||
msgid "dashboard.day"
|
||||
msgstr "Day"
|
||||
|
||||
msgid "page.sign-up.title"
|
||||
msgstr "Angel sign-up"
|
||||
|
||||
msgid "page.sign-up.login-data"
|
||||
msgstr "Login data"
|
||||
|
||||
msgid "page.sign-up.name"
|
||||
msgstr "Name"
|
||||
|
||||
msgid "page.sign-up.event-data"
|
||||
msgstr "Event data"
|
||||
|
||||
msgid "page.sign-up.what-do-you-want-to-do"
|
||||
msgstr "What do you want to do?"
|
||||
|
||||
msgid "page.sign-up.sign-up"
|
||||
msgstr "Sign up"
|
||||
|
||||
msgid "pages.sign-up.disabled"
|
||||
msgstr "The angel registration is disabled"
|
||||
|
||||
msgid "pages.sign-up.successful"
|
||||
msgstr "Angel sign-up success. You can now log in."
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
{% include "layouts/parts/language_dropdown.twig" %}
|
||||
|
||||
{% if has_permission_to('register') and config('registration_enabled') %}
|
||||
{{ _self.toolbar_item(__('Register'), url('register'), 'register', 'plus') }}
|
||||
{{ _self.toolbar_item(__('Register'), url('/sign-up'), 'register', 'plus') }}
|
||||
{% endif %}
|
||||
|
||||
{% if has_permission_to('login') %}
|
||||
|
|
|
@ -214,9 +214,10 @@ Renders a Bootstrap checkbox element with mb-3.
|
|||
@param {bool} [opt.disabled=false] - Whether to add the "disabled" attribute. Defaults to false.
|
||||
@param {bool} [opt.raw_label=false] - Whether to use the raw label value (=do not escape). Defaults to false.
|
||||
@param {string} [opt.info] - If set an additional info icon will be added to the label with the text as tooltip.
|
||||
@param {string} [opt.class="mb-3"] - CSS classes for the checkbox element. Defaults to "mb-3".
|
||||
#}
|
||||
{% macro checkbox(name, label, opt) %}
|
||||
<div class="form-check mb-3">
|
||||
<div class="form-check {{ opt.class is defined ? opt.class : 'mb-3' }}">
|
||||
<input class="form-check-input" type="checkbox" id="{{ name }}" name="{{ name }}"
|
||||
value="{{ opt.value|default('1') }}"
|
||||
{%- if opt.checked|default(false) %} checked{% endif %}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</a>
|
||||
{% else %}
|
||||
{% if has_permission_to('register') and config('registration_enabled') %}
|
||||
<a href="{{ url('/register') }}" class="btn btn-secondary back">
|
||||
<a href="{{ url('/sign-up') }}" class="btn btn-secondary back">
|
||||
{{ __('registration.register') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
{% if has_permission_to('register') and config('registration_enabled') %}
|
||||
{% if config('enable_password') %}
|
||||
<p>{{ __('Please sign up, if you want to help us!') }}</p>
|
||||
<a href="{{ url('register') }}" class="btn btn-primary">{{ __('Register') }} »</a>
|
||||
<a href="{{ url('/sign-up') }}" class="btn btn-primary">{{ __('Register') }} »</a>
|
||||
{% else %}
|
||||
<p>{{ __('Registration is only available via external login.') }}</p>
|
||||
{% endif %}
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">{{ __('settings.profile.email-preferences') }}</label>
|
||||
{{ f.checkbox('email_shiftinfo', __('settings.profile.email_shiftinfo', [config('app_name')]), {
|
||||
'checked': user.settings.email_shiftinfo,
|
||||
}) }}
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
{% extends 'layouts/app.twig' %}
|
||||
{% import 'macros/base.twig' as m %}
|
||||
{% import 'macros/form.twig' as f %}
|
||||
|
||||
{% block title %}{{ __('page.sign-up.title') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="mb-5">
|
||||
<h1>{{ __('page.sign-up.title') }}</h1>
|
||||
</div>
|
||||
|
||||
{% include 'layouts/parts/messages.twig' %}
|
||||
|
||||
<form method="post" action="{{ url('/sign-up') }}" novalidate class="mb-5">
|
||||
{{ csrf() }}
|
||||
|
||||
<div class="mb-5">
|
||||
<h2>{{ __('page.sign-up.login-data') }}</h2>
|
||||
<div class="row">
|
||||
{% if isPronounEnabled %}
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'pronoun',
|
||||
__('settings.profile.pronoun'),
|
||||
{
|
||||
'max_length': 15,
|
||||
'value': f.formData('pronoun', ''),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'username',
|
||||
__('settings.profile.nick'),
|
||||
{
|
||||
'autocomplete': 'nickname',
|
||||
'max_length': 24,
|
||||
'required': true,
|
||||
'required_icon': true,
|
||||
'value': f.formData('username', ''),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
|
||||
{% if not isPronounEnabled %}
|
||||
{# Insert an empty row to keep password / email in line #}
|
||||
<div class="col-md-6"></div>
|
||||
{% endif %}
|
||||
|
||||
{% if isPasswordEnabled %}
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'password',
|
||||
__('settings.password'),
|
||||
{
|
||||
'type': 'password',
|
||||
'autocomplete': 'new-password',
|
||||
'required': true,
|
||||
'required_icon': true,
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
"password_confirmation",
|
||||
__('settings.password.new_password2'),
|
||||
{
|
||||
'type': 'password',
|
||||
'autocomplete': 'new-password',
|
||||
'required': true,
|
||||
'required_icon': true,
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'email',
|
||||
__('settings.profile.email'),
|
||||
{
|
||||
'type': 'email',
|
||||
'max_length': 254,
|
||||
'required': true,
|
||||
'required_icon': true,
|
||||
'value': f.formData('email', ''),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">{{ __('settings.profile.email-preferences') }}</label>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
{# Empty class to prevent the default bottom margin #}
|
||||
{{ f.checkbox(
|
||||
'email_shiftinfo',
|
||||
__(
|
||||
'settings.profile.email_shiftinfo',
|
||||
[config('app_name')]
|
||||
),
|
||||
{
|
||||
'class': '',
|
||||
'checked': f.formData('email_shiftinfo', false),
|
||||
},
|
||||
) }}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
{# Empty class to prevent the default bottom margin #}
|
||||
{{ f.checkbox(
|
||||
'email_news',
|
||||
__('settings.profile.email_news'),
|
||||
{
|
||||
'class': '',
|
||||
'checked': f.formData('email_news', false),
|
||||
},
|
||||
) }}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
{# Empty class to prevent the default bottom margin #}
|
||||
{{ f.checkbox(
|
||||
'email_messages',
|
||||
__('settings.profile.email_messages'),
|
||||
{
|
||||
'class': '',
|
||||
'checked': f.formData('email_messages', false),
|
||||
},
|
||||
) }}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
{# Empty class to prevent the default bottom margin #}
|
||||
{{ f.checkbox(
|
||||
'email_by_human_allowed',
|
||||
__('settings.profile.email_by_human_allowed'),
|
||||
{
|
||||
'class': '',
|
||||
'checked': f.formData('email_by_human_allowed', false),
|
||||
},
|
||||
) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if isFullNameEnabled %}
|
||||
<div class="mb-5">
|
||||
<h2>{{ __('page.sign-up.name') }}</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'firstname',
|
||||
__('settings.profile.firstname'),
|
||||
{
|
||||
'autocomplete': 'given-name',
|
||||
'max_length': 64,
|
||||
'value': f.formData('firstname', ''),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'lastname',
|
||||
__('settings.profile.lastname'),
|
||||
{
|
||||
'autocomplete': 'family-name',
|
||||
'max_length': 64,
|
||||
'value': f.formData('lastname', ''),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-5">
|
||||
<h2>{{ __('page.sign-up.event-data') }}</h2>
|
||||
<div class="row">
|
||||
{% if isGoodieEnabled %}
|
||||
<div class="col-md-6">
|
||||
{% set privacy_email = config('privacy_email') %}
|
||||
{% set email_goody_label = __('settings.profile.email_goody') ~
|
||||
(privacy_email ? ' ' ~ __('settings.profile.privacy', [privacy_email]) : '')
|
||||
%}
|
||||
{{ f.checkbox(
|
||||
'email_goody',
|
||||
email_goody_label,
|
||||
{
|
||||
'raw_label': true,
|
||||
'checked': f.formData('email_goody', false),
|
||||
},
|
||||
) }}
|
||||
</div>
|
||||
<div class="col-md-6"></div>
|
||||
{% endif %}
|
||||
|
||||
{% if isPlannedArrivalDateEnabled %}
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'planned_arrival_date',
|
||||
__('settings.profile.planned_arrival_date'),
|
||||
{
|
||||
'type': 'date',
|
||||
'min': buildUpStartDate,
|
||||
'max': tearDownEndDate,
|
||||
'required': true,
|
||||
'required_icon': true,
|
||||
'value': f.formData('planned_arrival_date', buildUpStartDate),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if isGoodieTShirt %}
|
||||
<div class="col-md-6">
|
||||
{{ f.select(
|
||||
'tshirt_size',
|
||||
__('settings.profile.shirt_size'),
|
||||
tShirtSizes,
|
||||
{
|
||||
'default_option': __('form.select_placeholder'),
|
||||
'required': true,
|
||||
'required_icon': true,
|
||||
'selected': f.formData('tshirt_size', ''),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
'mobile',
|
||||
__('settings.profile.mobile'),
|
||||
{
|
||||
'type': 'tel-national',
|
||||
'max_length': 40,
|
||||
'value': f.formData('mobile', ''),
|
||||
}
|
||||
) }}
|
||||
{% if isShowMobileEnabled %}
|
||||
{{ f.checkbox(
|
||||
'mobile_show',
|
||||
__('settings.profile.mobile_show'),
|
||||
{
|
||||
'raw_label': true,
|
||||
'checked': f.formData('mobile_show', false),
|
||||
},
|
||||
) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if isDECTEnabled %}
|
||||
<div class="col-md-6">
|
||||
{{ f.input(
|
||||
"dect",
|
||||
__("settings.profile.dect"),
|
||||
{
|
||||
'type': 'tel-local',
|
||||
'max_length': 40,
|
||||
'value': f.formData('dect', ''),
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-5">
|
||||
<h2>{{ __('page.sign-up.what-do-you-want-to-do') }}</h2>
|
||||
<div class="row mb-3">
|
||||
{% for angelType in angelTypes %}
|
||||
<div class="col-sm-6 col-md-4 col-lg-3 col-xl-2">
|
||||
{{ f.checkbox(
|
||||
'angel_types_' ~ angelType.id,
|
||||
angelType.name ~ (angelType.restricted ? ' ' ~ m.icon('mortarboard-fill', 'text-body') : ''),
|
||||
{
|
||||
'value': angelType.id,
|
||||
'raw_label': true,
|
||||
'checked': preselectedAngelTypes['angel_types_' ~ angelType.id] ?? false,
|
||||
}
|
||||
) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-2">
|
||||
{{ m.icon('mortarboard-fill', 'text-body') }}
|
||||
{{ __('angeltypes.restricted.hint') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ m.icon('info-circle', 'text-body') }}
|
||||
{{ __('angeltypes.can-change-later') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#
|
||||
By assigning a name here, some magic™ will create a session var
|
||||
"form-data-register-submit" with the value 1 on submit.
|
||||
#}
|
||||
{{ f.submit(__('page.sign-up.sign-up'), {
|
||||
'name': 'register-submit',
|
||||
}) }}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -328,6 +328,6 @@ class OAuthController extends BaseController
|
|||
$this->session->set('oauth2_enable_password', $config['enable_password']);
|
||||
$this->session->set('oauth2_allow_registration', $config['allow_registration']);
|
||||
|
||||
return $this->redirect->to('/register');
|
||||
return $this->redirect->to('/sign-up');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Controllers;
|
||||
|
||||
use Engelsystem\Config\Config;
|
||||
use Engelsystem\Config\GoodieType;
|
||||
use Engelsystem\Events\Listener\OAuth2;
|
||||
use Engelsystem\Factories\User;
|
||||
use Engelsystem\Helpers\Authenticator;
|
||||
use Engelsystem\Http\Redirector;
|
||||
use Engelsystem\Http\Request;
|
||||
use Engelsystem\Http\Response;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
class SignUpController extends BaseController
|
||||
{
|
||||
use HasUserNotifications;
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Response $response,
|
||||
private Redirector $redirect,
|
||||
private SessionInterface $session,
|
||||
private Authenticator $auth,
|
||||
private OAuth2 $oAuth,
|
||||
private User $userFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function view(): Response
|
||||
{
|
||||
if ($this->determineRegistrationDisabled()) {
|
||||
return $this->notifySignUpDisabledAndRedirectToHome();
|
||||
}
|
||||
|
||||
return $this->renderSignUpPage();
|
||||
}
|
||||
|
||||
public function save(Request $request): Response
|
||||
{
|
||||
if ($this->determineRegistrationDisabled()) {
|
||||
return $this->notifySignUpDisabledAndRedirectToHome();
|
||||
}
|
||||
|
||||
$rawData = $request->getParsedBody();
|
||||
$user = $this->userFactory->createFromData($rawData);
|
||||
|
||||
$this->addNotification('pages.sign-up.successful');
|
||||
|
||||
if ($this->config->get('welcome_msg')) {
|
||||
// Set a session marker to display the welcome message on the next page
|
||||
$this->session->set('show_welcome', true);
|
||||
}
|
||||
|
||||
if ($user->oauth?->count() > 0) {
|
||||
// User has OAuth configured. Log in directly.
|
||||
$provider = $user->oauth->first();
|
||||
return $this->redirect->to('/oauth/' . $provider->provider);
|
||||
}
|
||||
|
||||
return $this->redirect->to('/');
|
||||
}
|
||||
|
||||
private function notifySignUpDisabledAndRedirectToHome(): Response
|
||||
{
|
||||
$this->addNotification('pages.sign-up.disabled', NotificationType::INFORMATION);
|
||||
return $this->redirect->to('/');
|
||||
}
|
||||
|
||||
private function renderSignUpPage(): Response
|
||||
{
|
||||
$goodieType = GoodieType::from($this->config->get('goodie_type'));
|
||||
$preselectedAngelTypes = $this->determinePreselectedAngelTypes();
|
||||
|
||||
// form-data-register-submit is a marker, that the form was submitted.
|
||||
// It will be used for instance to use the default angel types or the user selected ones.
|
||||
// Clear it before render to reset the marker state.
|
||||
$this->session->remove('form-data-register-submit');
|
||||
|
||||
return $this->response->withView(
|
||||
'pages/sign-up',
|
||||
[
|
||||
'tShirtSizes' => $this->config->get('tshirt_sizes'),
|
||||
'angelTypes' => AngelType::whereHideRegister(false)->get(),
|
||||
'preselectedAngelTypes' => $preselectedAngelTypes,
|
||||
'buildUpStartDate' => $this->userFactory->determineBuildUpStartDate()->format('Y-m-d'),
|
||||
'tearDownEndDate' => $this->config->get('teardown_end')?->format('Y-m-d'),
|
||||
'isPasswordEnabled' => $this->userFactory->determineIsPasswordEnabled(),
|
||||
'isDECTEnabled' => $this->config->get('enable_dect'),
|
||||
'isShowMobileEnabled' => $this->config->get('enable_mobile_show'),
|
||||
'isGoodieEnabled' => $goodieType !== GoodieType::None,
|
||||
'isGoodieTShirt' => $goodieType === GoodieType::Tshirt,
|
||||
'isPronounEnabled' => $this->config->get('enable_pronoun'),
|
||||
'isFullNameEnabled' => $this->config->get('enable_user_name'),
|
||||
'isPlannedArrivalDateEnabled' => $this->config->get('enable_planned_arrival'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array<string, 1> Checkbox field name/Id → 1
|
||||
*/
|
||||
private function determinePreselectedAngelTypes(): array
|
||||
{
|
||||
if ($this->session->has('form-data-register-submit')) {
|
||||
// form-data-register-submit means a user just submitted the page.
|
||||
// Preselect the angel types from the persisted session form data.
|
||||
return $this->loadAngelTypesFromSessionFormData();
|
||||
}
|
||||
|
||||
$preselectedAngelTypes = [];
|
||||
|
||||
if ($this->session->has('oauth2_connect_provider')) {
|
||||
$preselectedAngelTypes = $this->loadAngelTypesFromSessionOAuthGroups();
|
||||
}
|
||||
|
||||
foreach (AngelType::whereRestricted(false)->whereHideRegister(false)->get() as $angelType) {
|
||||
// preselect every angel type without restriction
|
||||
$preselectedAngelTypes['angel_types_' . $angelType->id] = 1;
|
||||
}
|
||||
|
||||
return $preselectedAngelTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array<string, 1>
|
||||
*/
|
||||
private function loadAngelTypesFromSessionOAuthGroups(): array
|
||||
{
|
||||
$oAuthAngelTypes = [];
|
||||
$ssoTeams = $this->oAuth->getSsoTeams($this->session->get('oauth2_connect_provider'));
|
||||
$oAuth2Groups = $this->session->get('oauth2_groups');
|
||||
|
||||
foreach ($ssoTeams as $name => $team) {
|
||||
if (in_array($name, $oAuth2Groups)) {
|
||||
// preselect angel type from oauth
|
||||
$oAuthAngelTypes['angel_types_' . $team['id']] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $oAuthAngelTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array<string, 1>
|
||||
*/
|
||||
private function loadAngelTypesFromSessionFormData(): array
|
||||
{
|
||||
$angelTypes = AngelType::whereHideRegister(false)->get();
|
||||
$selectedAngelTypes = [];
|
||||
|
||||
foreach ($angelTypes as $angelType) {
|
||||
$sessionKey = 'form-data-angel_types_' . $angelType->id;
|
||||
|
||||
if ($this->session->has($sessionKey)) {
|
||||
$selectedAngelTypes['angel_types_' . $angelType->id] = 1;
|
||||
// remove from session so that it doesn't stay there forever
|
||||
$this->session->remove($sessionKey);
|
||||
}
|
||||
}
|
||||
|
||||
return $selectedAngelTypes;
|
||||
}
|
||||
|
||||
private function determineRegistrationDisabled(): bool
|
||||
{
|
||||
$authUser = $this->auth->user();
|
||||
$isOAuth = $this->session->get('oauth2_connect_provider');
|
||||
$isPasswordEnabled = $this->userFactory->determineIsPasswordEnabled();
|
||||
|
||||
return !auth()->can('register') // No registration permission
|
||||
// Not authenticated and
|
||||
// Registration disabled
|
||||
|| (
|
||||
!$authUser
|
||||
&& !$this->config->get('registration_enabled')
|
||||
&& !$this->session->get('oauth2_allow_registration')
|
||||
)
|
||||
// Password disabled and not oauth
|
||||
|| (!$authUser && !$isPasswordEnabled && !$isOAuth);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Factories;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use DateTimeInterface;
|
||||
use Engelsystem\Config\Config;
|
||||
use Engelsystem\Config\GoodieType;
|
||||
use Engelsystem\Helpers\Authenticator;
|
||||
use Engelsystem\Helpers\Carbon;
|
||||
use Engelsystem\Http\Exceptions\ValidationException;
|
||||
use Engelsystem\Http\Validation\Validator;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Group;
|
||||
use Engelsystem\Models\OAuth;
|
||||
use Engelsystem\Models\User\Contact;
|
||||
use Engelsystem\Models\User\PersonalData;
|
||||
use Engelsystem\Models\User\Settings;
|
||||
use Engelsystem\Models\User\State;
|
||||
use Engelsystem\Models\User\User as EngelsystemUser;
|
||||
use Illuminate\Database\Connection;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
class User
|
||||
{
|
||||
public function __construct(
|
||||
private Authenticator $authenticator,
|
||||
private Config $config,
|
||||
private Connection $dbConnection,
|
||||
private LoggerInterface $logger,
|
||||
private SessionInterface $session,
|
||||
private Validator $validator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes some arbitrary data, validates it and tries to create a user from it.
|
||||
*
|
||||
* @param Array<string, mixed> $rawData Raw data from which a user should be created
|
||||
* @return The user if successful
|
||||
* @throws
|
||||
*/
|
||||
public function createFromData(array $rawData): EngelsystemUser
|
||||
{
|
||||
$data = $this->validateUser($rawData);
|
||||
return $this->createUser($data, $rawData);
|
||||
}
|
||||
|
||||
public function determineIsPasswordEnabled(): bool
|
||||
{
|
||||
$isPasswordEnabled = $this->config->get('enable_password');
|
||||
$oAuthEnablePassword = $this->session->get('oauth2_enable_password');
|
||||
|
||||
if (!is_null($oAuthEnablePassword)) {
|
||||
// o-auth overwrites config
|
||||
$isPasswordEnabled = $oAuthEnablePassword;
|
||||
}
|
||||
|
||||
return $isPasswordEnabled;
|
||||
}
|
||||
|
||||
public function determineBuildUpStartDate(): DateTimeInterface
|
||||
{
|
||||
return $this->config->get('buildup_start') ?? CarbonImmutable::now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Array<string, mixed> $rawData
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validateUser(array $rawData): array
|
||||
{
|
||||
$validationRules = [
|
||||
'username' => 'required|username',
|
||||
'email' => 'required|email',
|
||||
'email_shiftinfo' => 'optional|checked',
|
||||
'email_by_human_allowed' => 'optional|checked',
|
||||
'email_messages' => 'optional|checked',
|
||||
'email_news' => 'optional|checked',
|
||||
'email_goody' => 'optional|checked',
|
||||
// Using length here, because min/max would validate dect/mobile as numbers.
|
||||
'mobile' => 'optional|length:0:40',
|
||||
];
|
||||
|
||||
$isPasswordEnabled = $this->determineIsPasswordEnabled();
|
||||
|
||||
if ($isPasswordEnabled) {
|
||||
$minPasswordLength = $this->config->get('min_password_length');
|
||||
$validationRules['password'] = 'required|length:' . $minPasswordLength;
|
||||
$validationRules['password_confirmation'] = 'required';
|
||||
}
|
||||
|
||||
$isFullNameEnabled = $this->config->get('enable_user_name');
|
||||
|
||||
if ($isFullNameEnabled) {
|
||||
$validationRules['firstname'] = 'optional|length:0:64';
|
||||
$validationRules['lastname'] = 'optional|length:0:64';
|
||||
}
|
||||
|
||||
$isPronounEnabled = $this->config->get('enable_pronoun');
|
||||
|
||||
if ($isPronounEnabled) {
|
||||
$validationRules['pronoun'] = 'optional|max:15';
|
||||
}
|
||||
|
||||
$isShowMobileEnabled = $this->config->get('enable_mobile_show');
|
||||
|
||||
if ($isShowMobileEnabled) {
|
||||
$validationRules['mobile_show'] = 'optional|checked';
|
||||
}
|
||||
|
||||
$isPlannedArrivalDateEnabled = $this->config->get('enable_planned_arrival');
|
||||
|
||||
if ($isPlannedArrivalDateEnabled) {
|
||||
$isoBuildUpStartDate = $this->determineBuildUpStartDate();
|
||||
/** @var DateTimeInterface|null $tearDownEndDate */
|
||||
$tearDownEndDate = $this->config->get('teardown_end');
|
||||
|
||||
if ($tearDownEndDate) {
|
||||
$validationRules['planned_arrival_date'] = sprintf(
|
||||
'required|date|between:%s:%s',
|
||||
$isoBuildUpStartDate->format('Y-m-d'),
|
||||
$tearDownEndDate->format('Y-m-d')
|
||||
);
|
||||
} else {
|
||||
$validationRules['planned_arrival_date'] = sprintf(
|
||||
'required|date|min:%s',
|
||||
$isoBuildUpStartDate->format('Y-m-d'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$isDECTEnabled = $this->config->get('enable_dect');
|
||||
|
||||
if ($isDECTEnabled) {
|
||||
// Using length here, because min/max would validate dect/mobile as numbers.
|
||||
$validationRules['dect'] = 'optional|length:0:40';
|
||||
}
|
||||
|
||||
$goodieType = GoodieType::from($this->config->get('goodie_type'));
|
||||
$isGoodieTShirt = $goodieType === GoodieType::Tshirt;
|
||||
|
||||
if ($isGoodieTShirt) {
|
||||
$validationRules['tshirt_size'] = 'required|shirt-size';
|
||||
}
|
||||
|
||||
$data = $this->validate($rawData, $validationRules);
|
||||
|
||||
// additional validations
|
||||
$this->validateUniqueUsername($data['username']);
|
||||
$this->validateUniqueEmail($data['email']);
|
||||
|
||||
if ($isPasswordEnabled) {
|
||||
// Finally, validate that password matches password_confirmation.
|
||||
// The respect keyValue validation does not seem to work.
|
||||
$this->validatePasswordMatchesConfirmation($rawData);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Array<string, mixed> $rawData
|
||||
*/
|
||||
private function validatePasswordMatchesConfirmation(array $rawData): void
|
||||
{
|
||||
if ($rawData['password'] !== $rawData['password_confirmation']) {
|
||||
throw new ValidationException(
|
||||
(new Validator())->addErrors(['password' => [
|
||||
'settings.password.confirmation-does-not-match',
|
||||
]])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateUniqueUsername(string $username): void
|
||||
{
|
||||
if (EngelsystemUser::whereName($username)->exists()) {
|
||||
throw new ValidationException(
|
||||
(new Validator())->addErrors(['username' => [
|
||||
'settings.profile.nick.already-taken',
|
||||
]])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateUniqueEmail(string $email): void
|
||||
{
|
||||
if (EngelsystemUser::whereEmail($email)->exists()) {
|
||||
throw new ValidationException(
|
||||
(new Validator())->addErrors(['email' => [
|
||||
'settings.profile.email.already-taken',
|
||||
]])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Array<string, mixed> $data
|
||||
* @param Array<string, mixed> $rawData
|
||||
*/
|
||||
private function createUser(array $data, array $rawData): EngelsystemUser
|
||||
{
|
||||
$this->dbConnection->beginTransaction();
|
||||
|
||||
$user = new EngelsystemUser([
|
||||
'name' => $data['username'],
|
||||
'password' => '',
|
||||
'email' => $data['email'],
|
||||
'api_key' => '',
|
||||
'last_login_at' => null,
|
||||
]);
|
||||
$user->save();
|
||||
|
||||
$contact = new Contact([
|
||||
'dect' => $data['dect'] ?? null,
|
||||
'mobile' => $data['mobile'],
|
||||
]);
|
||||
$contact->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$isPlannedArrivalDateEnabled = $this->config->get('enable_planned_arrival');
|
||||
$plannedArrivalDate = null;
|
||||
|
||||
if ($isPlannedArrivalDateEnabled) {
|
||||
$plannedArrivalDate = Carbon::createFromFormat('Y-m-d', $data['planned_arrival_date']);
|
||||
}
|
||||
|
||||
$personalData = new PersonalData([
|
||||
'first_name' => $data['firstname'] ?? null,
|
||||
'last_name' => $data['lastname'] ?? null,
|
||||
'pronoun' => $data['pronoun'] ?? null,
|
||||
'shirt_size' => $data['tshirt_size'] ?? null,
|
||||
'planned_arrival_date' => $plannedArrivalDate,
|
||||
]);
|
||||
$personalData->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$isShowMobileEnabled = $this->config->get('enable_mobile_show');
|
||||
|
||||
$settings = new Settings([
|
||||
'language' => $this->session->get('locale') ?? 'en_US',
|
||||
'theme' => $this->config->get('theme'),
|
||||
'email_human' => $data['email_by_human_allowed'] ?? false,
|
||||
'email_messages' => $data['email_messages'] ?? false,
|
||||
'email_goody' => $data['email_goody'] ?? false,
|
||||
'email_shiftinfo' => $data['email_shiftinfo'] ?? false,
|
||||
'email_news' => $data['email_news'] ?? false,
|
||||
'mobile_show' => $isShowMobileEnabled && $data['mobile_show'],
|
||||
]);
|
||||
$settings->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$state = new State([]);
|
||||
|
||||
if ($this->config->get('autoarrive')) {
|
||||
$state->arrived = true;
|
||||
$state->arrival_date = CarbonImmutable::now();
|
||||
}
|
||||
|
||||
$state->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
if ($this->session->has('oauth2_connect_provider') && $this->session->has('oauth2_user_id')) {
|
||||
$oauth = new OAuth([
|
||||
'provider' => $this->session->get('oauth2_connect_provider'),
|
||||
'identifier' => $this->session->get('oauth2_user_id'),
|
||||
'access_token' => $this->session->get('oauth2_access_token'),
|
||||
'refresh_token' => $this->session->get('oauth2_refresh_token'),
|
||||
'expires_at' => $this->session->get('oauth2_expires_at'),
|
||||
]);
|
||||
$oauth->user()
|
||||
->associate($user)
|
||||
->save();
|
||||
|
||||
$this->session->remove('oauth2_connect_provider');
|
||||
$this->session->remove('oauth2_user_id');
|
||||
$this->session->remove('oauth2_access_token');
|
||||
$this->session->remove('oauth2_refresh_token');
|
||||
$this->session->remove('oauth2_expires_at');
|
||||
}
|
||||
|
||||
$defaultGroup = Group::find($this->authenticator->getDefaultRole());
|
||||
$user->groups()->attach($defaultGroup);
|
||||
|
||||
if ($this->determineIsPasswordEnabled() && array_key_exists('password', $data)) {
|
||||
auth()->setPassword($user, $data['password']);
|
||||
}
|
||||
|
||||
$assignedAngelTypeNames = $this->assignAngelTypes($user, $rawData);
|
||||
|
||||
$this->logger->info(sprintf(
|
||||
'User %s signed up as: %s',
|
||||
sprintf('%s (%u)', $user->displayName, $user->id),
|
||||
join(', ', $assignedAngelTypeNames),
|
||||
));
|
||||
|
||||
$this->dbConnection->commit();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function assignAngelTypes(EngelsystemUser $user, array $rawData): array
|
||||
{
|
||||
$possibleAngelTypes = AngelType::whereHideRegister(false)->get();
|
||||
$assignedAngelTypeNames = [];
|
||||
|
||||
foreach ($possibleAngelTypes as $possibleAngelType) {
|
||||
$angelTypeCheckboxId = 'angel_types_' . $possibleAngelType->id;
|
||||
|
||||
if (array_key_exists($angelTypeCheckboxId, $rawData)) {
|
||||
$user->userAngelTypes()->attach($possibleAngelType);
|
||||
$assignedAngelTypeNames[] = $possibleAngelType->name;
|
||||
}
|
||||
}
|
||||
|
||||
return $assignedAngelTypeNames;
|
||||
}
|
||||
|
||||
private function validate(array $rawData, array $rules): array
|
||||
{
|
||||
$isValid = $this->validator->validate($rawData, $rules);
|
||||
|
||||
if (!$isValid) {
|
||||
throw new ValidationException($this->validator);
|
||||
}
|
||||
|
||||
return $this->validator->getData();
|
||||
}
|
||||
}
|
|
@ -108,10 +108,6 @@ class LegacyMiddleware implements MiddlewareInterface
|
|||
$title = shifts_title();
|
||||
$content = user_shifts();
|
||||
return [$title, $content];
|
||||
case 'register':
|
||||
$title = register_title();
|
||||
$content = guest_register();
|
||||
return [$title, $content];
|
||||
case 'admin_user':
|
||||
$title = admin_user_title();
|
||||
$content = admin_user();
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Engelsystem\Models;
|
|||
|
||||
use Carbon\Carbon;
|
||||
use Engelsystem\Models\User\UsesUserModel;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
|
||||
/**
|
||||
|
@ -26,6 +27,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
|
|||
*/
|
||||
class OAuth extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use UsesUserModel;
|
||||
|
||||
public $table = 'oauth'; // phpcs:ignore
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Test\Feature\Controllers;
|
||||
|
||||
use Engelsystem\Application;
|
||||
use Engelsystem\Config\Config;
|
||||
use Engelsystem\Controllers\SignUpController;
|
||||
use Engelsystem\Events\Listener\OAuth2;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\BaseModel;
|
||||
use Engelsystem\Test\Feature\ApplicationFeatureTest;
|
||||
use Engelsystem\Test\Utils\FormFieldAssert;
|
||||
use Engelsystem\Test\Utils\SignUpConfig;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @group sign-up-controller-tests
|
||||
*/
|
||||
final class SignUpControllerTest extends ApplicationFeatureTest
|
||||
{
|
||||
private Application $application;
|
||||
private Config $config;
|
||||
private SessionInterface $session;
|
||||
/**
|
||||
* @var OAuth2&MockObject
|
||||
*/
|
||||
private OAuth2 $oauth;
|
||||
/**
|
||||
* @var Array<BaseModel>
|
||||
*/
|
||||
private array $modelsToBeDeleted;
|
||||
private SignUpController $subject;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->modelsToBeDeleted = [];
|
||||
$this->application = app();
|
||||
$this->oauth = $this->getMockBuilder(OAuth2::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->application->instance(OAuth2::class, $this->oauth);
|
||||
$this->config = $this->application->get(Config::class);
|
||||
$this->session = $this->application->get(SessionInterface::class);
|
||||
$this->subject = $this->application->make(SignUpController::class);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
$this->deleteModels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the sign-up page with a minimum fields config.
|
||||
* Asserts that the basic fields are there while the other fields are not there.
|
||||
*
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testViewMinimumConfig(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$response = $this->subject->view();
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$responseHTML = $response->getBody()->__toString();
|
||||
|
||||
// assert the expected fields are there
|
||||
FormFieldAssert::assertContainsInputField('username', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('password', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('password_confirmation', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('email', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('mobile', $responseHTML);
|
||||
|
||||
// assert the disabled fields are not there
|
||||
FormFieldAssert::assertNotContainsInputField('pronoun', $responseHTML);
|
||||
FormFieldAssert::assertNotContainsInputField('firstname', $responseHTML);
|
||||
FormFieldAssert::assertNotContainsInputField('lastname', $responseHTML);
|
||||
FormFieldAssert::assertNotContainsInputField('email_goody', $responseHTML);
|
||||
FormFieldAssert::assertNotContainsSelectField('tshirt_size', $responseHTML);
|
||||
FormFieldAssert::assertNotContainsInputField('planned_arrival_date', $responseHTML);
|
||||
FormFieldAssert::assertNotContainsInputField('mobile_show', $responseHTML);
|
||||
FormFieldAssert::assertNotContainsInputField('dect', $responseHTML);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the sign-up page with a maximum fields config.
|
||||
* Asserts that all fields are there.
|
||||
*
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testViewMaximumConfig(): void
|
||||
{
|
||||
SignUpConfig::setMaximumConfig($this->config);
|
||||
$response = $this->subject->view();
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$responseHTML = $response->getBody()->__toString();
|
||||
|
||||
// assert the expected fields are there
|
||||
FormFieldAssert::assertContainsInputField('pronoun', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('username', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('email', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('mobile', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('password', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('password_confirmation', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('firstname', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('lastname', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('email_goody', $responseHTML);
|
||||
FormFieldAssert::assertContainsSelectField('tshirt_size', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('planned_arrival_date', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('mobile_show', $responseHTML);
|
||||
FormFieldAssert::assertContainsInputField('dect', $responseHTML);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testViewAngelTypesOAuthPreselection(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$angelTypes = $this->createAngelTypes();
|
||||
$this->session->set('oauth2_connect_provider', 'test_oauth_provider');
|
||||
$this->session->set('oauth2_groups', [$angelTypes[1]->name]);
|
||||
$this->oauth
|
||||
->method('getSsoTeams')
|
||||
->with('test_oauth_provider')
|
||||
->willReturn(
|
||||
[
|
||||
$angelTypes[1]->name => ['id' => $angelTypes[1]->id],
|
||||
$angelTypes[2]->name => ['id' => $angelTypes[2]->id],
|
||||
],
|
||||
);
|
||||
|
||||
$response = $this->subject->view();
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$responseHTML = $response->getBody()->__toString();
|
||||
|
||||
// assert that the unrestricted angel type is there and checked
|
||||
FormFieldAssert::assertContainsCheckedCheckbox('angel_types_' . $angelTypes[0]->id, $responseHTML);
|
||||
|
||||
// assert that the first restricted angel type from oauth is there and checked
|
||||
FormFieldAssert::assertContainsCheckedCheckbox('angel_types_' . $angelTypes[1]->id, $responseHTML);
|
||||
|
||||
// assert that the second restricted angel type not in oauth is there and not checked
|
||||
FormFieldAssert::assertContainsUncheckedCheckbox('angel_types_' . $angelTypes[2]->id, $responseHTML);
|
||||
|
||||
// assert that the angel type with "hide_register" = true is not there
|
||||
FormFieldAssert::assertNotContainsInputField('angel_types_' . $angelTypes[3]->id, $responseHTML);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testViewAngelTypesPreselection(): void
|
||||
{
|
||||
$angelTypes = $this->createAngelTypes();
|
||||
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$response = $this->subject->view();
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$responseHTML = $response->getBody()->__toString();
|
||||
|
||||
// assert that the unrestricted angel type is there and checked
|
||||
FormFieldAssert::assertContainsCheckedCheckbox('angel_types_' . $angelTypes[0]->id, $responseHTML);
|
||||
|
||||
// assert that restricted angel type are there and not checked
|
||||
FormFieldAssert::assertContainsUncheckedCheckbox('angel_types_' . $angelTypes[1]->id, $responseHTML);
|
||||
FormFieldAssert::assertContainsUncheckedCheckbox('angel_types_' . $angelTypes[2]->id, $responseHTML);
|
||||
|
||||
// assert that the angel type with "hide_register" = true is not there
|
||||
FormFieldAssert::assertNotContainsInputField('angel_types_' . $angelTypes[3]->id, $responseHTML);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that values are prefilled after submit
|
||||
*
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testViewValuesAfterSubmit(): void
|
||||
{
|
||||
$angelTypes = $this->createAngelTypes();
|
||||
|
||||
// fake submit and set form-data in session
|
||||
$this->session->set('form-data-register-submit', '1');
|
||||
$this->session->set('form-data-angel_types_' . $angelTypes[1]->id, '1');
|
||||
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$response = $this->subject->view();
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$responseHTML = $response->getBody()->__toString();
|
||||
|
||||
// assert that the unrestricted angel type is not checked
|
||||
FormFieldAssert::assertContainsUncheckedCheckbox('angel_types_' . $angelTypes[0]->id, $responseHTML);
|
||||
|
||||
// assert that the restricted angel type is checked
|
||||
FormFieldAssert::assertContainsCheckedCheckbox('angel_types_' . $angelTypes[1]->id, $responseHTML);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates three angel types:
|
||||
* - unrestricted
|
||||
* - restricted
|
||||
* - unrestricted, hidden on sign-up
|
||||
*
|
||||
* @return Array<AngelType>
|
||||
*/
|
||||
private function createAngelTypes(): array
|
||||
{
|
||||
$angelType1 = AngelType::create([
|
||||
'name' => 'Test angel type 1',
|
||||
]);
|
||||
|
||||
$angelType2 = AngelType::create([
|
||||
'name' => 'Test angel type 2',
|
||||
'restricted' => true,
|
||||
]);
|
||||
|
||||
$angelType3 = AngelType::create([
|
||||
'name' => 'Test angel type 3',
|
||||
'restricted' => true,
|
||||
]);
|
||||
|
||||
$angelType4 = AngelType::create([
|
||||
'name' => 'Test angel type 4',
|
||||
'hide_register' => true,
|
||||
]);
|
||||
|
||||
$this->modelsToBeDeleted[] = $angelType1;
|
||||
$this->modelsToBeDeleted[] = $angelType2;
|
||||
$this->modelsToBeDeleted[] = $angelType3;
|
||||
$this->modelsToBeDeleted[] = $angelType4;
|
||||
return [$angelType1, $angelType2, $angelType3, $angelType4];
|
||||
}
|
||||
|
||||
private function deleteModels(): void
|
||||
{
|
||||
foreach ($this->modelsToBeDeleted as $modelToBeDeleted) {
|
||||
$modelToBeDeleted->delete();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ use Psr\Http\Message\ServerRequestInterface;
|
|||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\Test\TestLogger;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
|
||||
abstract class ControllerTest extends TestCase
|
||||
|
@ -78,6 +79,7 @@ abstract class ControllerTest extends TestCase
|
|||
$this->session = new Session(new MockArraySessionStorage());
|
||||
$this->app->instance('session', $this->session);
|
||||
$this->app->instance(Session::class, $this->session);
|
||||
$this->app->instance(SessionInterface::class, $this->session);
|
||||
|
||||
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
|
||||
|
||||
|
|
|
@ -440,7 +440,7 @@ class OAuthControllerTest extends TestCase
|
|||
|
||||
$this->setExpects($this->auth, 'user', null, null, $this->atLeastOnce());
|
||||
|
||||
$this->setExpects($this->redirect, 'to', ['/register']);
|
||||
$this->setExpects($this->redirect, 'to', ['/sign-up']);
|
||||
|
||||
$request = new Request();
|
||||
$request = $request
|
||||
|
@ -533,7 +533,7 @@ class OAuthControllerTest extends TestCase
|
|||
|
||||
$this->setExpects($this->auth, 'user', null, null, $this->atLeastOnce());
|
||||
|
||||
$this->setExpects($this->redirect, 'to', ['/register']);
|
||||
$this->setExpects($this->redirect, 'to', ['/sign-up']);
|
||||
|
||||
$request = new Request();
|
||||
$request = $request
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Test\Unit\Controllers;
|
||||
|
||||
use Engelsystem\Controllers\SignUpController;
|
||||
use Engelsystem\Factories\User;
|
||||
use Engelsystem\Helpers\Authenticator;
|
||||
use Engelsystem\Models\OAuth;
|
||||
use Engelsystem\Models\User\User as EngelsystemUser;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
/**
|
||||
* @group sign-up-controller-tests
|
||||
*/
|
||||
final class SignUpControllerTest extends ControllerTest
|
||||
{
|
||||
/**
|
||||
* @var Authenticator&MockObject
|
||||
*/
|
||||
private Authenticator $authenticator;
|
||||
|
||||
/**
|
||||
* @var MockObject&User
|
||||
*/
|
||||
private User $userFactory;
|
||||
|
||||
private SignUpController $subject;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->mockTranslator();
|
||||
|
||||
$this->authenticator = $this->getMockBuilder(Authenticator::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->app->instance(Authenticator::class, $this->authenticator);
|
||||
$this->app->alias(Authenticator::class, 'authenticator');
|
||||
|
||||
$this->userFactory = $this->getMockBuilder(User::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->config->set('oauth', []);
|
||||
$this->app->instance(User::class, $this->userFactory);
|
||||
$this->subject = $this->app->make(SignUpController::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testSave(): void
|
||||
{
|
||||
$this->setPasswordRegistrationEnabledConfig();
|
||||
|
||||
$userData = ['user' => 'data'];
|
||||
$request = $this->request->withParsedBody($userData);
|
||||
|
||||
// Assert the controller passes the submitted data to the user factory
|
||||
$this->userFactory
|
||||
->expects(self::once())
|
||||
->method('createFromData')
|
||||
->with($userData)
|
||||
->willReturn(new EngelsystemUser());
|
||||
|
||||
// Assert that the user is redirected to home
|
||||
$this->response
|
||||
->expects(self::once())
|
||||
->method('redirectTo')
|
||||
->with('http://localhost/', 302);
|
||||
|
||||
$this->subject->save($request);
|
||||
|
||||
// Assert that the success notification is there
|
||||
self::assertEquals(
|
||||
[
|
||||
'messages.message' => ['pages.sign-up.successful'],
|
||||
],
|
||||
$this->session->all()
|
||||
);
|
||||
|
||||
// Assert that "show_welcome" is not set in session,
|
||||
// because "welcome_msg" is not configured.
|
||||
$this->assertFalse($this->session->has('show_welcome'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testSaveOAuth(): void
|
||||
{
|
||||
$this->setPasswordRegistrationEnabledConfig();
|
||||
|
||||
$userData = ['user' => 'data'];
|
||||
$request = $this->request->withParsedBody($userData);
|
||||
|
||||
$user = (new EngelsystemUser())->factory()->create();
|
||||
$oauth = (new OAuth())->factory()->create([
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
|
||||
$this->userFactory
|
||||
->expects(self::once())
|
||||
->method('createFromData')
|
||||
->with($userData)
|
||||
->willReturn($user);
|
||||
|
||||
// Assert that the user is redirected to the OAuth login
|
||||
$this->response
|
||||
->expects(self::once())
|
||||
->method('redirectTo')
|
||||
->with('http://localhost/oauth/' . $oauth->provider, 302);
|
||||
|
||||
$this->subject->save($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testSaveWithWelcomeMesssage(): void
|
||||
{
|
||||
$this->setPasswordRegistrationEnabledConfig();
|
||||
$this->config->set('welcome_msg', true);
|
||||
|
||||
$userData = ['user' => 'data'];
|
||||
$request = $this->request->withParsedBody($userData);
|
||||
$this->subject->save($request);
|
||||
|
||||
// Assert that "show_welcome" is set in session,
|
||||
// because "welcome_msg" is enabled in the config.
|
||||
$this->assertTrue($this->session->get('show_welcome'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testSaveRegistrationDisabled(): void
|
||||
{
|
||||
$this->config->set('registration_enabled', false);
|
||||
$request = $this->request->withParsedBody([]);
|
||||
|
||||
// Assert the controller does not call createFromData
|
||||
$this->userFactory
|
||||
->expects(self::never())
|
||||
->method('createFromData');
|
||||
|
||||
// Assert that the user is redirected to home
|
||||
$this->response
|
||||
->expects(self::once())
|
||||
->method('redirectTo')
|
||||
->with('http://localhost/', 302);
|
||||
|
||||
$this->subject->save($request);
|
||||
|
||||
// Assert that the error notification is there
|
||||
self::assertEquals(
|
||||
[
|
||||
'messages.information' => ['pages.sign-up.disabled'],
|
||||
],
|
||||
$this->session->all()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\SignUpController
|
||||
*/
|
||||
public function testViewRegistrationDisabled(): void
|
||||
{
|
||||
$this->config->set('registration_enabled', false);
|
||||
$request = $this->request->withParsedBody([]);
|
||||
|
||||
// Assert the controller does not call createFromData
|
||||
$this->userFactory
|
||||
->expects(self::never())
|
||||
->method('createFromData');
|
||||
|
||||
// Assert that the user is redirected to home
|
||||
$this->response
|
||||
->expects(self::once())
|
||||
->method('redirectTo')
|
||||
->with('http://localhost/', 302);
|
||||
|
||||
$this->subject->view($request);
|
||||
|
||||
// Assert that the error notification is there
|
||||
self::assertEquals(
|
||||
[
|
||||
'messages.information' => ['pages.sign-up.disabled'],
|
||||
],
|
||||
$this->session->all()
|
||||
);
|
||||
}
|
||||
|
||||
private function setPasswordRegistrationEnabledConfig(): void
|
||||
{
|
||||
$this->config->set('registration_enabled', true);
|
||||
$this->authenticator
|
||||
->method('can')
|
||||
->with('register')
|
||||
->willReturn(true);
|
||||
$this->userFactory->method('determineIsPasswordEnabled')
|
||||
->willReturn(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,496 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Test\Unit\Factories;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Engelsystem\Config\Config;
|
||||
use Engelsystem\Factories\User;
|
||||
use Engelsystem\Helpers\Authenticator;
|
||||
use Engelsystem\Http\Exceptions\ValidationException;
|
||||
use Engelsystem\Http\Request;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\User\User as UserModel;
|
||||
use Engelsystem\Test\Unit\HasDatabase;
|
||||
use Engelsystem\Test\Unit\ServiceProviderTest;
|
||||
use Engelsystem\Test\Utils\SignUpConfig;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
|
||||
class UserTest extends ServiceProviderTest
|
||||
{
|
||||
use HasDatabase;
|
||||
|
||||
private User $subject;
|
||||
|
||||
private Config $config;
|
||||
|
||||
private SessionInterface $session;
|
||||
|
||||
private CarbonImmutable $now;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->now = CarbonImmutable::now();
|
||||
Carbon::setTestNow($this->now);
|
||||
|
||||
$this->initDatabase();
|
||||
$this->config = new Config([]);
|
||||
$this->app->instance(Config::class, $this->config);
|
||||
$this->app->alias(Config::class, 'config');
|
||||
$this->config->set('oauth', []);
|
||||
$this->session = new Session(new MockArraySessionStorage());
|
||||
$this->app->instance(SessionInterface::class, $this->session);
|
||||
$this->app->instance(LoggerInterface::class, $this->getMockForAbstractClass(LoggerInterface::class));
|
||||
|
||||
$this->app->instance(ServerRequestInterface::class, new Request());
|
||||
$this->app->instance(Authenticator::class, $this->app->make(Authenticator::class));
|
||||
$this->app->alias(Authenticator::class, 'authenticator');
|
||||
|
||||
$this->subject = $this->app->make(User::class);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
Carbon::setTestNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal config with empty data.
|
||||
*
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testMinimumConfigEmpty(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
|
||||
$this->assertDataRaisesValidationException(
|
||||
[],
|
||||
[
|
||||
'username' => [
|
||||
'validation.username.required',
|
||||
'validation.username.username',
|
||||
],
|
||||
'email' => [
|
||||
'validation.email.required',
|
||||
'validation.email.email',
|
||||
],
|
||||
'password' => [
|
||||
'validation.password.required',
|
||||
'validation.password.length',
|
||||
],
|
||||
'password_confirmation' => [
|
||||
'validation.password_confirmation.required',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal config with valid data.
|
||||
*
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testMinimumConfigCreate(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
|
||||
$user = $this->subject->createFromData([
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
]);
|
||||
|
||||
$this->assertSame('fritz', $user->name);
|
||||
$this->assertSame('fritz@example.com', $user->email);
|
||||
$this->assertSame(false, $user->state->arrived);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum config with empty data.
|
||||
*
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testMaximumConfigEmpty(): void
|
||||
{
|
||||
SignUpConfig::setMaximumConfig($this->config);
|
||||
|
||||
$this->assertDataRaisesValidationException(
|
||||
[],
|
||||
[
|
||||
'username' => [
|
||||
'validation.username.required',
|
||||
'validation.username.username',
|
||||
],
|
||||
'email' => [
|
||||
'validation.email.required',
|
||||
'validation.email.email',
|
||||
],
|
||||
'password' => [
|
||||
'validation.password.required',
|
||||
'validation.password.length',
|
||||
],
|
||||
'password_confirmation' => [
|
||||
'validation.password_confirmation.required',
|
||||
],
|
||||
'planned_arrival_date' => [
|
||||
'validation.planned_arrival_date.required',
|
||||
'validation.planned_arrival_date.date',
|
||||
'validation.planned_arrival_date.min',
|
||||
],
|
||||
'tshirt_size' => [
|
||||
'validation.tshirt_size.required',
|
||||
'validation.tshirt_size.shirtSize',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum config with invalid data.
|
||||
*
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testMaximumConfigInvalid(): void
|
||||
{
|
||||
SignUpConfig::setMaximumConfig($this->config);
|
||||
|
||||
$this->assertDataRaisesValidationException(
|
||||
[
|
||||
'username' => 'fritz23',
|
||||
'pronoun' => str_repeat('a', 20),
|
||||
'firstname' => str_repeat('a', 70),
|
||||
'lastname' => str_repeat('a', 70),
|
||||
'email' => 'notanemail',
|
||||
'password' => 'a',
|
||||
'tshirt_size' => 'A',
|
||||
'planned_arrival_date' => $this->now->subDays(7),
|
||||
'dect' => str_repeat('a', 50),
|
||||
'mobile' => str_repeat('a', 50),
|
||||
],
|
||||
[
|
||||
'username' => [
|
||||
'validation.username.username',
|
||||
],
|
||||
'email' => [
|
||||
'validation.email.email',
|
||||
],
|
||||
'mobile' => [
|
||||
'validation.mobile.optional',
|
||||
],
|
||||
'password' => [
|
||||
'validation.password.length',
|
||||
],
|
||||
'password_confirmation' => [
|
||||
'validation.password_confirmation.required',
|
||||
],
|
||||
'firstname' => [
|
||||
'validation.firstname.optional',
|
||||
],
|
||||
'lastname' => [
|
||||
'validation.lastname.optional',
|
||||
],
|
||||
'pronoun' => [
|
||||
'validation.pronoun.optional',
|
||||
],
|
||||
'planned_arrival_date' => [
|
||||
'validation.planned_arrival_date.min',
|
||||
],
|
||||
'dect' => [
|
||||
'validation.dect.optional',
|
||||
],
|
||||
'tshirt_size' => [
|
||||
'validation.tshirt_size.shirtSize',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal config with valid data.
|
||||
*
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testMaximumConfigCreate(): void
|
||||
{
|
||||
SignUpConfig::setMaximumConfig($this->config);
|
||||
|
||||
$user = $this->subject->createFromData([
|
||||
'pronoun' => 'they',
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
'planned_arrival_date' => $this->now->format('Y-m-d'),
|
||||
'tshirt_size' => 'M',
|
||||
'mobile_show' => 1,
|
||||
]);
|
||||
|
||||
$this->assertSame('they', $user->personalData->pronoun);
|
||||
$this->assertSame('fritz', $user->name);
|
||||
$this->assertSame('fritz@example.com', $user->email);
|
||||
$this->assertTrue(password_verify('s3cret', $user->password));
|
||||
$this->assertSame(
|
||||
$this->now->format('Y-m-d'),
|
||||
$user->personalData->planned_arrival_date->format('Y-m-d')
|
||||
);
|
||||
$this->assertTrue($user->settings->mobile_show);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testPasswordDoesNotMatchConfirmation(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
|
||||
$this->assertDataRaisesValidationException(
|
||||
[
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 'huhuuu',
|
||||
],
|
||||
[
|
||||
'password' => [
|
||||
'settings.password.confirmation-does-not-match',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testUsernameAlreadyTaken(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$this->createFritz();
|
||||
|
||||
$this->assertDataRaisesValidationException(
|
||||
[
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
],
|
||||
[
|
||||
'username' => [
|
||||
'settings.profile.nick.already-taken',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testEmailAlreadyTaken(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$this->createFritz();
|
||||
|
||||
$this->assertDataRaisesValidationException(
|
||||
[
|
||||
'username' => 'peter',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
],
|
||||
[
|
||||
'email' => [
|
||||
'settings.profile.email.already-taken',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testAngelTypeAssignment(): void
|
||||
{
|
||||
$angelTypes = $this->createAngelTypes();
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
|
||||
$user = $this->subject->createFromData([
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
'angel_types_' . $angelTypes[0]->id => 1,
|
||||
'angel_types_' . $angelTypes[1]->id => 1,
|
||||
// some angel type, that does not exist
|
||||
'angel_types_asd' => 1,
|
||||
]);
|
||||
|
||||
// Expect an assignment of the normal angel type
|
||||
$this->assertTrue(
|
||||
$user->userAngelTypes->contains('name', $angelTypes[0]->name)
|
||||
);
|
||||
|
||||
// Do not expect an assignment of the angel type hidden on sign-up
|
||||
$this->assertFalse(
|
||||
$user->userAngelTypes->contains('name', $angelTypes[1]->name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testDisablePasswortViaOAuth(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$this->config->set('enable_password', false);
|
||||
$this->session->set('oauth2_enable_password', true);
|
||||
|
||||
$user = $this->subject->createFromData([
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
]);
|
||||
|
||||
$this->assertSame('fritz', $user->name);
|
||||
$this->assertSame('fritz@example.com', $user->email);
|
||||
$this->assertTrue(password_verify('s3cret', $user->password));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testAutoArrive(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$this->config->set('autoarrive', true);
|
||||
|
||||
$user = $this->subject->createFromData([
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
]);
|
||||
|
||||
$this->assertSame('fritz', $user->name);
|
||||
$this->assertSame('fritz@example.com', $user->email);
|
||||
$this->assertSame(true, $user->state->arrived);
|
||||
$this->assertEqualsWithDelta(
|
||||
$this->now->timestamp,
|
||||
$user->state->arrival_date->timestamp,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Covers the case where both, build-up and tear-down dates are configured.
|
||||
*
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testBuildUpAndTearDownDates(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$this->config->set('enable_planned_arrival', true);
|
||||
$this->config->set('buildup_start', $this->now);
|
||||
$this->config->set('teardown_end', $this->now->addDays(7));
|
||||
|
||||
$this->assertDataRaisesValidationException(
|
||||
[
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
'planned_arrival_date' => $this->now->subDays(7),
|
||||
],
|
||||
[
|
||||
'planned_arrival_date' => [
|
||||
'validation.planned_arrival_date.between',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Factories\User
|
||||
*/
|
||||
public function testOAuth(): void
|
||||
{
|
||||
SignUpConfig::setMinimumConfig($this->config);
|
||||
$this->session->set('oauth2_connect_provider', 'sso');
|
||||
$this->session->set('oauth2_user_id', 'fritz_sso');
|
||||
$this->session->set('oauth2_access_token', 'abc123');
|
||||
$this->session->set('oauth2_refresh_token', 'jkl456');
|
||||
$this->session->set('oauth2_expires_at', '2023-08-15 08:00:00');
|
||||
|
||||
$user = $this->subject->createFromData([
|
||||
'username' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => 's3cret',
|
||||
'password_confirmation' => 's3cret',
|
||||
]);
|
||||
|
||||
$oAuth = $user->oauth->first();
|
||||
$this->assertNotNull($oAuth);
|
||||
$this->assertSame('sso', $oAuth->provider);
|
||||
$this->assertSame('fritz_sso', $oAuth->identifier);
|
||||
$this->assertSame('abc123', $oAuth->access_token);
|
||||
$this->assertSame('jkl456', $oAuth->refresh_token);
|
||||
$this->assertSame('2023-08-15 08:00:00', $oAuth->expires_at->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user with nick "fritz" and email "fritz@example.com".
|
||||
*/
|
||||
private function createFritz(): void
|
||||
{
|
||||
UserModel::create([
|
||||
'name' => 'fritz',
|
||||
'email' => 'fritz@example.com',
|
||||
'password' => '',
|
||||
'api_key' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates two AngelTypes:
|
||||
* 1. Normal angel type
|
||||
* 2. Angel type hidden on sign-up
|
||||
*
|
||||
* @return Array<AngelType>
|
||||
*/
|
||||
private function createAngelTypes(): array
|
||||
{
|
||||
return [
|
||||
AngelType::create([
|
||||
'name' => 'Test angel type 1',
|
||||
]),
|
||||
AngelType::create([
|
||||
'name' => 'Test angel type 2',
|
||||
'hide_register' => true,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Array<string, mixed> $data Data passed to User::createFromData
|
||||
* @param Array<string, Array<string>> $expectedValidationErrors Expected validation errors
|
||||
*/
|
||||
private function assertDataRaisesValidationException(array $data, array $expectedValidationErrors): void
|
||||
{
|
||||
try {
|
||||
$this->subject->createFromData($data);
|
||||
self::fail('Expected exception not raised');
|
||||
} catch (ValidationException $err) {
|
||||
$validator = $err->getValidator();
|
||||
$validationErrors = $validator->getErrors();
|
||||
$this->assertSame($expectedValidationErrors, $validationErrors);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ use Engelsystem\Database\Migration\Migrate;
|
|||
use Engelsystem\Database\Migration\MigrationServiceProvider;
|
||||
use Engelsystem\Http\Request;
|
||||
use Illuminate\Database\Capsule\Manager as CapsuleManager;
|
||||
use Illuminate\Database\Connection;
|
||||
use PDO;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
|
@ -30,6 +31,7 @@ trait HasDatabase
|
|||
$this->database = new Database($connection);
|
||||
|
||||
$this->app->instance(Database::class, $this->database);
|
||||
$this->app->instance(Connection::class, $connection);
|
||||
$this->app->register(MigrationServiceProvider::class);
|
||||
|
||||
$this->app->instance(ServerRequestInterface::class, new Request());
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Test\Utils;
|
||||
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* Class that provides some form field assertions.
|
||||
*/
|
||||
final class FormFieldAssert
|
||||
{
|
||||
/**
|
||||
* Asserts that the HTML does contain an INPUT field with the given name.
|
||||
*/
|
||||
public static function assertContainsInputField(string $name, string $html): void
|
||||
{
|
||||
Assert::assertMatchesRegularExpression(self::makeInputPattern('input', $name), $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the HTML does not contain an INPUT field with the given name.
|
||||
*/
|
||||
public static function assertNotContainsInputField(string $name, string $html): void
|
||||
{
|
||||
Assert::assertDoesNotMatchRegularExpression(self::makeInputPattern('input', $name), $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the HTML does contain a SELECT field with the given name.
|
||||
*/
|
||||
public static function assertContainsSelectField(string $name, string $html): void
|
||||
{
|
||||
Assert::assertMatchesRegularExpression(self::makeInputPattern('select', $name), $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the HTML does not contain a SELECT field with the given name.
|
||||
*/
|
||||
public static function assertNotContainsSelectField(string $name, string $html): void
|
||||
{
|
||||
Assert::assertDoesNotMatchRegularExpression(self::makeInputPattern('select', $name), $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the HTML does contain an INPUT field of the type "checkbox" with the give name and
|
||||
* with the "checked" attribute.
|
||||
*/
|
||||
public static function assertContainsCheckedCheckbox(string $name, string $html): void
|
||||
{
|
||||
Assert::assertMatchesRegularExpression(self::makeCheckedCheckboxPattern($name), $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the HTML does contain an INPUT field of the type "checkbox" with the given name and
|
||||
* without the "checked" attribute.
|
||||
*/
|
||||
public static function assertContainsUncheckedCheckbox(string $name, string $html): void
|
||||
{
|
||||
self::assertContainsInputField($name, $html);
|
||||
Assert::assertDoesNotMatchRegularExpression(self::makeCheckedCheckboxPattern($name), $html);
|
||||
}
|
||||
|
||||
private static function makeInputPattern(string $tag, string $name): string
|
||||
{
|
||||
return strtr('/<$TAG[^>]*name="$NAME"/s', [
|
||||
'$TAG' => $tag,
|
||||
'$NAME' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
private static function makeCheckedCheckboxPattern(string $name): string
|
||||
{
|
||||
return strtr('/<input[^>]*type="checkbox"[^>]*name="$NAME"[^>]*checked.*?/s', [
|
||||
'$NAME' => $name,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Test\Utils;
|
||||
|
||||
use Engelsystem\Config\Config;
|
||||
use Engelsystem\Config\GoodieType;
|
||||
|
||||
final class SignUpConfig
|
||||
{
|
||||
public static function setMaximumConfig(Config $config): void
|
||||
{
|
||||
$config->set('registration_enabled', true);
|
||||
$config->set('enable_password', true);
|
||||
$config->set('enable_pronoun', true);
|
||||
$config->set('goodie_type', GoodieType::Tshirt->value);
|
||||
$config->set('tshirt_sizes', [
|
||||
'S' => 'Small Straight-Cut',
|
||||
'M' => 'Medium Straight-Cut',
|
||||
]);
|
||||
// disallow numeric values in username for tests
|
||||
$config->set('username_regex', '/\d+/');
|
||||
$config->set('min_password_length', 3);
|
||||
$config->set('theme', 0);
|
||||
$config->set('enable_planned_arrival', true);
|
||||
$config->set('enable_user_name', true);
|
||||
$config->set('enable_mobile_show', true);
|
||||
$config->set('enable_dect', true);
|
||||
}
|
||||
|
||||
public static function setMinimumConfig(Config $config): void
|
||||
{
|
||||
$config->set('registration_enabled', true);
|
||||
$config->set('enable_password', true);
|
||||
$config->set('enable_pronoun', false);
|
||||
$config->set('goodie_type', GoodieType::None->value);
|
||||
// disallow numeric values in username for tests
|
||||
$config->set('username_regex', '/\d+/');
|
||||
$config->set('min_password_length', 3);
|
||||
$config->set('theme', 0);
|
||||
$config->set('enable_planned_arrival', false);
|
||||
$config->set('enable_user_name', false);
|
||||
$config->set('enable_mobile_show', false);
|
||||
$config->set('enable_dect', false);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue