diff --git a/app/controllers/registration.php b/app/controllers/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..edb6b42d0ed31decc5a84d9194ebd9089552192e --- /dev/null +++ b/app/controllers/registration.php @@ -0,0 +1,133 @@ +<?php +class RegistrationController extends AuthenticatedController +{ + protected $allow_nobody = true; + + public function before_filter(&$action, &$args) + { + parent::before_filter($action, $args); + + PageLayout::setTitle(_('Registrierung')); + } + + public function index_action() + { + $new_user = new User(); + + $this->registrationform = \Studip\Forms\Form::fromSORM( + $new_user, + [ + 'legend' => _('Herzlich willkommen!'), + 'fields' => [ + 'username' => [ + 'label' => _('Benutzername'), + 'required' => true, + 'maxlength' => '63', + 'validate' => function ($value, $input) { + if (!preg_match(Config::get()->USERNAME_REGULAR_EXPRESSION, $value)) { + return Config::get()->getMetadata('USERNAME_REGULAR_EXPRESSION')['comment'] ?: + _('Benutzername muss mindestens 4 Zeichen lang sein und darf nur aus Buchstaben, ' + . 'Ziffern, Unterstrich, @, Punkt und Minus bestehen.'); + } + $user = User::findByUsername($value); + $context = $input->getContextObject(); + if ($user && ($user->id !== $context->getId())) { + return _('Benutzername ist schon vergeben.'); + } + return true; + } + ], + 'password' => [ + 'label' => _('Passwort'), + 'type' => 'password', + 'required' => true, + 'maxlength' => '31', + 'minlength' => '8', + 'mapper' => function($value) { + $hasher = UserManagement::getPwdHasher(); + return $hasher->HashPassword($value); + } + ], + 'confirm_password' => [ + 'label' => _('Passwortbestätigung'), + 'type' => 'password', + 'required' => true, + 'maxlength' => '31', + 'minlength' => '8', + ':pattern' => "password.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')", //mask special chars + 'data-validation_requirement' => _('Passwörter stimmen nicht überein.'), + 'store' => function() {} + ], + 'title_front' => [ + 'label' => _('Titel'), + 'type' => 'datalist', + 'options' => $GLOBALS['TITLE_FRONT_TEMPLATE'] + ], + 'title_rear' => [ + 'label' => _('Titel nachgestellt'), + 'type' => 'datalist', + 'options' => $GLOBALS['TITLE_REAR_TEMPLATE'], + ], + 'vorname' => [ + 'label' => _('Vorname'), + 'required' => true + ], + 'nachname' => [ + 'label' => _('Nachname'), + 'required' => true + ], + 'geschlecht' => [ + 'name' => 'geschlecht', + 'label' => _('Geschlecht'), + 'type' => 'radio', + 'orientation' => 'horizontal', + 'options' => [ + '0' => _('keine Angabe'), + '1' => _('männlich'), + '2' => _('weiblich'), + '3' => _('divers'), + ], + ], + 'email' => [ + 'label' => _('E-Mail'), + 'required' => true, + 'validate' => function ($value, $input) { + $user = User::findOneByEmail($value); + $context = $input->getContextObject(); + if ($user && ($user->id !== $context->getId())) { + return _('Diese Emailadresse ist bereits registriert.'); + } + return true; + } + ], + ] + ] + ); + $this->registrationform->setSaveButtonText(_('Registrierung abschließen')); + $this->registrationform->setCancelButtonText(_('Abbrechen')); + $this->registrationform->setCancelButtonName(URLHelper::getURL('index.php?cancel_login=1')); + + $this->registrationform->addStoreCallback( + function ($form) { + $new_user = $form->getLastPart()->getContextObject(); + + $GLOBALS['sess']->regenerate_session_id(['auth']); + $GLOBALS['auth']->unauth(); + $GLOBALS['auth']->auth['jscript'] = true; + $GLOBALS['auth']->auth['perm'] = $new_user['perms']; + $GLOBALS['auth']->auth['uname'] = $new_user['username']; + $GLOBALS['auth']->auth['auth_plugin'] = $new_user['auth_plugin']; + $GLOBALS['auth']->auth_set_user_settings($new_user->user_id); + $GLOBALS['auth']->auth['uid'] = $new_user['user_id']; + $GLOBALS['auth']->auth['exp'] = time() + (60 * $GLOBALS['auth']->lifetime); + $GLOBALS['auth']->auth['refresh'] = time() + (60 * $GLOBALS['auth']->refresh); + + Seminar_Register_Auth::sendValidationMail($new_user); + + return 1; + } + ); + + $this->registrationform->autoStore()->setURL(URLHelper::getURL('dispatch.php/start')); + } +} diff --git a/app/views/registration/index.php b/app/views/registration/index.php new file mode 100644 index 0000000000000000000000000000000000000000..b253987c6c8c462ff6fefd40491cb083270badd9 --- /dev/null +++ b/app/views/registration/index.php @@ -0,0 +1 @@ +<?= $registrationform->render() ?> diff --git a/templates/register/success.php b/app/views/registration/save.php similarity index 100% rename from templates/register/success.php rename to app/views/registration/save.php diff --git a/db/migrations/5.5.19_update_username_regular_expression.php b/db/migrations/5.5.19_update_username_regular_expression.php new file mode 100644 index 0000000000000000000000000000000000000000..cdcbc9d40bc810742e2c4585b55b5ca3032c4679 --- /dev/null +++ b/db/migrations/5.5.19_update_username_regular_expression.php @@ -0,0 +1,27 @@ +<?php +class UpdateUsernameRegularExpression extends Migration +{ + public function description() + { + return 'Alters configuration description to make clearer that it is used for creating new users'; + } + + public function up() + { + $db = DBManager::get(); + $stmt = $db->prepare("UPDATE `config` + SET `description` = 'Regulärer Ausdruck für erlaubte Zeichen in Benutzernamen. Das Kommentarfeld kann genutzt werden, um eine Fehlermeldung anzugeben, die zum Beispiel im Registrierungsformular ausgegeben wird, wenn der Ausdruck nicht erfüllt wird.' + WHERE `field` = 'USERNAME_REGULAR_EXPRESSION'"); + $stmt->execute(); + } + + public function down() + { + $db = DBManager::get(); + $stmt = $db->prepare("UPDATE `config` + SET `description` = 'Regex for allowed characters in usernames' + WHERE `field` = 'USERNAME_REGULAR_EXPRESSION'"); + $stmt->execute(); + + } +} diff --git a/lib/classes/forms/ConfirmInput.php b/lib/classes/forms/ConfirmInput.php new file mode 100644 index 0000000000000000000000000000000000000000..13e36348e4a362e650d1b9cb12979d7d446ede66 --- /dev/null +++ b/lib/classes/forms/ConfirmInput.php @@ -0,0 +1,18 @@ +<?php + +namespace Studip\Forms; + +class ConfirmInput extends Input +{ + public function render() + { + $template = $GLOBALS['template_factory']->open('forms/confirm_password_input'); + $template->title = $this->title; + $template->name = $this->name; + $template->value = $this->value; + $template->id = md5(uniqid()); + $template->required = $this->required; + $template->attributes = arrayToHtmlAttributes($this->attributes); + return $template->render(); + } +} diff --git a/lib/classes/forms/DatalistInput.php b/lib/classes/forms/DatalistInput.php new file mode 100644 index 0000000000000000000000000000000000000000..06cc9e030962f2feb9fa6b29c49bdbf97fb61623 --- /dev/null +++ b/lib/classes/forms/DatalistInput.php @@ -0,0 +1,21 @@ +<?php + +namespace Studip\Forms; + +class DatalistInput extends Input +{ + public function render() + { + $options = $this->extractOptionsFromAttributes($this->attributes); + + $template = $GLOBALS['template_factory']->open('forms/datalist_input'); + $template->title = $this->title; + $template->name = $this->name; + $template->value = $this->value; + $template->id = md5(uniqid()); + $template->required = $this->required; + $template->attributes = arrayToHtmlAttributes($this->attributes); + $template->options = $options; + return $template->render(); + } +} diff --git a/lib/classes/forms/Form.php b/lib/classes/forms/Form.php index 49713b73db321f1a1e64202a674ad10b13abc26d..3b295524d1b95f52a639267a8206ad41c6c81235 100644 --- a/lib/classes/forms/Form.php +++ b/lib/classes/forms/Form.php @@ -17,6 +17,8 @@ class Form extends Part protected $save_button_text = ''; protected $save_button_name = ''; + protected $cancel_button_text = ''; + protected $cancel_button_name = ''; protected $autoStore = false; protected $debugmode = false; protected $success_message = ''; @@ -207,6 +209,31 @@ class Form extends Part return $this->save_button_name ?: $this->getSaveButtonText(); } + public function setCancelButtonText(string $text): Form + { + $this->cancel_button_text = $text; + return $this; + } + + /** + * @return string The text for the "save" button in the form. + */ + public function getCancelButtonText() : string + { + return $this->cancel_button_text ?: _('Abbrechen'); + } + + public function setCancelButtonName(string $name): Form + { + $this->cancel_button_name = $name; + return $this; + } + + public function getCancelButtonName() : string + { + return $this->cancel_button_name ?: $this->getCancelButtonText(); + } + public function setSuccessMessage(string $success_message): Form { $this->success_message = $success_message; @@ -250,12 +277,35 @@ class Form extends Part { $this->autoStore = true; if (\Request::isPost() && \Request::isAjax() && !\Request::isDialog()) { - $this->store(); - if ($this->success_message) { - \PageLayout::postSuccess($this->success_message); + if (\Request::submitted('STUDIPFORM_SERVERVALIDATION')) { + //verify the user input: + $output = []; + foreach ($this->getAllInputs() as $input) { + if ($input->validate) { + $callback = $input->getValidationCallback(); + $value = $this->getStorableValueFromRequest($input); + $valid = $callback($value, $input); + if ($valid !== true) { + $output[$input->getName()] = [ + 'name' => $input->getName(), + 'label' => $input->getTitle(), + 'error' => $callback($value, $input) + ]; + } + } + } + echo json_encode($output); + page_close(); + die(); + } else { + //storing the input + $this->store(); + if ($this->success_message) { + \PageLayout::postSuccess($this->success_message); + } + page_close(); + die(); } - page_close(); - die(); } return $this; } @@ -325,6 +375,17 @@ class Form extends Part $stored = 0; + foreach ($this->getAllInputs() as $input) { + if ($input->validate) { + $callback = $input->getValidationCallback(); + $value = $this->getStorableValueFromRequest($input); + $valid = $callback($value, $input); + if ($valid !== true) { + return $stored; + } + } + } + //store by each input $all_values = []; foreach ($this->getAllInputs() as $input) { diff --git a/lib/classes/forms/Input.php b/lib/classes/forms/Input.php index aa3069b70b1224c560d9337a1d0eb8c562158b93..9d2ad327caef281ab4924d2cc8c5f8f64857b8e1 100644 --- a/lib/classes/forms/Input.php +++ b/lib/classes/forms/Input.php @@ -11,6 +11,7 @@ abstract class Input protected $parent = null; public $mapper = null; public $store = null; + public $validate = null; public $if = null; public $permission = true; public $required = false; @@ -133,6 +134,21 @@ abstract class Input return $this->name; } + public function getTitle() + { + return $this->title; + } + + public function hasValidation() + { + return $this->validate !== null; + } + + public function getValidationCallback() + { + return $this->validate; + } + /** * Returns the value of this input. * @return null @@ -215,6 +231,18 @@ abstract class Input return $this; } + /** + * Sets the server-side verify function of this input. The callable returns true if the given value is okay, or + * false or a textstring representing the error. + * @param callable $verify + * @return $this + */ + public function setValidationFunction(Callable $validate) + { + $this->validate = $validate; + return $this; + } + /** * Sets a condition to display this input. The condition is a javascript condition which is used by vue to * hide the input if the condition is not satisfies. @@ -261,7 +289,7 @@ abstract class Input protected function extractOptionsFromAttributes(array &$attributes) { - $options = null; + $options = []; if (isset($attributes['options'])) { $options = $attributes['options']; unset($attributes['options']); diff --git a/lib/classes/forms/Part.php b/lib/classes/forms/Part.php index 3609eb4fad39be7756c2f501fe5c6a483fba8866..fdca8f5395af8e88dd99b8b5f05bba4735709360 100644 --- a/lib/classes/forms/Part.php +++ b/lib/classes/forms/Part.php @@ -235,6 +235,7 @@ abstract class Part $attributes['type'], $attributes['mapper'], $attributes['store'], + $attributes['validate'], $attributes['if'], $attributes['permission'], $attributes['required'], @@ -257,6 +258,9 @@ abstract class Part if (isset($data['store']) && is_callable($data['store'])) { $input->store = $data['store']; } + if (isset($data['validate']) && is_callable($data['validate'])) { + $input->validate = $data['validate']; + } if (!empty($data['if'])) { $input->if = $data['if']; } diff --git a/lib/classes/forms/PasswordInput.php b/lib/classes/forms/PasswordInput.php new file mode 100644 index 0000000000000000000000000000000000000000..68e4718ed037ea5b611ad202cf26f33ecbb5b4f5 --- /dev/null +++ b/lib/classes/forms/PasswordInput.php @@ -0,0 +1,18 @@ +<?php + +namespace Studip\Forms; + +class PasswordInput extends Input +{ + public function render() + { + $template = $GLOBALS['template_factory']->open('forms/password_input'); + $template->title = $this->title; + $template->name = $this->name; + $template->value = $this->value; + $template->id = md5(uniqid()); + $template->required = $this->required; + $template->attributes = arrayToHtmlAttributes($this->attributes); + return $template->render(); + } +} diff --git a/lib/classes/forms/RadioInput.php b/lib/classes/forms/RadioInput.php new file mode 100644 index 0000000000000000000000000000000000000000..1945d87cbb868f9fd139bde12750ff9552247833 --- /dev/null +++ b/lib/classes/forms/RadioInput.php @@ -0,0 +1,22 @@ +<?php + +namespace Studip\Forms; + +class RadioInput extends Input +{ + public function render() + { + $options = $this->extractOptionsFromAttributes($this->attributes); + $template = $GLOBALS['template_factory']->open('forms/radio_input'); + $template->title = $this->title; + $template->name = $this->name; + $template->value = $this->value; + $template->id = md5(uniqid()); + $template->required = $this->required; + $template->options = $options; + $template->attributes = arrayToHtmlAttributes($this->attributes); + $template->orientation = $this->attributes['orientation']; + return $template->render(); + + } +} diff --git a/lib/classes/forms/RangeInput.php b/lib/classes/forms/RangeInput.php index 9f99d592929655fc7a0074fcf71179ccf836c4b8..7a69e9eacfb6a88a1ea66561164b4d2d8f97291c 100644 --- a/lib/classes/forms/RangeInput.php +++ b/lib/classes/forms/RangeInput.php @@ -11,6 +11,9 @@ class RangeInput extends Input $template->name = $this->name; $template->value = $this->value; $template->id = md5(uniqid()); + $template->min = $this->attributes['min']; + $template->max = $this->attributes['max']; + $template->step = $this->attributes['step']; $template->required = $this->required; $template->attributes = arrayToHtmlAttributes($this->attributes); return $template->render(); diff --git a/lib/navigation/LoginNavigation.php b/lib/navigation/LoginNavigation.php index 96ec90ed8979128d05027795ec5a8c7c4844bc84..bb63a03e97f19f655fdb4614630013e9643b6931 100644 --- a/lib/navigation/LoginNavigation.php +++ b/lib/navigation/LoginNavigation.php @@ -43,9 +43,9 @@ class LoginNavigation extends Navigation } if (Config::get()->ENABLE_SELF_REGISTRATION) { - $navigation = new Navigation(_('Registrieren'), 'register1.php'); + $navigation = new Navigation(_('Registrieren'), 'dispatch.php/registration'); $navigation->setDescription(_('um das System erstmalig zu nutzen')); - $this->addSubNavigation('register', $navigation); + $this->addSubNavigation('registration', $navigation); } if (Config::get()->ENABLE_FREE_ACCESS) { diff --git a/public/register1.php b/public/register1.php deleted file mode 100644 index 7b4740f931e7710fce5cc9535f69cbe32e7494cb..0000000000000000000000000000000000000000 --- a/public/register1.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php -/** - * register1.php - Benutzerregistrierung in Stud.IP, Part I - * - * @author Stefan Suchi <suchi@gmx.de> - * @author Oliver Brakel <obrakel@gwdg.de> - * @copyright 2000 authors - * @license GPL2 or any later version - */ - -require '../lib/bootstrap.php'; - -page_open([ - 'sess' => 'Seminar_Session', - 'auth' => 'Seminar_Default_Auth', - 'perm' => 'Seminar_Perm', - 'user' => 'Seminar_User', -]); - -include 'lib/seminar_open.php'; // initialise Stud.IP-Session - -if (!Config::get()->ENABLE_SELF_REGISTRATION) { - PageLayout::postError(_('Registrierung ausgeschaltet'), [ - _('In dieser Installation ist die Möglichkeit zur Registrierung ausgeschaltet.'), - sprintf( - '<a href="%s">%s</a>', - URLHelper::getLink('index.php'), - _('Hier geht es zur Startseite.') - ) - ]); - - echo $GLOBALS['template_factory']->render('layouts/base.php', [ - 'content_for_layout' => '', - ]); -} elseif (Config::get()->SHOW_TERMS_ON_FIRST_LOGIN) { - header('Location: ' . URLHelper::getURL('register2.php')); -} elseif ($GLOBALS['auth']->is_authenticated() && $GLOBALS['user']->id !== 'nobody') { - PageLayout::postError(_('Sie sind schon als BenutzerIn am System angemeldet!'), [ - sprintf( - '<a href="%s">%s</a>', - URLHelper::getLink('index.php'), - _('Hier geht es zur Startseite.') - ) - ]); - echo $GLOBALS['template_factory']->render('layouts/base.php', [ - 'content_for_layout' => '', - ]); -} else { - PageLayout::setHelpKeyword('Basis.AnmeldungRegistrierung'); - PageLayout::setTitle(_('Nutzungsbedingungen')); - echo $GLOBALS['template_factory']->render( - 'register/step1.php', - [], - $GLOBALS['template_factory']->open('layouts/base.php') - ); - $auth->logout(); -} - -page_close(); diff --git a/public/register2.php b/public/register2.php deleted file mode 100644 index 8d73c81ee6952012fcf547ac65fd16ab9ca9d06f..0000000000000000000000000000000000000000 --- a/public/register2.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php -/** - * register2.php - Benutzerregistrierung in Stud.IP, Part II - * - * @author Stefan Suchi <suchi@gmx.de> - * @author Oliver Brakel <obrakel@gwdg.de> - * @copyright 2000 authors - * @license GPL2 or any later version - */ - -require '../lib/bootstrap.php'; - -page_open([ - 'sess' => 'Seminar_Session', - 'auth' => Config::get()->ENABLE_SELF_REGISTRATION ? 'Seminar_Register_Auth' : 'Seminar_Default_Auth', - 'perm' => 'Seminar_Perm', - 'user' => 'Seminar_User', -]); - -if (!Config::get()->ENABLE_SELF_REGISTRATION){ - PageLayout::postError(_('Registrierung ausgeschaltet'), [ - _('In dieser Installation ist die Möglichkeit zur Registrierung ausgeschaltet.'), - sprintf( - '<a href="%s">%s</a>', - URLHelper::getLink('index.php'), - _('Hier geht es zur Startseite.') - ) - ]); - - echo $GLOBALS['template_factory']->render('layouts/base.php', [ - 'content_for_layout' => '', - ]); -} elseif ($GLOBALS['auth']->auth['uid'] === 'nobody') { - $GLOBALS['auth']->logout(); - header('Location: ' . URLHelper::getURL('register2.php')); -} else { - include 'lib/seminar_open.php'; // initialise Stud.IP-Session - - PageLayout::setHelpKeyword('Basis.AnmeldungRegistrierung'); - PageLayout::setTitle(_('Registrierung erfolgreich')); - echo $GLOBALS['template_factory']->render( - 'register/success.php', - [], - $GLOBALS['template_factory']->open('layouts/base.php') - ); - $GLOBALS['auth']->logout(); -} - -page_close(); diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js index b0e66d31c10e448eedf54a059dd699e9bb3cd3ce..2cbac9ea08dc59ceecad899b923499c8697b9eab 100644 --- a/resources/assets/javascripts/bootstrap/forms.js +++ b/resources/assets/javascripts/bootstrap/forms.js @@ -250,12 +250,15 @@ STUDIP.ready(function () { data() { let params = JSON.parse(f.dataset.inputs); params.STUDIPFORM_REQUIRED = f.dataset.required ? JSON.parse(f.dataset.required) : []; + params.STUDIPFORM_SERVERVALIDATION = f.dataset.server_validation > 0; params.STUDIPFORM_DISPLAYVALIDATION = false; params.STUDIPFORM_VALIDATIONNOTES = []; params.STUDIPFORM_AUTOSAVEURL = f.dataset.autosave; params.STUDIPFORM_REDIRECTURL = f.dataset.url; - params.STUDIPFORM_SELECTEDLANGUAGES = {}; - params.STUDIPFORM_DEBUGMODE = JSON.parse(f.dataset.debugmode); + params.STUDIPFORM_INPUTS_ORDER = []; + for (let i in JSON.parse(f.dataset.inputs)) { + params.STUDIPFORM_INPUTS_ORDER.push(i); + } return params; }, methods: { @@ -265,31 +268,31 @@ STUDIP.ready(function () { this.STUDIPFORM_DISPLAYVALIDATION = true; //validation: - let validated = this.validate(); - - if (!validated) { - e.preventDefault(); - v.$el.scrollIntoView({ - "behavior": "smooth" - }); - return; - } - - if (this.STUDIPFORM_AUTOSAVEURL) { - let params = this.getFormValues(); + let validation_promise = this.validate(); + validation_promise.then(function (validated) { + if (!validated) { + v.$el.scrollIntoView({ + behavior: 'smooth' + }); + return; + } - $.ajax({ - url: this.STUDIPFORM_AUTOSAVEURL, - data: params, - type: 'post', - success() { - if (v.STUDIPFORM_REDIRECTURL && !v.STUDIPFORM_DEBUGMODE) { - window.location.href = v.STUDIPFORM_REDIRECTURL; + if (v.STUDIPFORM_AUTOSAVEURL) { + let params = v.getFormValues(); + + $.ajax({ + url: v.STUDIPFORM_AUTOSAVEURL, + data: params, + type: 'post', + success() { + if (v.STUDIPFORM_REDIRECTURL) { + window.location.href = v.STUDIPFORM_REDIRECTURL + } } - } - }); - e.preventDefault(); - } + }); + } + }); + e.preventDefault(); }, getFormValues() { let v = this; @@ -311,32 +314,74 @@ STUDIP.ready(function () { let v = this; this.STUDIPFORM_VALIDATIONNOTES = []; - let validated = this.$el.checkValidity(); - - $(this.$el).find('input, select, textarea').each(function () { - if (!this.validity.valid) { - let note = { - name: $(this.labels[0]).find('.textlabel').text(), - description: $gettext('Fehler!'), - describedby: this.id - }; - if (this.validity.tooShort) { - note.description = $gettextInterpolate( - $gettext('Geben Sie mindestens %{min} Zeichen ein.'), - {min: this.minLength} - ); - } - if (this.validity.valueMissing) { - if (this.type === 'checkbox') { - note.description = $gettext('Dieses Feld muss ausgewählt sein.'); - } else { - note.description = $gettext('Hier muss ein Wert eingetragen werden.'); + let validation_promise = new Promise(function (resolve, reject) { + let validated = v.$el.checkValidity(); + + $(v.$el).find('input, select, textarea').each(function () { + let name = $(this).attr('name'); + if (!this.validity.valid) { + let note = { + name: this.name, + label: $(this.labels[0]).find('.textlabel').text(), + description: $gettext('Fehler!'), + describedby: this.id + }; + if ($(this).data('validation_requirement')) { + note.description = $(this).data('validation_requirement'); + } + if (this.validity.tooShort) { + note.description = $gettextInterpolate( + $gettext('Geben Sie mindestens %{min} Zeichen ein.'), + {min: this.minLength} + ); + } + if (this.validity.valueMissing) { + if (this.type === 'checkbox') { + note.description = $gettext('Dieses Feld muss ausgewählt sein.'); + } else { + if (this.minLength > 0) { + note.description = $gettextInterpolate( + $gettext('Hier muss ein Wert mit mindestens %{min} Zeichen eingetragen werden.'), + {min: this.minLength} + ); + } else { + note.description = $gettext('Hier muss ein Wert eingetragen werden.'); + } + + } } + v.STUDIPFORM_VALIDATIONNOTES.push(note); } - v.STUDIPFORM_VALIDATIONNOTES.push(note); + }); + if (v.STUDIPFORM_SERVERVALIDATION) { + + let params = v.getFormValues(); + params.STUDIPFORM_SERVERVALIDATION = 1; + + $.ajax({ + url: v.STUDIPFORM_AUTOSAVEURL, + data: params, + type: 'post', + dataType: 'json', + success(output) { + for (let i in output) { + let note = { + name: output[i].name, + label: output[i].label, + description: output[i].error, + describedby: null + }; + v.STUDIPFORM_VALIDATIONNOTES.push(note); + } + validated = v.STUDIPFORM_VALIDATIONNOTES.length < 1; + resolve(validated); + } + }); + } else { + resolve(validated); } }); - return validated; + return validation_promise; }, setInputs(inputs) { for (const [key, value] of Object.entries(inputs)) { @@ -353,6 +398,19 @@ STUDIP.ready(function () { this.STUDIPFORM_SELECTEDLANGUAGES = languages; } }, + computed: { + ordererValidationNotes: function () { + let orderedNotes = []; + for (let i in this.STUDIPFORM_INPUTS_ORDER) { + for (let k in this.STUDIPFORM_VALIDATIONNOTES) { + if (this.STUDIPFORM_VALIDATIONNOTES[k].name === this.STUDIPFORM_INPUTS_ORDER[i]) { + orderedNotes.push(this.STUDIPFORM_VALIDATIONNOTES[k]); + } + } + } + return orderedNotes; + } + }, mounted () { $(this.$el).addClass("vueified"); } @@ -362,7 +420,7 @@ STUDIP.ready(function () { } // Well, this is really nasty: Select2 can't determine the select - // element's width if it is hidden (by itself or by it's parent). + // element's width if it is hidden (by itself or by its parent). // This is due to the fact that elements are not rendered when hidden // (which seems pretty obvious when you think about it) but elements // only have a width when they are rendered (pretty obvious as well). diff --git a/resources/assets/stylesheets/scss/forms.scss b/resources/assets/stylesheets/scss/forms.scss index 72e2d26acdc6e86e99026cb8f80452644cf5f22b..0b43f044afae4f571df093bacc3b504bacdf7ddd 100644 --- a/resources/assets/stylesheets/scss/forms.scss +++ b/resources/assets/stylesheets/scss/forms.scss @@ -70,6 +70,14 @@ form.default { } } + input[list] { + @include background-icon(arr_1down, clickable); + + background-repeat: no-repeat; + background-position: center right 4px; + padding-right: 24px + } + textarea:not(.size-l) + .ck-editor { max-width: $max-width-m; } diff --git a/templates/forms/confirm_password_input.php b/templates/forms/confirm_password_input.php new file mode 100644 index 0000000000000000000000000000000000000000..e2e2583fb9f4f75749e5b33fca4fc2303b694a76 --- /dev/null +++ b/templates/forms/confirm_password_input.php @@ -0,0 +1,16 @@ +<div class="formpart"> + <label <?= $this->required ? 'class="studiprequired"' : '' ?> for="<?= $id ?>"> + <span class="textlabel"> + <?= htmlReady($this->title) ?> + </span> + <? if ($this->required) : ?> + <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span> + <? endif ?> + <input type="password" + v-model="<?= htmlReady($this->name) ?>" + name="<?= htmlReady($this->name) ?>" + value="<?= htmlReady($this->value) ?>" + id="<?= $id ?>" <?= $this->required ? 'required aria-required="true"' : '' ?> + <?= $attributes ?>> + </label> +</div> diff --git a/templates/forms/datalist_input.php b/templates/forms/datalist_input.php new file mode 100644 index 0000000000000000000000000000000000000000..7958c332875ee1d8cb667ea00135c4790d14f7e9 --- /dev/null +++ b/templates/forms/datalist_input.php @@ -0,0 +1,22 @@ +<div class="formpart"> + <label <?= $this->required ? 'class="studiprequired"' : '' ?> for="<?= $id ?>"> + <span class="textlabel"> + <?= htmlReady($this->title) ?> + </span> + <? if ($this->required) : ?> + <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span> + <? endif ?> + + <input type="text" list="<?= $this->title ?>" id="" v-model="<?= htmlReady($this->name) ?>" <?= $this->required ? 'required aria-required="true"' : '' ?> /> + + <datalist class="" id="<?= $this->title ?>" <?= $attributes ?>> + <? foreach ($options as $key => $option) : ?> + <option value="<?= htmlReady($option) ?>"<?= ($option == $value ? " selected" : "") ?>> + </option> + <? endforeach ?> + </datalist> + </label> + +</div> + + diff --git a/templates/forms/form.php b/templates/forms/form.php index 05b0b302fe8e38231c7fc12cc90dd3ca8266ae1c..96cd2c055d9cb9ac20c2e0fb6589224e97c60291 100644 --- a/templates/forms/form.php +++ b/templates/forms/form.php @@ -2,14 +2,17 @@ $inputs = []; $allinputs = $form->getAllInputs(); $required_inputs = []; +$server_validation = false; foreach ($allinputs as $input) { foreach ($input->getAllInputNames() as $name) { $inputs[$name] = $input->getValue(); } - if ($input->required) { $required_inputs[] = $input->getName(); } + if ($input->hasValidation()) { + $server_validation = true; + } } $form_id = md5(uniqid()); ?><form v-cloak @@ -21,12 +24,14 @@ $form_id = md5(uniqid()); data-url="<?= htmlReady($form->getURL()) ?>" <? endif ?> @submit="submit" + @cancel="" novalidate <?= $form->getDataSecure() ? 'data-secure' : '' ?> id="<?= htmlReady($form_id) ?>" data-inputs="<?= htmlReady(json_encode($inputs)) ?>" data-debugmode="<?= htmlReady(json_encode($form->getDebugMode())) ?>" data-required="<?= htmlReady(json_encode($required_inputs)) ?>" + data-server_validation="<?= $server_validation ? 1 : 0?>" class="default studipform<?= $form->isCollapsable() ? ' collapsable' : '' ?>"> <?= CSRFProtection::tokenTag(['ref' => 'securityToken']) ?> @@ -52,7 +57,7 @@ $form_id = md5(uniqid()); <div v-if="STUDIPFORM_DISPLAYVALIDATION && (STUDIPFORM_VALIDATIONNOTES.length > 0)"> <?= _('Folgende Angaben müssen korrigiert werden, um das Formular abschicken zu können:') ?> <ul> - <li v-for="note in STUDIPFORM_VALIDATIONNOTES" :aria-describedby="note.describedby">{{ note.name + ": " + note.description }}</li> + <li v-for="note in ordererValidationNotes" :aria-describedby="note.describedby">{{ note.label.trim() + ": " + note.description }}</li> </ul> </div> </article> @@ -64,7 +69,8 @@ $form_id = md5(uniqid()); </div> <? if (!Request::isDialog()) : ?> <footer> - <?= \Studip\Button::create($form->getSaveButtonText(), $form->getSaveButtonName(), ['form' => $form_id]) ?> + <?= \Studip\Button::createAccept($form->getSaveButtonText(), $form->getSaveButtonName(), ['form' => $form_id]) ?> + <?= \Studip\LinkButton::createCancel($form->getCancelButtonText(), $form->getCancelButtonName()) ?> </footer> <? endif ?> </form> diff --git a/templates/forms/password_input.php b/templates/forms/password_input.php new file mode 100644 index 0000000000000000000000000000000000000000..e2e2583fb9f4f75749e5b33fca4fc2303b694a76 --- /dev/null +++ b/templates/forms/password_input.php @@ -0,0 +1,16 @@ +<div class="formpart"> + <label <?= $this->required ? 'class="studiprequired"' : '' ?> for="<?= $id ?>"> + <span class="textlabel"> + <?= htmlReady($this->title) ?> + </span> + <? if ($this->required) : ?> + <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span> + <? endif ?> + <input type="password" + v-model="<?= htmlReady($this->name) ?>" + name="<?= htmlReady($this->name) ?>" + value="<?= htmlReady($this->value) ?>" + id="<?= $id ?>" <?= $this->required ? 'required aria-required="true"' : '' ?> + <?= $attributes ?>> + </label> +</div> diff --git a/templates/forms/radio_input.php b/templates/forms/radio_input.php new file mode 100644 index 0000000000000000000000000000000000000000..37170c99e4960a7781ec0778b3fa140e542229d9 --- /dev/null +++ b/templates/forms/radio_input.php @@ -0,0 +1,16 @@ +<div class="formpart"> + <section <?= $this->orientation == 'horizontal' ? 'class="hgroup"' : '' ?> for="<?= $id ?>"> + <span class="textlabel"> + <?= htmlReady($this->title) ?> + </span> + + <? foreach ($options as $key => $option) : ?> + <label class="" <?= $attributes ?>> + <input type="radio" + v-model="<?= htmlReady($this->name) ?>" + value="<?= htmlReady($key) ?>" <?= $key == $value ? 'checked' : '' ?>> + <?= htmlReady($option) ?> + </label> + <? endforeach ?> +</section> +</div> diff --git a/templates/forms/range_input.php b/templates/forms/range_input.php index c85b02d3a398d3bd3f76e2a25a5f23d0a64eb1f5..07bd934926ec9f97ef5765b888da89e970397a3f 100644 --- a/templates/forms/range_input.php +++ b/templates/forms/range_input.php @@ -10,4 +10,7 @@ name="<?= htmlReady($name) ?>" value="<?= htmlReady($value) ?>" id="<?= $id ?>" - <?= $attributes ?>></range-input> + min="<?= $min ?>" + max="<?= $max ?>" + step="<?= $step ?>" + <?= $attributes ?>></range-input> diff --git a/templates/forms/text_input.php b/templates/forms/text_input.php index ae93758d14bff316d87ccf95510fbd147af43008..3104ee843269aaea1a0b81c59c68a1d8cd3cc779 100644 --- a/templates/forms/text_input.php +++ b/templates/forms/text_input.php @@ -6,11 +6,12 @@ <? if ($this->required) : ?> <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span> <? endif ?> + <input type="text" + v-model="<?= htmlReady($this->name) ?>" + name="<?= htmlReady($this->name) ?>" + value="<?= htmlReady($this->value) ?>" + id="<?= $id ?>" <?= ($this->required ? 'required aria-required="true"' : '') ?> + <?= $attributes ?>> </label> - <input type="text" - v-model="<?= htmlReady($this->name) ?>" - name="<?= htmlReady($this->name) ?>" - value="<?= htmlReady($this->value) ?>" - id="<?= $id ?>" <?= ($this->required ? 'required aria-required="true"' : '') ?> - <?= $attributes ?>> + </div> diff --git a/templates/register/form.php b/templates/register/form.php deleted file mode 100644 index 137cd05e69cd7b80b5c4a4fd1c9d32e965b436b1..0000000000000000000000000000000000000000 --- a/templates/register/form.php +++ /dev/null @@ -1,175 +0,0 @@ -<?php -use Studip\Button, Studip\LinkButton; - -$email_restriction = Config::get()->EMAIL_DOMAIN_RESTRICTION; -?> -<script type="text/javascript" language="javaScript"> -jQuery(document).ready(function() { - STUDIP.register.re_username = <?= $validator->username_regular_expression ?>; - STUDIP.register.re_name = <?= $validator->name_regular_expression ?>; - - $('form[name=login]').submit(function () { - return STUDIP.register.checkdata(); - }); -}); -</script> - -<? if (isset($username)): ?> - <?= MessageBox::error(_("Bei der Registrierung ist ein Fehler aufgetreten!"), [$error_msg, _("Bitte korrigieren Sie Ihre Eingaben und versuchen Sie es erneut")]) ?> -<? endif; ?> - -<h1><?= _('Stud.IP - Registrierung') ?></h1> - -<form name="login" action="<?= URLHelper::getLink() ?>" method="post" class="default"> - <?= CSRFProtection::tokenTag() ?> - <input type="hidden" name="login_ticket" value="<?= Seminar_Session::get_ticket() ?>"> - - <fieldset> - <legend><?= _('Herzlich willkommen!') ?></legend> - - <p><?= _('Bitte füllen Sie zur Anmeldung das Formular aus:') ?></p> - - <label for="username"> - <em class="required"><?= _('Benutzername') ?></em> - <input type="text" name="username" id="username" - onchange="STUDIP.register.checkusername()" - value="<?= htmlReady($username) ?>" - autofocus - required maxlength="63" - autocapitalize="off" autocorrect="off"> - </label> - - <label for="password"> - <em class="required"><?= _('Passwort') ?></em> - <input type="password" name="password" id="password" - onchange="STUDIP.register.checkpassword()" - required maxlength="31"> - </label> - - <label for="password2"> - <em class="required"><?= _('Passwortbestätigung') ?></em> - <input type="password" name="password2" id="password2" - onchange="STUDIP.register.checkpassword2()" - required maxlength="31"> - </label> - - <label for="title_front"> - <?= _('Titel') ?> - </label> - <section class="hgroup size-m"> - <select name="title_chooser_front" data-copy-to="#title_front" class="size-s"> - <? foreach ($GLOBALS['TITLE_FRONT_TEMPLATE'] as $template): ?> - <option <? if ($template === $title_front) echo 'selected'; ?>> - <?= htmlReady($template) ?> - </option> - <? endforeach; ?> - </select> - - <input type="text" name="title_front" id="title_front" - value="<?= htmlReady($title_front) ?>" - maxlength="63" class="no-hint"> - </section> - - <label for="title_rear"> - <?= _('Titel nachgestellt') ?> - </label> - <section class="hgroup size-m"> - <select name="title_chooser_rear" data-copy-to="#title_rear" class="size-s"> - <? foreach ($GLOBALS['TITLE_REAR_TEMPLATE'] as $template): ?> - <option <? if ($template === $title_rear) echo 'selected'; ?>> - <?= htmlReady($template) ?> - </option> - <? endforeach; ?> - </select> - - <input type="text" name="title_rear" id="title_rear" - value="<?= htmlReady($title_rear) ?>" - maxlength="63" class="no-hint"> - </section> - - <label for="first_name"> - <em class="required"><?= _('Vorname') ?></em> - - <input type="text" name="Vorname" id="first_name" - onchange="STUDIP.register.checkVorname()" - value="<?= htmlReady($Vorname) ?>" - required maxlength="63"> - </label> - - <label for="last_name"> - <em class="required"><?= _('Nachname') ?></em> - - <input type="text" name="Nachname" id="last_name" - onchange="STUDIP.register.checkNachname()" - value="<?= htmlReady($Nachname) ?>" - required maxlength="63"> - </label> - - <div> - <?= _('Geschlecht') ?> - </div> - - <section class="hgroup" id="gender"> - <label> - <input type="radio" name="geschlecht" value="0" - <? if (!$geschlecht) echo 'checked' ?>> - <?= _('unbekannt') ?> - </label> - - <label> - <input type="radio" name="geschlecht" value="1" - <? if ($geschlecht == 1) echo "checked" ?>> - <?= _('männlich') ?> - </label> - - <label> - <input type="radio" name="geschlecht" value="2" - <? if ($geschlecht == 2) echo "checked" ?>> - <?= _('weiblich') ?> - </label> - - <label> - <input type="radio" name="geschlecht" value="3" - <? if ($geschlecht == 3) echo "checked" ?>> - <?= _('divers') ?> - </label> - </section> - - - <label for="email"> - <em class="required"><?= _('E-Mail') ?></em> - <? if (!trim($email_restriction)): ?> - <input type="email" name="Email" id="email" - onchange="STUDIP.register.checkEmail()" - value="<?= htmlReady(trim($Email)) ?>" - required maxlength="63"> - <? endif; ?> - </label> - - <? if (trim($email_restriction)): ?> - <section class="hgroup size-m"> - <input type="text" name="Email" id="email" - onchange="STUDIP.register.checkEmail()" - value="<?= htmlReady(preg_replace('/@.*$/', '', trim($Email ?: ''))) ?>" - required maxlength="63" - class="no-hint"> - <select name="emaildomain"> - <? foreach (explode(',', $email_restriction) as $domain): ?> - <option value="<?= trim($domain) ?>" - <? if (trim($domain) == Request::get('emaildomain')) echo 'selected'; ?>> - @<?= trim($domain) ?> - </option> - <? endforeach; ?> - </select> - </section> - <? endif; ?> - </fieldset> - - <footer> - <?= Button::createAccept(_('Registrieren'))?> - <?= LinkButton::createCancel( - _('Registrierung abbrechen'), - URLHelper::getURL('index.php?cancel_login=1') - ) ?> - </footer> -</form> diff --git a/templates/register/step1.php b/templates/register/step1.php deleted file mode 100644 index b67116e113bdce4933a7a4f9513e8c633d7be557..0000000000000000000000000000000000000000 --- a/templates/register/step1.php +++ /dev/null @@ -1,6 +0,0 @@ -<?= $GLOBALS['template_factory']->render('terms.php') ?> - -<footer style="text-align: center"> - <?= Studip\LinkButton::createAccept(_('Ich erkenne die Nutzungsbedingungen an'), URLHelper::getLink('register2.php')) ?> - <?= Studip\LinkButton::createCancel(_('Registrierung abbrechen'), URLHelper::getLink('index.php')) ?> -</footer>