diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..9ab59b180fb670006ac910a2bc4524a1376dcea0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ + +# Ignore developer files when exporting +.gitattributes export-ignore +.gitignore export-ignore +.gitlab-ci.yml export-ignore +.phive export-ignore +captainhook.json export-ignore +phpcs.xml.dist export-ignore +phpstan-baseline.neon export-ignore +phpstan.dist.neon export-ignore +phpunit.dist.xml export-ignore +tests export-ignore + +# Explicitly set file type and line endings for PHP files, improves git diff output +*.php text eol=lf diff=php \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5d49cc71d98b16e2ed3cc08be94e85f83584b1e5..a708841abb3c83ea2ba84c9f3a1fb8966eea8e46 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,11 @@ tools/ phpstan.neon .phpunit.result.cache phpunit.xml + +tools/ + +phpstan.neon + +.phpunit.result.cache + +phpunit.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8d20acb6c365ad8036a7b36d47dabd876d08efa..b5a64b401e554341447c74d7cf93a89ac95a3fdb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,17 @@ include: - - project: 'quiqqer/stabilization/semantic-release' - file: '/ci-templates/.gitlab-ci.yml' + - component: dev.quiqqer.com/quiqqer/stabilization/ci-cd-components/quiqqer-package-bundle/quiqqer-package-bundle@main + +# Remove the entire phpunit-php8.1 block, to allow PHPUnit to run on PHP 8.1 in your pipeline +phpunit-php8.1: + rules: + - when: never + +# Remove the entire phpunit-php8.2 block, to allow PHPUnit to run on PHP 8.2 in your pipeline +phpunit-php8.2: + rules: + - when: never + +# Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline +phpunit-php8.3: + rules: + - when: never \ No newline at end of file diff --git a/.phive/phars.xml b/.phive/phars.xml index a1315a09b4adad780a9c5e52f74835c708c5c7d5..f67ea918e05290132fd03421244b4f0f7085ebdf 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,4 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <phive xmlns="https://phar.io/phive"> - <phar name="phpstan" version="^1.10.67" installed="1.10.67" location="./tools/phpstan" copy="false"/> + <phar name="phpstan" version="1.*" installed="1.12.13" location="./tools/phpstan" copy="false"/> + <phar name="phpunit" version="^10.5.20" installed="10.5.20" location="./tools/phpunit" copy="true"/> + <phar name="phpcs" version="^3.10.1" installed="3.10.1" location="./tools/phpcs" copy="true"/> + <phar name="phpcbf" version="^3.10.1" installed="3.10.1" location="./tools/phpcbf" copy="true"/> + <phar name="captainhook" version="^5.23.3" installed="5.23.3" location="./tools/captainhook" copy="true"/> </phive> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..4a69a59b440e5beec561eca1e341509bd5a18688 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing + +This package follows the [QUIQQER contribution guidelines](https://dev.quiqqer.com/quiqqer/stabilization/documentation/-/wikis/home). \ No newline at end of file diff --git a/ajax/frontend/auth/existsUnverifiedActivation.php b/ajax/frontend/auth/existsUnverifiedActivation.php index 9ed0c26c3cd859e4282dad36f456680c6c11a4a8..4aad06829642cd799d9b55ed861ae71a167a5c86 100644 --- a/ajax/frontend/auth/existsUnverifiedActivation.php +++ b/ajax/frontend/auth/existsUnverifiedActivation.php @@ -4,20 +4,22 @@ * @return string - User e-mail address */ -use QUI\FrontendUsers\ActivationVerification; -use QUI\Verification\Verifier; +use QUI\Verification\VerificationRepository; QUI::$Ajax->registerFunction( 'package_quiqqer_frontend-users_ajax_frontend_auth_existsUnverifiedActivation', function ($userId) { try { $User = QUI::getUsers()->get($userId); - Verifier::getVerificationByIdentifier($User->getUUID(), ActivationVerification::getType()); + $verificationRepository = new VerificationRepository(); + $verification = $verificationRepository->findByIdentifier( + 'activate-' . $User->getUUID() + ); } catch (Exception) { return false; } - return $User->getAttribute('email'); + return $verification ? $User->getAttribute('email') : false; }, ['userId'] ); diff --git a/ajax/frontend/auth/resendActivationMail.php b/ajax/frontend/auth/resendActivationMail.php index af8fbb4971ff780dcc8242a6cb07e4c8a7084234..c49e571a27133487e6dd07e0b812f7fd62686859 100644 --- a/ajax/frontend/auth/resendActivationMail.php +++ b/ajax/frontend/auth/resendActivationMail.php @@ -4,12 +4,11 @@ * This file contains package_quiqqer_frontend-users_ajax_frontend_auth_resendActivationMail */ -use QUI\FrontendUsers\ActivationVerification; use QUI\FrontendUsers\Handler; use QUI\FrontendUsers\RegistrarInterface; use QUI\FrontendUsers\Registrars\Email\Registrar as EmailRegistrar; use QUI\Utils\Security\Orthos; -use QUI\Verification\Verifier; +use QUI\Verification\VerificationRepository; /** * Resend an activation mail @@ -21,9 +20,16 @@ function ($email) { try { $User = QUI::getUsers()->getUserByMail(Orthos::clear($email)); - Verifier::getVerificationByIdentifier($User->getUUID(), ActivationVerification::getType()); - } catch (Exception $Exception) { + $verificationRepository = new VerificationRepository(); + $verification = $verificationRepository->findByIdentifier( + 'activate-' . $User->getUUID() + ); + // if the verification does not exist -> do not resend mail + if (empty($verification)) { + return false; + } + } catch (Exception $Exception) { QUI\System\Log::writeException($Exception); return false; } diff --git a/ajax/frontend/profile/address/getDelete.php b/ajax/frontend/profile/address/getDelete.php new file mode 100644 index 0000000000000000000000000000000000000000..c5d1e9ea352c87e2e2535d7a891afeb8c3b7fac0 --- /dev/null +++ b/ajax/frontend/profile/address/getDelete.php @@ -0,0 +1,22 @@ +<?php + +/** + * This file contains package_quiqqer_invoice_ajax_frontend_address_getDelete + */ + +/** + * + * @param int $addressId + * @return string + */ +QUI::$Ajax->registerFunction( + 'package_quiqqer_frontend-users_ajax_frontend_profile_address_getDelete', + function ($addressId) { + $_REQUEST['delete'] = $addressId; + $Address = new QUI\FrontendUsers\Controls\Address\Address(); + + return QUI\ControlUtils::parse($Address); + }, + ['addressId'], + ['Permission::checkUser'] +); diff --git a/ajax/frontend/profile/save.php b/ajax/frontend/profile/save.php index 010fab69bd8d4bb6058b4a0214e9d25577b4dfd0..2297123161e8387335037c92d9d2324d2f45850d 100644 --- a/ajax/frontend/profile/save.php +++ b/ajax/frontend/profile/save.php @@ -21,14 +21,18 @@ function ($category, $settings, $data) { // Check permission if (!Utils::hasPermissionToViewCategory($category, $settings)) { - throw new \QUI\FrontendUsers\Exception( + throw new \QUI\FrontendUsers\Exception([ 'quiqqer/frontend-users', 'exception.ajax.frontend.profile.save.no_category_permission' - ); + ]); } $Control = QUI\FrontendUsers\Utils::getProfileSettingControl($category, $settings); - $Control->setAttribute('User', QUI::getUserBySession()); + + if (method_exists($Control, 'setAttribute')) { + $Control->setAttribute('User', QUI::getUserBySession()); + } + $Control->onSave(); }, ['category', 'settings', 'data'], diff --git a/ajax/settings/getAuthenticators.php b/ajax/settings/getAuthenticators.php index 3ad5648a6cf4fa8abd581fbc3ada95bdab172c22..24d78c6d898ba2019db947d564e3309290e195a9 100644 --- a/ajax/settings/getAuthenticators.php +++ b/ajax/settings/getAuthenticators.php @@ -6,14 +6,11 @@ * @return array */ -use QUI\FrontendUsers\AbstractRegistrar; - QUI::$Ajax->registerFunction( 'package_quiqqer_frontend-users_ajax_settings_getAuthenticators', function () { $authenticators = []; - /** @var AbstractRegistrar $Registrar */ foreach (QUI\Users\Auth\Handler::getInstance()->getAvailableAuthenticators() as $class) { /** @var QUI\Users\AbstractAuthenticator $Authenticator */ $Authenticator = new $class(); diff --git a/bin/controls/settings/Registrars.Entry.html b/bin/controls/settings/Registrars.Entry.html index 3505e6294bd474b549297404e3f1443968c1d3a5..a51cac9431a4aec49191f4c8d647583d7057ee85 100644 --- a/bin/controls/settings/Registrars.Entry.html +++ b/bin/controls/settings/Registrars.Entry.html @@ -33,6 +33,7 @@ class="field-container-field quiqqer-frontendusers-settings-registrars-setting"> <option value="mail">{{activationModeOptionMail}}</option> <option value="auto">{{activationModeOptionAuto}}</option> + <option value="autoWithEmailConfirm">{{activationModeOptionAutoWithEmailConfirm}}</option> <option value="manual">{{activationModeOptionManual}}</option> </select> </label> diff --git a/bin/controls/settings/Registrars.js b/bin/controls/settings/Registrars.js index c665f951c969d7bfdd1034e7044d9c1f76e1a850..0192065a3438e21b87837d9c2fb8a48cb25a4782 100644 --- a/bin/controls/settings/Registrars.js +++ b/bin/controls/settings/Registrars.js @@ -19,7 +19,7 @@ define('package/quiqqer/frontend-users/bin/controls/settings/Registrars', [ 'css!package/quiqqer/frontend-users/bin/controls/settings/Registrars.css' ], function (QUI, QUIControl, QUILoader, QUIFormUtils, QUILocale, QUIAjax, - Mustache, entryTemplate) { + Mustache, entryTemplate) { "use strict"; var lg = 'quiqqer/frontend-users'; @@ -82,14 +82,15 @@ define('package/quiqqer/frontend-users/bin/controls/settings/Registrars', [ 'class' : 'quiqqer-frontendusers-settings-registrars-entry', 'data-registrar': Registrar.type, html : Mustache.render(entryTemplate, { - title : Registrar.title, - description : Registrar.description, - labelActivationMode : QUILocale.get(lg, lgPrefix + 'labelActivationMode'), - activationModeOptionMail : QUILocale.get(lg, lgPrefix + 'activationModeOptionMail'), - activationModeOptionAuto : QUILocale.get(lg, lgPrefix + 'activationModeOptionAuto'), - activationModeOptionManual: QUILocale.get(lg, lgPrefix + 'activationModeOptionManual'), - labelActive : QUILocale.get(lg, lgPrefix + 'labelActive'), - labelDisplayPosition : QUILocale.get(lg, lgPrefix + 'labelDisplayPosition') + title : Registrar.title, + description : Registrar.description, + labelActivationMode : QUILocale.get(lg, lgPrefix + 'labelActivationMode'), + activationModeOptionMail : QUILocale.get(lg, lgPrefix + 'activationModeOptionMail'), + activationModeOptionAuto : QUILocale.get(lg, lgPrefix + 'activationModeOptionAuto'), + activationModeOptionAutoWithEmailConfirm: QUILocale.get(lg, lgPrefix + 'activationModeOptionAutoWithEmailConfirm'), + activationModeOptionManual : QUILocale.get(lg, lgPrefix + 'activationModeOptionManual'), + labelActive : QUILocale.get(lg, lgPrefix + 'labelActive'), + labelDisplayPosition : QUILocale.get(lg, lgPrefix + 'labelDisplayPosition') }) }).inject(self.$Content); diff --git a/bin/frontend/controls/Registration.js b/bin/frontend/controls/Registration.js index 5969e3100f8fbde592f8d7ada2b3f18a4569a40f..07553c5f68b1cbe83f29c52ae6b4e51a04cf1a0f 100644 --- a/bin/frontend/controls/Registration.js +++ b/bin/frontend/controls/Registration.js @@ -110,6 +110,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/Registration', [ ); if (RedirectElm) { + if (RedirectElm.get('data-reload')) { + window.location.reload(); + return; + } + var url = RedirectElm.get('data-url'); var instant = RedirectElm.get('data-instant') === "1"; diff --git a/bin/frontend/controls/RegistrationSignUp.js b/bin/frontend/controls/RegistrationSignUp.js index 984c4eeb4bf662c691d4a9657c4197e35e474b44..d3e98ffa841937a4ee565c62afea0185e4b7732d 100644 --- a/bin/frontend/controls/RegistrationSignUp.js +++ b/bin/frontend/controls/RegistrationSignUp.js @@ -332,6 +332,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/RegistrationSignUp' sendRegistration: function (registrar, registration_id, formData) { this.showLoader(); + console.log(111); var self = this; return new Promise(function (resolve, reject) { @@ -395,8 +396,15 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/RegistrationSignUp' var Redirect = Section.getElement('.quiqqer-frontendUsers-redirect'); - if (Redirect && Redirect.get('data-instant') && self.getAttribute('reloadOnSuccess')) { - window.location = Redirect.get('data-url'); + if (Redirect && self.getAttribute('reloadOnSuccess')) { + if (Redirect.get('data-reload')) { + window.location.reload(); + return; + } + + if (Redirect.get('data-instant')){ + window.location = Redirect.get('data-url'); + } } if (Section.getElement('.content-message-error')) { @@ -442,8 +450,6 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/RegistrationSignUp' * @return {Promise} */ showTerms: function (registrar, isSocial) { - console.log('show terms', this.getAttribute('termsAccepted')); - if (this.getAttribute('termsAccepted')) { return Promise.resolve(); } @@ -638,8 +644,15 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/RegistrationSignUp' var Redirect = Section.getElement('.quiqqer-frontendUsers-redirect'); - if (Redirect && Redirect.get('data-instant')) { - window.location = Redirect.get('data-url'); + if (Redirect && self.getAttribute('reloadOnSuccess')) { + if (Redirect.get('data-reload')) { + window.location.reload(); + return; + } + + if (Redirect.get('data-instant')){ + window.location = Redirect.get('data-url'); + } } var ErrorBox = Section.getElement('.content-message-error'); @@ -1331,7 +1344,10 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/RegistrationSignUp' duration: 250, callback: function () { Captcha.setStyle('display', 'none'); - Password.setStyle('display', 'none'); + + if (Password) { + Password.setStyle('display', 'none'); + } Mail.setStyle('opacity', 0); Mail.setStyle('display', 'inline'); @@ -1378,8 +1394,8 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/RegistrationSignUp' childNodes.setStyle('display', 'none'); - return self.hideTerms().then(function () { - return self.showLoader(); + return self.showLoader().then(() => { + return self.hideTerms(); }).then(function () { var Form = self.getElm().getElement('form[name="quiqqer-fu-registrationSignUp-email"]'), FormData = QUIFormUtils.getFormData(Form); diff --git a/bin/frontend/controls/address/Manager.js b/bin/frontend/controls/address/Manager.js index d27bd72a6c401b6da6f40fce7fac210e71e72bc9..15477e4bf24906ffc7fa8cbe90f99a3bd297a16f 100644 --- a/bin/frontend/controls/address/Manager.js +++ b/bin/frontend/controls/address/Manager.js @@ -6,10 +6,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ 'qui/QUI', 'qui/controls/Control', 'qui/controls/loader/Loader', + 'qui/controls/windows/Confirm', 'Locale', 'Ajax' -], function(QUI, QUIControl, QUILoader, QUILocale, QUIAjax) { +], function(QUI, QUIControl, QUILoader, QUIConfirm, QUILocale, QUIAjax) { 'use strict'; const lg = 'quiqqer/frontend-users'; @@ -24,8 +25,6 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ '$editClick', '$deleteClick', '$addClick', - '$openContainer', - '$closeContainer', '$clickCreateSubmit', '$clickEditSave' ], @@ -56,14 +55,6 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ this.$imported = true; this.getElm().set('data-quiid', this.getId()); - const entries = this.getElm().getElements( - '.quiqqer-frontend-users-address-list-entry' - ); - - entries.setStyles({ - 'position': 'relative' - }); - this.getElm().getElements('[name="create"]').addEvent('click', this.$addClick); this.getElm().getElements('[name="delete"]').addEvent('click', this.$deleteClick); this.getElm().getElements('[name="edit"]').addEvent('click', this.$editClick); @@ -170,30 +161,51 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ const self = this; - // open delete dialog - this.$openContainer(this.getElm()).then(function(Container) { - return self.getCreateTemplate().then(function(result) { - const Content = Container.getElement( - '.quiqqer-frontend-users-address-container-content' - ); + self.getCreateTemplate().then(function(result) { + const Form = new Element('form', { + 'class': 'quiqqer-frontendUsers-controls-profile-control default-content', + html: result, + 'data-name': 'address-container' + }); - new Element('form', { - 'class': 'quiqqer-frontend-users-address-container-create', - html: result, - events: { - submit: function(event) { - event.stop(); - } - } - }).inject(Content); + QUI.parse(Form).then(function() { + self.$removeUnusedNodes(Form); - Content.getElement('header').inject( - Container.getElement('.quiqqer-frontend-users-address-container-header') - ); + new QUIConfirm({ + 'class' : 'qui-window-popup--frontendUsers-profile qui-window-popup--frontendUsers-profile-address-add', + maxHeight: 800, + maxWidth: 700, + autoclose: false, + backgroundClosable: false, - Content.getElement('[type="submit"]').addEvent('click', self.$clickCreateSubmit); + title: QUILocale.get(lg, 'dialog.frontend-users.title'), + icon: 'fa fa-address-card-o', - self.resize(); + ok_button: { + text: QUILocale.get(lg, 'dialog.frontend-users.create.address.btn') + }, + cancel_button: { + text: QUILocale.get(lg, 'dialog.frontend-users.btn.cancel') + }, + + events: { + onOpen: function(Popup) { + const Content = Popup.getContent(); + Content.innerHTML = ''; + Form.inject(Content); + }, + onSubmit: function(Popup) { + Popup.Loader.show(); + + self.$clickCreateSubmit(Popup).then(function() { + Popup.close(); + self.refresh(); + }).catch(() => { + Popup.Loader.hide(); + }); + } + } + }).open(); }); }); }, @@ -201,47 +213,41 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ /** * click event - address creation * - * @param {DOMEvent} event + * @param {QUIConfirm} Popup */ - $clickCreateSubmit: function(event) { - event.stop(); - - const Target = event.target, - Container = Target.getParent('.quiqqer-frontend-users-address-container'), - Form = Container.getElement('form'); - - this.Loader.show(); + $clickCreateSubmit: function(Popup) { + const Content = Popup.getContent(), + Form = Content.getElement('form'); - require(['qui/utils/Form'], (FormUtils) => { - const formData = FormUtils.getFormData(Form); - const requiredFields = Form.getElements('[required]'); - - for (let i = 0, len = requiredFields.length; i < len; i++) { - if ('reportValidity' in requiredFields[i]) { - requiredFields[i].reportValidity(); - - if ('checkValidity' in requiredFields[i]) { - if (requiredFields[i].checkValidity() === false) { - this.Loader.hide(); - return; + return new Promise(function(resolve, reject) { + require(['qui/utils/Form'], (FormUtils) => { + const formData = FormUtils.getFormData(Form); + const requiredFields = Form.getElements('[required]'); + + for (let i = 0, len = requiredFields.length; i < len; i++) { + if ('reportValidity' in requiredFields[i]) { + requiredFields[i].reportValidity(); + + if ('checkValidity' in requiredFields[i]) { + if (requiredFields[i].checkValidity() === false) { + reject(); + return; + } } } } - } - QUIAjax.post('package_quiqqer_frontend-users_ajax_frontend_profile_address_create', () => { - this.$closeContainer(Container); - this.refresh(); - }, { - 'package': 'quiqqer/frontend-users', - data: JSON.encode(formData), - onError: (err) => { - QUI.getMessageHandler().then(function(MH) { - MH.addError(err.getMessage()); - }); + QUIAjax.post('package_quiqqer_frontend-users_ajax_frontend_profile_address_create', resolve, { + 'package': 'quiqqer/frontend-users', + data: JSON.encode(formData), + onError: (err) => { + QUI.getMessageHandler().then(function(MH) { + MH.addError(err.getMessage()); + }); - this.Loader.hide(); - } + reject(); + } + }); }); }); }, @@ -271,72 +277,82 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ $deleteClick: function(event) { event.stop(); - const self = this; - let Target = event.event.target; + let Target = event.target; - if (!Target.hasClass('quiqqer-frontend-users-address-list-entry')) { - Target = Target.getParent('.quiqqer-frontend-users-address-list-entry'); + if (Target.nodeName !== 'BUTTON') { + Target = Target.getParent('button'); } - const Address = Target; + const addressId = Target.getParent('[data-name="address"]').querySelector('[name="address"]').value; + const self = this; - // open delete dialog - this.$openContainer(Target).then(function(Container) { - Container.addClass( - 'quiqqer-frontend-users-address-container-delete' - ); + self.getDeleteTemplate(addressId).then(function(result) { - const Content = Container.getElement('.quiqqer-frontend-users-address-container-content'); + const Form = new Element('form', { + 'class': 'quiqqer-frontendUsers-controls-profile-control default-content', + html: result, + 'data-name': 'address-container' + }); - new Element('div', { - 'class': 'quiqqer-frontend-users-address-container-delete-message', - html: QUILocale.get(lg, 'dialog.frontend-users.delete.address') - }).inject(Content); + QUI.parse(Form).then(function() { + self.$removeUnusedNodes(Form); - new Element('button', { - 'class': 'quiqqer-frontend-users-address-container-delete-button', - html: QUILocale.get('quiqqer/system', 'delete'), - events: { - click: function(event) { - let Target = event.target; + new QUIConfirm({ + 'class' : 'qui-window-popup--frontendUsers-profile qui-window-popup--frontendUsers-profile-address-delete', + maxHeight: 450, + maxWidth: 500, + autoclose: false, + backgroundClosable: false, - if (Target.nodeName !== 'BUTTON') { - Target = Target.getParent('button'); - } + title: QUILocale.get(lg, 'dialog.frontend-users.address.delete.title'), + icon: 'fa fa-address-card-o', - Target.disabled = true; - Target.setStyle('width', Target.getSize().x); - Target.set('html', '<span class="fa fa-spinner fa-spin"></span>'); - - self.Loader.show(); - - self.deleteAddress( - Target.getParent('.quiqqer-frontend-users-address-list-entry').getElement( - '[name="address"]').value - ).then(function() { - return self.$closeContainer(Container); - }).then(function() { - Address.setStyles({ - overflow: 'hidden', - height: Address.getSize().y - }); + ok_button: { + text: QUILocale.get(lg, 'dialog.frontend-users.address.delete.btn') + }, + cancel_button: { + text: QUILocale.get(lg, 'dialog.frontend-users.btn.cancel') + }, - moofx(Address).animate({ - height: 0, - opacity: 0 - }, { - duration: 250, - callback: function() { - self.refresh(); - } + events: { + onOpen: function(Popup) { + const Content = Popup.getContent(); + Content.innerHTML = ''; + Form.inject(Content); + + const ConfirmBtn = Popup.getElm().querySelector('[name="submit"]'); + + ConfirmBtn.classList.remove('btn-success'); + ConfirmBtn.classList.add('btn-danger'); + }, + onSubmit: function(Popup) { + Popup.Loader.show(); + + self.deleteAddress(addressId).then(function() { + Popup.close(); + self.refresh(); + }).catch(() => { + Popup.Loader.hide(); }); - }).catch(function() { - self.$closeContainer(Container); - self.Loader.hide(); - }); + } } - } - }).inject(Content); + }).open(); + }); + }); + }, + + /** + * Return the address create template + * + * @return {Promise} + */ + getDeleteTemplate: function(addressId) { + return new Promise(function(resolve, reject) { + QUIAjax.get('package_quiqqer_frontend-users_ajax_frontend_profile_address_getDelete', resolve, { + 'package': 'quiqqer/frontend-users', + onError: reject, + addressId: addressId + }); }); }, @@ -366,39 +382,60 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ $editClick: function(event) { event.stop(); - const self = this; let Target = event.target; if (Target.nodeName !== 'BUTTON') { Target = Target.getParent('button'); } - const addressId = Target.getParent('.quiqqer-frontend-users-address-list-entry').getElement( - '[name="address"]').value; + const addressId = Target.getParent('[data-name="address"]').querySelector('[name="address"]').value; + const self = this; - this.$openContainer(this.getElm()).then(function(Container) { - return self.getEditTemplate(addressId).then(function(result) { - const Content = Container.getElement( - '.quiqqer-frontend-users-address-container-content' - ); + self.getEditTemplate(addressId).then(function(result) { + const Form = new Element('form', { + 'class': 'quiqqer-frontendUsers-controls-profile-control default-content', + html: result, + 'data-name': 'address-container' + }); - new Element('form', { - 'class': 'quiqqer-frontend-users-address-container-edit', - html: result, - events: { - submit: function(event) { - event.stop(); - } - } - }).inject(Content); + QUI.parse(Form).then(function() { + self.$removeUnusedNodes(Form); - Content.getElement('header').inject( - Container.getElement('.quiqqer-frontend-users-address-container-header') - ); + new QUIConfirm({ + 'class' : 'qui-window-popup--frontendUsers-profile qui-window-popup--frontendUsers-profile-address-edit', + maxHeight: 800, + maxWidth: 700, + autoclose: false, + backgroundClosable: false, - Content.getElement('[name="editSave"]').addEvent('click', self.$clickEditSave); + title: QUILocale.get(lg, 'dialog.frontend-users.edit.title'), + icon: 'fa fa-address-card-o', - self.resize(); + ok_button: { + text: QUILocale.get(lg, 'dialog.frontend-users.edit.address.btn') + }, + cancel_button: { + text: QUILocale.get(lg, 'dialog.frontend-users.btn.cancel') + }, + + events: { + onOpen: function(Popup) { + const Content = Popup.getContent(); + Content.innerHTML = ''; + Form.inject(Content); + }, + onSubmit: function(Popup) { + Popup.Loader.show(); + + self.$clickEditSave(Popup).then(function() { + Popup.close(); + self.refresh(); + }).catch(() => { + Popup.Loader.hide(); + }); + } + } + }).open(); }); }); }, @@ -406,48 +443,34 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ /** * event : click -> save the address edit * - * @param {DOMEvent} event + * {QUIConfirm} Popup */ - $clickEditSave: function(event) { - event.stop(); + $clickEditSave: function(Popup) { + const self = this, + Content = Popup.getContent(), + Form = Content.getElement('form'); - const Target = event.target, - Container = Target.getParent('.quiqqer-frontend-users-address-container'), - Form = Container.getElement('form'); - - this.Loader.show(); + return new Promise(function(resolve, reject) { + require(['qui/utils/Form'], (FormUtils) => { + const formData = FormUtils.getFormData(Form); - require(['qui/utils/Form'], (FormUtils) => { - const formData = FormUtils.getFormData(Form); - const requiredFields = Form.getElements('[required]'); + if (self.$hasValidityIssues(Form)) { + reject(); + return; + } - for (let i = 0, len = requiredFields.length; i < len; i++) { - if ('reportValidity' in requiredFields[i]) { - requiredFields[i].reportValidity(); + QUIAjax.post('package_quiqqer_frontend-users_ajax_frontend_profile_address_edit', resolve, { + 'package': 'quiqqer/frontend-users', + data: JSON.encode(formData), + addressId: formData.addressId, + onError: (err) => { + QUI.getMessageHandler().then((MH) => { + MH.addError(err.getMessage()); + }); - if ('checkValidity' in requiredFields[i]) { - if (requiredFields[i].checkValidity() === false) { - this.Loader.hide(); - return; - } + reject(); } - } - } - - QUIAjax.post('package_quiqqer_frontend-users_ajax_frontend_profile_address_edit', () => { - this.$closeContainer(Container); - this.refresh(); - }, { - 'package': 'quiqqer/frontend-users', - data: JSON.encode(formData), - addressId: formData.addressId, - onError: (err) => { - QUI.getMessageHandler().then((MH) => { - MH.addError(err.getMessage()); - }); - - this.Loader.hide(); - } + }); }); }); }, @@ -469,74 +492,30 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/address/Manager', [ //endregion - /** - * Open a div container with effect - * - * @return {Promise} - */ - $openContainer: function(Parent) { - const self = this; - - const Container = new Element('div', { - 'class': 'quiqqer-frontend-users-address-container', - html: '<div class="quiqqer-frontend-users-address-container-header"></div>' + - '<div class="quiqqer-frontend-users-address-container-content"></div>', - tabIndex: -1 - }).inject(Parent); - - new Element('span', { - 'class': 'fa fa-close quiqqer-frontend-users-address-container-close', - events: { - click: function() { - self.$closeContainer(Container); - } - } - }).inject(Container, 'top'); - - return new Promise(function(resolve) { - moofx(Container).animate({ - left: 0, - opacity: 1 - }, { - duration: 250, - callback: function() { - // no scroll animation because after address edit is open - // there may be an animation depend on selected option in "businessType" select - self.getElm().scrollIntoView() - resolve(Container); - } - }); - }); + $removeUnusedNodes: function(Node) { + if (Node.querySelector('button')) { + Node.querySelector('button').destroy(); + } }, - /** - * Open a div container with effect - * - * @param {HTMLDivElement} Container - * @return {Promise} - */ - $closeContainer: function(Container) { - const self = this; + $hasValidityIssues: function(Form) { + const requiredFields = Form.getElements('[required]'); + let i = 0, + len = requiredFields.length; - return new Promise(function(resolve) { - moofx(Container).animate({ - left: -50, - opacity: 0 - }, { - duration: 250, - callback: function() { - Container.destroy(); - self.getElm().setStyle('height', null); + for (i; i < len; i++) { + if ('reportValidity' in requiredFields[i]) { + requiredFields[i].reportValidity(); - if (self.$Profile) { - self.$Profile.getElm().setStyle('overflow', null); + if ('checkValidity' in requiredFields[i]) { + if (requiredFields[i].checkValidity() === false) { + return true; } - - self.resize(); - resolve(); } - }); - }); + } + } + + return false; } }); }); diff --git a/bin/frontend/controls/login/Login.js b/bin/frontend/controls/login/Login.js index f5c85caa122a33bd95cb82c6d93ea372468d10f8..0b98ad3824aad38501219b312e255f4f4ee22b91 100644 --- a/bin/frontend/controls/login/Login.js +++ b/bin/frontend/controls/login/Login.js @@ -108,10 +108,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ this.Loader.show(); } - var Elm = this.getElm(); + const Elm = this.getElm(), + HeaderElm = Elm.querySelector('[data-name="header"]'); - if (this.getAttribute('header') === false) { - Elm.getElement('h2').destroy(); + if (this.getAttribute('header') === false && HeaderElm) { + HeaderElm.destroy(); } this.$parseQuiControls(); @@ -128,22 +129,24 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ } QUIAjax.get('package_quiqqer_frontend-users_ajax_frontend_login_getControl', function(result) { - var Ghost = new Element('div', { + const Ghost = new Element('div', { html: result }); - if (self.getAttribute('header') === false) { - Ghost.getElement('h2').destroy(); + const HeaderElm = Ghost.querySelector('[data-name="header"]'); + + if (self.getAttribute('header') === false && HeaderElm) { + HeaderElm.destroy(); } self.getElm().set( 'html', - Ghost.getElement('.quiqqer-fu-login').get('html') + Ghost.querySelector('.quiqqer-fu-login').get('html') ); self.Loader.inject(self.$Elm); - Ghost.getElements('style').inject(self.getElm()); + Ghost.querySelector('style').inject(self.getElm()); self.$parseQuiControls(); }, { @@ -161,7 +164,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ var self = this; QUI.parse(this.getElm()).then(function() { - var Login = self.getElm().getElement('.quiqqer-fu-login-container'); + var Login = self.getElm().querySelector('[data-name="login-container"]'); // already logged in if (!Login) { @@ -174,6 +177,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ Login.setStyle('opacity', 0); Login.setStyle('display', null); + Login.setStyle('positino', 'relative'); self.getElm().getElements('form[name="quiqqer-fu-login-email"]').addEvent('submit', function(event) { event.stop(); @@ -185,15 +189,17 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ var emailAddress = self.getAttribute('emailAddress'); if (emailAddress) { - self.getElm().getElement('form[name="quiqqer-fu-login-email"]').getElement('input[name="username"]').value = emailAddress; + self.getElm().querySelector('form[name="quiqqer-fu-login-email"]').querySelector( + 'input[name="username"]').value = emailAddress; - self.getElm().getElement('form[name="quiqqer-fu-login-email"]').getElement('input[name="password"]').focus(); + self.getElm().querySelector('form[name="quiqqer-fu-login-email"]').querySelector( + 'input[name="password"]').focus(); } - self.getElm().getElements('.quiqqer-fu-login-social-entry').addEvent('click', self.$auth); + self.getElm().getElements('[data-name="social-login-form"]').addEvent('click', self.$auth); self.getElm().getElements( - '.quiqqer-fu-login-forget-password-link a' + '[data-name="forgot-password-link"] a' ).addEvent('click', function(event) { event.stop(); self.openForgottenPassword(); @@ -213,9 +219,8 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ self.sendForgottenPassword(); }); - // submit events - var container = self.getElm().getElements('.quiqqer-fu-login-social-entry-control'); + var container = self.getElm().getElements('[data-name="social-login-controlContainer"]'); var i, len, Control, ControlDom; for (i = 0, len = container.length; i < len; i++) { @@ -225,7 +230,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ //Control.addEvent(''); } - self.getElm().getElements('form.quiqqer-fu-login-social-entry').addEvents({ + self.getElm().getElements('[data-name="social-login-form"]').addEvents({ submit: self.$authBySocial }); @@ -258,7 +263,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ } if (submitauth) { - var Form = self.getElm().getElement('form[data-authenticator-hash="' + + var Form = self.getElm().querySelector('form[data-authenticator-hash="' + self.getAttribute('submitauth') + '"]'); if (Form) { @@ -273,7 +278,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ */ authByEmail: function() { var self = this, - Form = this.getElm().getElement('form[name="quiqqer-fu-login-email"]'); + Form = this.getElm().querySelector('form[name="quiqqer-fu-login-email"]'); if (this.getAttribute('showLoader')) { this.Loader.show(); @@ -402,8 +407,8 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ * @param Form */ $showSocialLoader: function(Form) { - var Icon = Form.getElement('.quiqqer-fu-login-social-entry-icon'); - var Loader = Form.getElement('.quiqqer-fu-login-social-entry-loader'); + var Icon = Form.querySelector('[data-name="social-login-entry-icon"]'); + var Loader = Form.querySelector('[data-name="social-login-entry-loader"]'); Loader.setStyle('opacity', 0); Loader.setStyle('display', 'inline-block'); @@ -430,8 +435,8 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ * @param Form */ $hideSocialLoader: function(Form) { - var Icon = Form.getElement('.quiqqer-fu-login-social-entry-icon'); - var Loader = Form.getElement('.quiqqer-fu-login-social-entry-loader'); + var Icon = Form.querySelector('[data-name="social-login-entry-icon"]'); + var Loader = Form.querySelector('[data-name="social-login-entry-loader"]'); Icon.setStyle('opacity', 0); Icon.setStyle('display', 'inline-block'); @@ -462,11 +467,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ var Target = event.target; - if (!Target.hasClass('quiqqer-fu-login-social-entry')) { - Target = Target.getParent('.quiqqer-fu-login-social-entry'); + if (Target.getAttribute('data-name') !== 'social-login-form') { + Target = Target.getAttribute('[data-name="social-login-form"]'); } - var Container = Target.getElement('.quiqqer-fu-login-social-entry-control'); + var Container = Target.getElement('[data-name="social-login-controlContainer"]'); var ControlDom = Container.getFirst(); var Control = QUI.Controls.getById(ControlDom.get('data-quiid')); @@ -512,16 +517,36 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ * opens the password forgotten sheet */ openForgottenPassword: function() { - var Reset = this.getElm().getElement('.quiqqer-fu-login-forget-password-reset'); + var Reset = this.getElm().querySelector('[data-name="password-reset"]'); if (!Reset) { return; } + // set these styles to make the animation work correctly when using _basic files + Reset.style.position = 'absolute'; + Reset.style.left = 0; + Reset.style.top = 0; + Reset.style.width = '100%'; + Reset.style.zIndex = 1; + Reset.style.opacity = 0; + + const Login = this.getElm().querySelector('[data-name="login-container"]'); + Login.style.height = Login.offsetHeight + 'px'; + + const LoginInner = this.getElm().querySelector('[data-name="login-container-inner"]'); + + // set these styles to make the animation work correctly when using _basic files Reset.setStyle('opacity', 0); Reset.setStyle('left', -50); Reset.setStyle('display', 'block'); + if (LoginInner) { + moofx(LoginInner).animate({ + opacity: 0 + }); + } + moofx(Reset).animate({ left: 0, opacity: 1 @@ -530,6 +555,12 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ } }); + + if (LoginInner) { + moofx(Login).animate({ + height: Reset.offsetHeight + }); + } }, /** @@ -538,9 +569,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ * @param {Object} error */ $onUserLoginError: function(Control, error) { - var ActivationInfoBox = this.$Elm.getElement( - '.quiqqer-fu-login-activation-info' - ); + var ActivationInfoBox = this.$Elm.querySelector('[data-name="activation-info"]'); ActivationInfoBox.set('html', ''); @@ -574,14 +603,14 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ var email = this.getAttribute('emailAddress'); if (!email) { - var Form = this.getElm().getElement('form[name="quiqqer-fu-login-email"]'); + var Form = this.getElm().querySelector('form[name="quiqqer-fu-login-email"]'); if (!Form) { showResendError(); return; } - email = Form.getElement('input[name="username"]').value.trim(); + email = Form.querySelector('input[name="username"]').value.trim(); } new ResendActivationLinkBtn({ @@ -608,12 +637,23 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ * close the password reset */ closeForgottenPassword: function() { - var Reset = this.getElm().getElement('.quiqqer-fu-login-forget-password-reset'); + var Reset = this.getElm().querySelector('[data-name="password-reset"]'); if (!Reset) { return; } + const Login = this.getElm().querySelector('[data-name="login-container"]'); + Login.style.height = Login.offsetHeight + 'px'; + + const LoginInner = this.getElm().querySelector('[data-name="login-container-inner"]'); + + if (LoginInner) { + moofx(LoginInner).animate({ + opacity: 1 + }); + } + moofx(Reset).animate({ left: -50, opacity: 0 @@ -622,6 +662,16 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ Reset.setStyle('display', 'none'); } }); + + if (LoginInner) { + moofx(Login).animate({ + height: LoginInner.offsetHeight + }, { + callback: function() { + Login.style.height = null; + } + }); + } }, /** @@ -630,9 +680,10 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/login/Login', [ sendForgottenPassword: function() { var self = this, Elm = this.getElm(), - SubmitBtn = Elm.getElement('.quiqqer-fu-login-forget-password-reset [type="submit"]'), - EmailInput = Elm.getElement('.quiqqer-fu-login-forget-password-reset [name="email"]'), - Section = Elm.getElement('.quiqqer-fu-login-forget-password-reset section'); + PasswordReset = Elm.querySelector('[data-name="password-reset"]'), + SubmitBtn = PasswordReset.querySelector('[type="submit"]'), + EmailInput = PasswordReset.querySelector('[name="email"]'), + Section = PasswordReset.querySelector('[data-name="password-reset-inner"]'); if (EmailInput.value === '') { return Promise.resolve(); diff --git a/bin/frontend/controls/profile/ChangePassword.js b/bin/frontend/controls/profile/ChangePassword.js index f9116bec198baf11dffe7fcc24e707f8de52b9f8..9c66963175771d7c58047804d2cf42b8853de794 100644 --- a/bin/frontend/controls/profile/ChangePassword.js +++ b/bin/frontend/controls/profile/ChangePassword.js @@ -12,15 +12,15 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw 'Locale' -], function (QUIControl, QUIControlUtils, QUIFormUtils, QUILocale) { - "use strict"; +], function(QUIControl, QUIControlUtils, QUIFormUtils, QUILocale) { + 'use strict'; var lg = 'quiqqer/frontend-users'; return new Class({ Extends: QUIControl, - Type : 'package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassword', + Type: 'package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassword', Binds: [ '$onInject', @@ -30,12 +30,12 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw '$hideSuccess' ], - initialize: function (options) { + initialize: function(options) { this.parent(options); - this.$ErrorContainer = null; + this.$ErrorContainer = null; this.$SuccessContainer = null; - this.$ProfileControl = null; + this.$ProfileControl = null; this.addEvents({ onImport: this.$onImport @@ -45,9 +45,9 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw /** * event: on import */ - $onImport: function () { - var self = this; - var Elm = this.getElm(); + $onImport: function() { + var self = this; + var Elm = this.getElm(); var PasswordOldInput = Elm.getElement('input[name="passwordOld"]'); if (PasswordOldInput) { @@ -60,30 +60,30 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw return; } - this.$ErrorContainer = Elm.getElement('.quiqqer-frontendUsers-changepassword-error'); - this.$SuccessContainer = Elm.getElement('.quiqqer-frontendUsers-changepassword-success'); + this.$ErrorContainer = Elm.querySelector('[data-name="msg-error"]'); + this.$SuccessContainer = Elm.querySelector('[data-name="msg-success"]'); QUIControlUtils.getControlByElement( Elm.getParent('.quiqqer-frontendUsers-controls-profile') - ).then(function (ProfileControl) { + ).then(function(ProfileControl) { self.$ProfileControl = ProfileControl; self.$ProfileControl.addEvents({ - onSave : function () { + onSave: function() { QUIFormUtils.setDataToForm({ - 'passwordOld' : '', - 'passwordNew' : '', + 'passwordOld': '', + 'passwordNew': '', 'passwordNewConfirm': '' }, Form); }, - onSaveEnd : function () { + onSaveEnd: function() { self.$showSuccess(); }, - onSaveError: function (Control, error) { + onSaveError: function(Control, error) { self.$showError(error.getMessage()); } }); - }, function () { + }, function() { // nothing }); }, @@ -93,7 +93,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw * * @param {String} msg */ - $showError: function (msg) { + $showError: function(msg) { this.$hideSuccess(); this.$ErrorContainer.set('html', msg); @@ -105,7 +105,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw /** * Show error msg */ - $hideError: function () { + $hideError: function() { this.$ErrorContainer.setStyle('display', 'none'); this.$ProfileControl.resize(); }, @@ -113,7 +113,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw /** * Show success msg */ - $showSuccess: function () { + $showSuccess: function() { this.$hideError(); this.$SuccessContainer.set( @@ -128,7 +128,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassw /** * Hide success msg */ - $hideSuccess: function () { + $hideSuccess: function() { this.$SuccessContainer.setStyle('display', 'none'); this.$ProfileControl.resize(); } diff --git a/bin/frontend/controls/profile/DeleteAccount.js b/bin/frontend/controls/profile/DeleteAccount.js index 2cfbe4567e35e4d3415ac917027c8eab09efc56b..88bd693c54b636d80644c020dda4b11231fae8bf 100644 --- a/bin/frontend/controls/profile/DeleteAccount.js +++ b/bin/frontend/controls/profile/DeleteAccount.js @@ -11,15 +11,15 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccou 'Locale', 'Ajax' -], function (QUIControl, QUIConfirm, QUILocale, QUIAjax) { - "use strict"; +], function(QUIControl, QUIConfirm, QUILocale, QUIAjax) { + 'use strict'; var lg = 'quiqqer/frontend-users'; return new Class({ Extends: QUIControl, - Type : 'package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccount', + Type: 'package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccount', Binds: [ '$onImport', @@ -27,11 +27,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccou ], options: { - username : '', + username: '', deletestarted: 0 }, - initialize: function (options) { + initialize: function(options) { this.parent(options); this.addEvents({ @@ -42,24 +42,24 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccou /** * event: on import */ - $onImport: function () { + $onImport: function() { var Elm = this.getElm(); - var SubmitBtn = Elm.getElement('button.quiqqer-frontendUsers-saveButton'), + var SubmitBtn = Elm.querySelector('[type="submit"]'), confirmed = false; if (!SubmitBtn) { return; } - var self = this; + var self = this; var username = this.getAttribute('username'); if (!username) { username = ''; } - SubmitBtn.addEvent('click', function (event) { + SubmitBtn.addEvent('click', function(event) { if (confirmed || self.getAttribute('deletestarted')) { return; } @@ -68,50 +68,51 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccou new QUIConfirm({ maxHeight: 350, + maxWidth: 600, autoclose: true, information: QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.information', { username: username }), - title : QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.title'), - texticon : 'fa fa-trash', - text : QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.text'), - icon : 'fa fa-trash', + title: QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.title'), + texticon: 'fa fa-trash', + text: QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.text'), + icon: 'fa fa-trash', cancel_button: { - text : QUILocale.get('quiqqer/system', 'cancel'), + text: QUILocale.get('quiqqer/system', 'cancel'), textimage: 'fa fa-remove' }, ok_button: { - text : QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.btn'), + text: QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.btn'), textimage: 'fa fa-trash' }, events: { - onOpen : function (Popup) { + onOpen: function(Popup) { var SubmitBtn = Popup.getButton('submit'); SubmitBtn.disable(); Popup.Loader.show(); - self.$checkDeleteAccount().then(function () { + self.$checkDeleteAccount().then(function() { SubmitBtn.enable(); Popup.Loader.hide(); - }, function (Error) { + }, function(Error) { Popup.setAttribute( 'information', QUILocale.get(lg, 'controls.profile.DeleteAccount.confirm.information_error', { username: username, - error : Error.getMessage() + error: Error.getMessage() }) ); Popup.Loader.hide(); }); }, - onSubmit: function (Popup) { + onSubmit: function(Popup) { confirmed = true; Popup.close(); SubmitBtn.click(); @@ -126,11 +127,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccou * * @return {Promise} */ - $checkDeleteAccount: function () { - return new Promise(function (resolve, reject) { + $checkDeleteAccount: function() { + return new Promise(function(resolve, reject) { QUIAjax.get('package_quiqqer_frontend-users_ajax_frontend_profile_checkDeleteAccount', resolve, { 'package': 'quiqqer/frontend-users', - onError : reject, + onError: reject, showError: false }); }); diff --git a/bin/frontend/controls/profile/Profile.js b/bin/frontend/controls/profile/Profile.js index 3d7dce91110704f7231c008569c72c9f3c4f2da0..b27eb67f8d1601d62283a83610de7c2a48beabea 100644 --- a/bin/frontend/controls/profile/Profile.js +++ b/bin/frontend/controls/profile/Profile.js @@ -83,7 +83,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ resize: function () { var self = this, Elm = this.getElm(), - Animation = Elm.getElement('.quiqqer-frontendUsers-controls-profile-categoryContentAnimation'); + Animation = Elm.querySelector('[data-name="content-animated"]'); return new Promise(function (resolve) { self.$parseContent().then(() => { @@ -128,7 +128,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ this.$bindCategoriesEvents(); this.openSetting().then(function () { - var Form = Elm.getElement('.quiqqer-frontendUsers-controls-profile-categoryContent'); + var Form = Elm.querySelector('[data-name="form"]'); var category = Form.get('data-category'); var settings = Form.get('data-setting'); @@ -155,7 +155,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ this.$parseContent().then(function () { self.$addFormEvents(); - var Form = Elm.getElement('.quiqqer-frontendUsers-controls-profile-categoryContent'); + var Form = Elm.querySelector('[data-name="form"]'); if (!Form) { return; @@ -205,6 +205,10 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ var pathName = window.location.pathname, url = QUIQQER_SITE.url + '/' + this.$category + '/' + this.$settings; + if (url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1) { + pathName = window.location.href; + } + if (!this.$settings || !this.$category) { return; } @@ -255,9 +259,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ category = category || false; settings = settings || false; - var Animation = Elm.getElement( - '.quiqqer-frontendUsers-controls-profile-categoryContentAnimation' - ); + var Animation = Elm.querySelector('[data-name="content-animated"]'); var Animate = new Promise(function (resolve) { if (!Animation) { @@ -280,12 +282,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ return new Promise(function (resolve, reject) { QUIAjax.get('package_quiqqer_frontend-users_ajax_frontend_profile_getControl', function (result) { var height = self.$Elm.getSize().y; - var Form = self.$Elm.getElement( - '.quiqqer-frontendUsers-controls-profile-categoryContent' - ); + var Form = self.$Elm.querySelector('[data-name="form"]'); if (!result) { - result = '<div class="quiqqer-frontendUsers-controls-profile-categoryContentAnimation">' + + result = '<div class="quiqqer-frontendUsers-controls-profile-categoryContentAnimation" ' + + 'data-name="content-animated">' + QUILocale.get(lg, 'controls.profile.Profile.setting_error') + '</div>'; } @@ -296,7 +297,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ // build the form if (!Form) { - var Control = Ghost.getElement( + var Control = Ghost.querySelector( '[data-qui="package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile"]' ); @@ -305,26 +306,14 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ } self.$Elm.set('html', Control.get('html')); - - Animation = Elm.getElement( - '.quiqqer-frontendUsers-controls-profile-categoryContentAnimation' - ); - - // Form = self.$Elm.getElement( - // '.quiqqer-frontendUsers-controls-profile-categoryContent' - // ); - + Animation = Elm.querySelector('[data-name="content-animated"]'); self.$bindCategoriesEvents(); } var styles = Ghost.getElements('style'); var scripts = Ghost.getElements('script'); - var Content = Ghost.getElement( - '.quiqqer-frontendUsers-controls-profile-categoryContentAnimation' - ); - - //Form.setStyle('height', height); + var Content = Ghost.querySelector('[data-name="content-animated"]'); if (!Content) { return; @@ -378,56 +367,45 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ * init category events */ $bindCategoriesEvents: function () { - var i, len, Header; - var self = this; - var Elm = this.getElm(); - var categories = Elm.getElements('.quiqqer-fupc-category'); + let i, len, Header; + const self = this; + const Elm = this.getElm(); + const categories = Elm.querySelectorAll('[data-name="nav-category"]'); var toggle = function () { var Category = this; - if (!this.hasClass('quiqqer-fupc-category')) { - Category = this.getParent('.quiqqer-fupc-category'); + if (this.getAttribute('data-name') !== 'nav-category') { + Category = this.getParent('[data-name="nav-category"]'); } - var Opener = Category.getElement('.quiqqer-fupc-category-header-opener'); - - Opener.removeClass('fa-arrow-circle-o-right'); - Opener.removeClass('fa-arrow-circle-o-down'); - - if (Category.hasClass('quiqqer-fupc-category--open')) { - Category.removeClass('quiqqer-fupc-category--open'); - Opener.addClass('fa-arrow-circle-o-right'); + if (Category.getAttribute('data-open') === "1") { + Category.setAttribute('data-open', 0); return; } - Category.addClass('quiqqer-fupc-category--open'); - Opener.addClass('fa-arrow-circle-o-down'); + Category.setAttribute('data-open', 1); }; for (i = 0, len = categories.length; i < len; i++) { - Header = categories[i].getElement('.quiqqer-fupc-category-header'); - - new Element('span', { - 'class': 'fa fa-arrow-circle-o-down quiqqer-fupc-category-header-opener' - }).inject(Header); + Header = categories[i].querySelector('[data-name="header"]'); Header.addEvent('click', toggle); } // category settings click - var items = Elm.getElements('.quiqqer-fupc-category-items-item'); + const items = Elm.getElements('[data-name="nav-category-item"]'); items.addEvent('click', function (event) { event.stop(); - var Target = event.target; + let Target = event.target; - if (!Target.hasClass('quiqqer-fupc-category-items-item')) { - Target = Target.getParent('.quiqqer-fupc-category-items-item'); + if (Target.getAttribute('data-name') !== 'nav-category-item') { + Target = Target.getParent('[data-name="nav-category-item"]'); } - var category = Target.getParent('.quiqqer-fupc-category').get('data-category'), + const category = Target.getParent('[data-name="nav-category"]').get('data-category'), setting = Target.get('data-setting'); self.$setMenuItemActive(category, setting); @@ -437,10 +415,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ // mobile - var MobileCategories = Elm.getElement( - '.quiqqer-frontendUsers-controls-profile-categories-mobile select' - ); - + const MobileCategories = Elm.getElement('[name="profile-categories-mobile"]'); if (MobileCategories) { if (self.$category && self.$settings) { MobileCategories.value = self.$category + ':' + self.$settings; @@ -468,7 +443,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ * @return {Promise} */ save: function () { - var self = this; + const self = this; this.fireEvent('saveBegin', [this]); @@ -523,7 +498,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ }, /** - * Set an menu item active + * Set a menu item active * * @param {String} category * @param {String} settings @@ -537,10 +512,11 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile', [ return; } - this.$Elm.getElements('.quiqqer-fupc-category-items-item--active') - .removeClass('quiqqer-fupc-category-items-item--active'); + this.$Elm.querySelectorAll('[data-active]').forEach((ActiveItem) => { + ActiveItem.removeAttribute('data-active'); + }); - Item.addClass('quiqqer-fupc-category-items-item--active'); + Item.setAttribute('data-active', 1); } }); }); diff --git a/bin/frontend/controls/profile/UserAvatar.js b/bin/frontend/controls/profile/UserAvatar.js index 60ad4c4724a02fe5779c3c31dd312ab3312df53b..25b3641ff6bb6d30672f305083d619a159f35de4 100644 --- a/bin/frontend/controls/profile/UserAvatar.js +++ b/bin/frontend/controls/profile/UserAvatar.js @@ -62,7 +62,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserAvatar' ); if (Node) { - Node.getElement('.quiqqer-fupc-category-items-item--active').click(); + Node.getElement('[data-active="1"]').click(); } }); diff --git a/bin/frontend/controls/profile/UserData.css b/bin/frontend/controls/profile/UserData.css index 80f8cb352f8fa3ebe49fbb020f5a7e00ca137eef..33bb70e69a6a5ab80665c7a00807c7a5813b9ea7 100644 --- a/bin/frontend/controls/profile/UserData.css +++ b/bin/frontend/controls/profile/UserData.css @@ -1,9 +1,3 @@ -.quiqqer-frontendUsers-userdata-email__hidden { +[data-hidden] { display: none; -} - -.quiqqer-frontendUsers-userdata-email-new-hint { - clear: both; - width: 100%; - float: left; } \ No newline at end of file diff --git a/bin/frontend/controls/profile/UserData.js b/bin/frontend/controls/profile/UserData.js index c1a3b218228631ec6d93274081e47327571fde1c..c853829a8e5901a5375166b771903376667f053e 100644 --- a/bin/frontend/controls/profile/UserData.js +++ b/bin/frontend/controls/profile/UserData.js @@ -17,15 +17,15 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', 'css!package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData.css' -], function (QUI, QUIControl, QUIControlUtils, QUIFunctionUtils, QUILocale, Registration) { - "use strict"; +], function(QUI, QUIControl, QUIControlUtils, QUIFunctionUtils, QUILocale, Registration) { + 'use strict'; var lg = 'quiqqer/frontend-users'; return new Class({ Extends: QUIControl, - Type : 'package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', + Type: 'package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', Binds: [ '$onInject', @@ -33,7 +33,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', '$clearEmailErrorMsg' ], - initialize: function (options) { + initialize: function(options) { this.parent(options); this.$EmailErrorMsgElm = null; @@ -46,37 +46,41 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', /** * event: on import */ - $onImport: function () { - var self = this; - var Elm = this.getElm(); - var ChangeEmailElm = Elm.getElement('.quiqqer-frontendUsers-userdata-email-edit'); - var EmailNewElm = Elm.getElement('.quiqqer-frontendUsers-userdata-email-new'); - var EmailNewInput = Elm.getElement('input[name="emailNew"]'); - var ProfileNode = Elm.getParent('.quiqqer-frontendUsers-controls-profile'); + $onImport: function() { + const self = this; + const Elm = this.getElm(); + const ChangeEmailElm = Elm.querySelector('[data-name="email-edit"]'); + const EmailNewElm = Elm.querySelector('[data-name="email-new"]'); + const EmailNewInput = Elm.querySelector('input[name="emailNew"]'); + let ProfileNode = Elm.querySelector( + '[data-qui="package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile"]' + ); if (ProfileNode) { - QUIControlUtils.getControlByElement(ProfileNode).then(function (ProfileControl) { + QUIControlUtils.getControlByElement(ProfileNode).then(function(ProfileControl) { ProfileControl.addEvents({ - onSave : function () { - EmailNewElm.addClass('quiqqer-frontendUsers-userdata-email__hidden'); + onSave: function() { + EmailNewElm.addAttribute('data-hidden'); + // EmailNewElm.addClass('quiqqer-frontendUsers-userdata-email__hidden'); EmailNewInput.value = ''; }, - onSaveError: function () { + onSaveError: function() { EmailNewInput.value = ''; EmailNewInput.focus(); } }); - }, function () { + }, function() { // do nothing }); } - ChangeEmailElm.addEvent('click', function () { - EmailNewElm.removeClass('quiqqer-frontendUsers-userdata-email__hidden'); + ChangeEmailElm.addEvent('click', function() { + // EmailNewElm.removeClass('quiqqer-frontendUsers-userdata-email__hidden'); + EmailNewElm.removeAttribute('data-hidden'); EmailNewInput.focus(); // resize profile - var ProfileNode = self.getElm().getParent( + ProfileNode = self.getElm().getParent( '[data-qui="package/quiqqer/frontend-users/bin/frontend/controls/profile/Profile"]' ); @@ -84,22 +88,22 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', return; } - var Profile = QUI.Controls.getById(ProfileNode.get('data-quiid')); + const Profile = QUI.Controls.getById(ProfileNode.get('data-quiid')); if (Profile) { Profile.resize(); } }); - var CheckMail = function (event) { - var email = event.target.value.trim(); + const CheckMail = function(event) { + const email = event.target.value.trim(); Promise.all([ Registration.emailSyntaxValidation(email), Registration.emailValidation(email) - ]).then(function (result) { + ]).then(function(result) { var emailSyntaxValid = result[0]; - var emailValid = result[1]; + var emailValid = result[1]; if (emailSyntaxValid && emailValid) { self.$clearEmailErrorMsg(); @@ -131,7 +135,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', * * @param {String} msg */ - $showEmailErrorMsg: function (msg) { + $showEmailErrorMsg: function(msg) { if (!this.$EmailErrorMsgElm) { this.$EmailErrorMsgElm = new Element('div', { 'class': 'content-message-error' @@ -144,7 +148,7 @@ define('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData', /** * Hide error msg for e-mail change */ - $clearEmailErrorMsg: function () { + $clearEmailErrorMsg: function() { if (this.$EmailErrorMsgElm) { this.$EmailErrorMsgElm.destroy(); this.$EmailErrorMsgElm = null; diff --git a/captainhook.json b/captainhook.json new file mode 100644 index 0000000000000000000000000000000000000000..3702e1a358868bedd5ff4c7eae40bb1abb589267 --- /dev/null +++ b/captainhook.json @@ -0,0 +1,13 @@ +{ + "pre-commit": { + "enabled": true, + "actions": [ + { + "action": "\\CaptainHook\\App\\Hook\\PHP\\Action\\Linting" + }, + { + "action": "composer test" + } + ] + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index ec87d3b880d471a9fedfae04201e016d11d2371f..f758f19c49256e0eca1f34bc8e235f2ebe58c78c 100644 --- a/composer.json +++ b/composer.json @@ -1,44 +1,86 @@ { - "name": "quiqqer/frontend-users", - "type": "quiqqer-module", - "description": "The Frontend Users module extends QUIQQER with a profile extension and a registration for users.", - "license": "GPL-3.0+", - "authors": [ - { - "name": "Henning Leutz", - "email": "support@pcsg.de", - "homepage": "https://www.pcsg.de", - "role": "Developer" + "name": "quiqqer/frontend-users", + "type": "quiqqer-module", + "description": "The Frontend Users module extends QUIQQER with a profile extension and a registration for users.", + "license": "GPL-3.0+", + "authors": [ + { + "name": "Henning Leutz", + "email": "support@pcsg.de", + "homepage": "https://www.pcsg.de", + "role": "Developer" + }, + { + "name": "Patrick Müller", + "email": "support@pcsg.de", + "homepage": "https://www.pcsg.de", + "role": "Developer" + }, + { + "name": "Jan Wennrich", + "email": "support@pcsg.de", + "homepage": "https://www.pcsg.de", + "role": "Developer" + } + ], + "support": { + "email": "support@pcsg.de" }, - { - "name": "Patrick M\u00fcller", - "email": "support@pcsg.de", - "homepage": "https://www.pcsg.de", - "role": "Developer" + "require": { + "quiqqer/core": "^2", + "quiqqer/countries": "^2", + "quiqqer/verification": "^3", + "quiqqer/tooltips": "^2", + "quiqqer/data-layer": "^2" }, - { - "name": "Jan Wennrich", - "email": "support@pcsg.de", - "homepage": "https://www.pcsg.de", - "role": "Developer" - } - ], - "support": { - "email": "support@pcsg.de" - }, - "require": { - "quiqqer/core": "^2", - "quiqqer/countries": "^2", - "quiqqer/verification": "^2", - "quiqqer/tooltips": "^2", - "quiqqer/data-layer": "^2" - }, - "suggest": { - "quiqqer/rest": "User registration via REST API" - }, - "autoload": { - "psr-4": { - "QUI\\FrontendUsers\\": "src/QUI/FrontendUsers" + "suggest": { + "quiqqer/rest": "User registration via REST API" + }, + "autoload": { + "psr-4": { + "QUI\\FrontendUsers\\": "src/QUI/FrontendUsers" + } + }, + "scripts": { + "test": [ + "@dev:lint", + "@dev:phpunit" + ], + "dev:phpunit": "./tools/phpunit", + "dev:lint": [ + "@dev:lint:phpstan", + "@dev:lint:style" + ], + "dev:lint:phpstan": "./tools/phpstan", + "dev:lint:style": "./tools/phpcs", + "dev:lint:style:fix": "./tools/phpcbf", + "dev:init": [ + "@dev:init:check-requirements", + "@dev:init:tools", + "@dev:init:git-hooks" + ], + "dev:init:check-requirements": [ + "which composer > /dev/null || (echo 'Error: composer has to be globally installed'; exit 1)", + "which phive > /dev/null || (echo 'Error: PHIVE has to be globally installed'; exit 1)" + ], + "dev:init:tools": "phive install --temporary", + "dev:init:git-hooks": "./tools/captainhook install --only-enabled --force" + }, + "scripts-aliases": { + "test": [ + "dev:test" + ] + }, + "scripts-descriptions": { + "test": "Runs linting, static analysis, and unit tests.", + "dev:phpunit": "Run PHPUnit test suites", + "dev:lint": "Run PHPStan and code style check", + "dev:lint:phpstan": "Run PHPStan", + "dev:lint:style": "Run code style check (PHP_CodeSniffer)", + "dev:lint:style:fix": "Try to fix code style errors automatically", + "dev:init": "Initialize the developer tooling (tools and git hooks)", + "dev:init:check-requirements": "Check if the necessary requirements are met", + "dev:init:tools": "Install all developer tools (requires PHIVE)", + "dev:init:git-hooks": "Install all git hooks (may require tools to be installed)" } - } -} +} \ No newline at end of file diff --git a/locale.xml b/locale.xml index dbb2033b9db7b0ae907d17a830cbb0fffe67d0e5..7fe2690fde37151846673484a34aba51921c633d 100644 --- a/locale.xml +++ b/locale.xml @@ -460,6 +460,14 @@ <en> <![CDATA[Automatic redirect to the specified page after the user has been successfully activated. The automatic forwarding takes place after <b>10 seconds</b>. <b>Note:</b> Does not apply if users are required to be activated <b>manually</b> after a registration.]]></en> </locale> + <locale name="settings.reloadOnSuccess.title"> + <de><![CDATA[Seite neu laden nach Registrierung]]></de> + <en><![CDATA[Reload page after registration]]></en> + </locale> + <locale name="settings.reloadOnSuccess.description"> + <de><![CDATA[Nach einer erfolgreichen Registrierung soll die Seite neu geladen werden. Diese Einstellung überschreibt die Einstellung "Automatische Weiterleitung nach Aktivierung".]]></de> + <en><![CDATA[After successful registration the page should be reloaded. This setting overwrites the setting "Auto redirect on activation".]]></en> + </locale> <locale name="settings.redirectOnLogin.title"> <de><![CDATA[Automatische Weiterleitung nach Login]]></de> <en><![CDATA[Auto redirect on log in]]></en> @@ -822,11 +830,24 @@ <en><![CDATA[Gravatar]]></en> </locale> - <locale name="dialog.frontend-users.delete.address"> + <locale name="dialog.frontend-users.btn.cancel"> + <de><![CDATA[Abbrechen]]></de> + <en><![CDATA[Cancel]]></en> + </locale> + + <locale name="dialog.frontend-users.address.delete.title"> + <de><![CDATA[Adresse löschen]]></de> + <en><![CDATA[Delete address]]></en> + </locale> + <locale name="dialog.frontend-users.address.delete.message"> <de><![CDATA[Möchten Sie die Adresse wirklichen löschen?]]></de> <en><![CDATA[Do you really want to delete the address?]]></en> </locale> - <locale name="dialog.frontend-users.title"> + <locale name="dialog.frontend-users.address.delete.btn"> + <de><![CDATA[Adresse löschen]]></de> + <en><![CDATA[Delete address]]></en> + </locale> + <locale name="dialog.frontend-users.edit.title"> <de><![CDATA[Neue Adresse anlegen]]></de> <en><![CDATA[Create new address]]></en> </locale> @@ -1642,6 +1663,32 @@ The e-mail address for your user account on [host] has been changed to the addre </p>]]></en> </locale> + <!-- E-Mail-Address confirm mail --> + <locale name="mail.confirm_email_address.subject"> + <de><![CDATA[Bestätigung Ihrer E-Mail-Adresse]]></de> + <en><![CDATA[Confirm your email address]]></en> + </locale> + <locale name="mail.confirm_email_address.body" html="true"> + <de><![CDATA[<h1>Hallo [username]!</h1> +<p> +Bitte bestätigen Sie Ihre E-Mail-Adresse für Ihr Benutzerkonto auf [host]. Für die Bestätigung öffnen Sie bitte folgenden Link: +<br><br> +<a href="[confirmLink]">[confirmLink]</a> +</p> +<p> +<b>Hinweis:</b> Sollten Sie diese E-Mail nicht veranlasst haben, können Sie sie einfach ignorieren. +</p>]]></de> + <en><![CDATA[<h1>Hello [username]!</h1> +<p> +Please confirm your e-mail address for your user account on [host]. To confirm your e-mail address, please click on the following link: +<br> +<a href="[confirmLink]">[confirmLink]</a> +</p> +<p> +<b>Note:</b> If you have not initiated this email, please ignore this it. +</p>]]></en> + </locale> + <!-- User delete confirm mail --> <locale name="mail.delete_user_confirm.subject"> <de><![CDATA[Löschung Ihres Benutzerkontos]]></de> @@ -1771,36 +1818,36 @@ The deletion of your user account on [host] was requested on [date]. Please conf <locale name="control.sign.up.terms_of_use_and_privacy_policy.label" html="true"> <de><![CDATA[ Durch das Erstellen eines Benutzerkontos versichere ich die - <a href="[termsOfUseUrl]">[termsOfUseSiteTitle]</a> und - <a href="[privacyPolicyUrl]">[privacyPolicySiteTitle]</a> + <a href="[termsOfUseUrl]" data-id="[termsOfUseSiteId]">[termsOfUseSiteTitle]</a> und + <a href="[privacyPolicyUrl]" data-id="[privacyPolicySiteId]">[privacyPolicySiteTitle]</a> gelesen zu haben und erkläre mich mit diesen einverstanden. ]]></de> <en><![CDATA[ By creating a user account, I confirm that I have read and agree to the - <a href="[termsOfUseUrl]">[termsOfUseSiteTitle]</a> and - <a href="[privacyPolicyUrl]">[privacyPolicySiteTitle]</a>. + <a href="[termsOfUseUrl]" data-id="[termsOfUseSiteId]">[termsOfUseSiteTitle]</a> and + <a href="[privacyPolicyUrl]" data-id="[privacyPolicySiteId]">[privacyPolicySiteTitle]</a>. ]]></en> </locale> <locale name="control.sign.up.terms_of_use.label" html="true"> <de><![CDATA[ Durch das Erstellen eines Benutzerkontos versichere ich die - <a href="[termsOfUseUrl]">[termsOfUseSiteTitle]</a> gelesen zu haben + <a href="[termsOfUseUrl]" data-id="[termsOfUseSiteId]">[termsOfUseSiteTitle]</a> gelesen zu haben und erkläre mich mit diesen einverstanden. ]]></de> <en><![CDATA[ By creating a user account, I confirm that I have read and agree to the - <a href="[termsOfUseUrl]">[termsOfUseSiteTitle]</a>. + <a href="[termsOfUseUrl]" data-id="[termsOfUseSiteId]">[termsOfUseSiteTitle]</a>. ]]></en> </locale> <locale name="control.sign.up.privacy_policy.label" html="true"> <de><![CDATA[ Durch das Erstellen eines Benutzerkontos versichere ich die - <a href="[privacyPolicyUrl]">[privacyPolicySiteTitle]</a> + <a href="[privacyPolicyUrl]" data-id="[privacyPolicySiteId]">[privacyPolicySiteTitle]</a> gelesen zu haben und erkläre mich mit diesen einverstanden. ]]></de> <en><![CDATA[ By creating a user account, I confirm that I have read and agree to the - <a href="[privacyPolicyUrl]">[privacyPolicySiteTitle]</a>. + <a href="[privacyPolicyUrl]" data-id="[privacyPolicySiteId]">[privacyPolicySiteTitle]</a>. ]]></en> </locale> <locale name="control.sign.up.type.login.in.button"> @@ -1975,6 +2022,58 @@ The deletion of your user account on [host] was requested on [date]. Please conf ]]></en> </locale> + <locale name="mail.text.confirmEmail.title"> + <de><![CDATA[Frontend Users: Bestätigung der E-Mail-Adresse]]></de> + <en><![CDATA[Frontend Users: Confirmation of e-mail address]]></en> + </locale> + <locale name="mail.text.confirmEmail.description"> + <de><![CDATA[ + E-Mail zur Bestägigung einer E-Mail-Adresse. + ]]></de> + <en><![CDATA[ + Email for confirmation of an email address. + ]]></en> + </locale> + <locale name="order.confirmEmail.subject.description"> + <de><![CDATA[ + <p>Betreff der Bestätigungs--E-Mail. Verfügbare Variablen:</p> + <ul> + <li><b>[host]:</b> Host des Projektes (System)</li> + </ul> + ]]></de> + <en><![CDATA[ + <p>Subject of the confirmation e-mail. Available variables:</p> + <ul> + <li><b>[host]:</b> Host of the project (system)</li> + </ul> + ]]></en> + </locale> + <locale name="order.confirmEmail.body.description"> + <de><![CDATA[ + <p>Inhalt der Bestätigungs-E-Mail. Verfügbare Variablen:</p> + <ul> + <li><b>[host]:</b> Host des Projektes (System)</li> + <li><b>[userId]:</b> Benutzer ID</li> + <li><b>[email]:</b> E-Mail des Benutzers</li> + <li><b>[confirmLink]:</b> Bestätigungs-Link (muss enthalten sein!)</li> + <li><b>[username]:</b> Benutzername</li> + <li><b>[userFirstName]:</b> Vorname des Benutzers (falls vorhanden)</li> + <li><b>[userLastName]:</b> Nachname des Benutzers (falls vorhanden)</li> + </ul> + ]]></de> + <en><![CDATA[ + <p>Content of the confirmation e-mail. Available variables:</p> + <ul> + <li><b>[host]:</b> Host of the project (system)</li> + <li><b>[userId]:</b> User id of the user</li> + <li><b>[email]:</b> E mail of the user</li> + <li><b>[confirmLink]:</b> Confirmation link (must be included!)</li> + <li><b>[username]:</b> Username</li> + <li><b>[userFirstName]:</b> First name of the user (if available)</li> + <li><b>[userLastName]:</b> Last name of the user (if available)</li> + </ul> + ]]></en> + </locale> <locale name="mail.text.changeEmail.title"> <de><![CDATA[Frontend Users: E-Mail ändern]]></de> @@ -2135,6 +2234,10 @@ The deletion of your user account on [host] was requested on [date]. Please conf <de><![CDATA[Automatische Aktivierung]]></de> <en><![CDATA[Auto activation]]></en> </locale> + <locale name="controls.settings.registrars.template.activationModeOptionAutoWithEmailConfirm"> + <de><![CDATA[Automatische Aktivierung (Link zur Bestätigung der E-Mail-Adresse wird trotzdem versandt)]]></de> + <en><![CDATA[Auto activation (link to confirm email address will be sent regardless)]]></en> + </locale> <locale name="controls.settings.registrars.template.activationModeOptionManual"> <de><![CDATA[Manuelle Aktivierung (über Benutzer-Verwaltung)]]></de> <en><![CDATA[Manual activation (via User Management)]]></en> diff --git a/phpstan.dist.neon b/phpstan.dist.neon index aa10a371ae2d7b11f3021e6489cdb3fb8cc1edd7..ef886a95df9495ee50e39ea453be2b1086293062 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -2,11 +2,16 @@ includes: - phpstan-baseline.neon parameters: - level: 1 + level: 5 paths: - src - ajax - types + excludePaths: + # Ignore files that use classes from optional packages + - src/QUI/FrontendUsers/ErpProvider.php + - src/QUI/FrontendUsers/Rest/Provider.php + - src/QUI/FrontendUsers/GdprDataProvider.php bootstrapFiles: - tests/phpstan-bootstrap.php treatPhpDocTypesAsCertain: false diff --git a/phpunit.dist.xml b/phpunit.dist.xml new file mode 100644 index 0000000000000000000000000000000000000000..f6c7becf0c12757beb871a9333e2d81e02aa7cae --- /dev/null +++ b/phpunit.dist.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit bootstrap="tests/phpunit-bootstrap.php"> + <testsuites> + <testsuite name="Tests"> + <directory>tests/</directory> + </testsuite> + </testsuites> +</phpunit> diff --git a/settings.xml b/settings.xml index 5ccfa7c5a639c1b4933a1f7715f225dc12030f37..9b2941aecadda3f28758b013d3d101309ab6ee49 100644 --- a/settings.xml +++ b/settings.xml @@ -120,6 +120,10 @@ <conf name="autoRedirectOnSuccess"> <type><![CDATA[string]]></type> </conf> + <conf name="reloadOnSuccess"> + <type><![CDATA[bool]]></type> + <defaultvalue>0</defaultvalue> + </conf> <conf name="addressFields"> <type><![CDATA[string]]></type> </conf> @@ -582,6 +586,16 @@ </description> </input> + <input conf="registration.reloadOnSuccess" type="checkbox"> + <text> + <locale group="quiqqer/frontend-users" var="settings.reloadOnSuccess.title"/> + </text> + <description> + <locale group="quiqqer/frontend-users" + var="settings.reloadOnSuccess.description"/> + </description> + </input> + <input conf="registration.useCaptcha" type="checkbox"> <text> <locale group="quiqqer/frontend-users" var="settings.useCaptcha.title"/> diff --git a/src/QUI/FrontendUsers/AbstractFrontendUsersLinkVerificationHandler.php b/src/QUI/FrontendUsers/AbstractFrontendUsersLinkVerificationHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..bcd91ea09c496de2645f4f6599e409b8aba891fb --- /dev/null +++ b/src/QUI/FrontendUsers/AbstractFrontendUsersLinkVerificationHandler.php @@ -0,0 +1,33 @@ +<?php + +namespace QUI\FrontendUsers; + +use QUI; +use QUI\Projects\Project; +use QUI\Verification\AbstractLinkVerificationHandler; +use QUI\Verification\Entity\LinkVerification; + +/** + * Base class for all link verification handlers of quiqqer/frontend-users. + */ +abstract class AbstractFrontendUsersLinkVerificationHandler extends AbstractLinkVerificationHandler +{ + /** + * Get the Project this ActivationVerification is intended for + * + * @param LinkVerification $verification + * @return Project|null + * @throws QUI\Exception + */ + protected function getProject(LinkVerification $verification): ?Project + { + $project = $verification->getCustomDataEntry('project'); + $projectLang = $verification->getCustomDataEntry('projectLang'); + + if (empty($project) || empty($projectLang)) { + return null; + } + + return QUI::getProjectManager()->getProject($project, $projectLang); + } +} diff --git a/src/QUI/FrontendUsers/AbstractRegistrar.php b/src/QUI/FrontendUsers/AbstractRegistrar.php index 4739df041943db81bcea7d3942784688ebff5765..98dcf6f53722e8c6a8bcfcff38d716a08315a226 100644 --- a/src/QUI/FrontendUsers/AbstractRegistrar.php +++ b/src/QUI/FrontendUsers/AbstractRegistrar.php @@ -36,7 +36,7 @@ abstract public function validate(): array; abstract public function getInvalidFields(): array; /** - * @return mixed + * @return string */ abstract public function getUsername(): string; @@ -56,7 +56,7 @@ abstract public function onRegistered(QUI\Interfaces\Users\User $User): void; * @param QUI\Locale|null $Locale (optional) - If omitted use QUI::getLocale() * @return string */ - abstract public function getTitle(QUI\Locale $Locale = null): string; + abstract public function getTitle(null | QUI\Locale $Locale = null): string; /** * Get description @@ -64,7 +64,7 @@ abstract public function getTitle(QUI\Locale $Locale = null): string; * @param QUI\Locale|null $Locale (optional) - If omitted use QUI::getLocale() * @return string */ - abstract public function getDescription(QUI\Locale $Locale = null): string; + abstract public function getDescription(null | QUI\Locale $Locale = null): string; /** * Return an icon for the registrar diff --git a/src/QUI/FrontendUsers/ActivationVerification.php b/src/QUI/FrontendUsers/ActivationLinkVerification.php similarity index 53% rename from src/QUI/FrontendUsers/ActivationVerification.php rename to src/QUI/FrontendUsers/ActivationLinkVerification.php index c613d0a99f653f4d455bf96d8b99082caa82395e..e382a979eedff31d4a75f4c8c264532616ca181a 100644 --- a/src/QUI/FrontendUsers/ActivationVerification.php +++ b/src/QUI/FrontendUsers/ActivationLinkVerification.php @@ -4,7 +4,11 @@ use QUI; use QUI\Exception; -use QUI\Verification\AbstractVerification; +use QUI\ExceptionStack; +use QUI\Projects\Project; +use QUI\Verification\Entity\LinkVerification; +use QUI\Verification\Enum\VerificationErrorReason; +use QUI\Verification\Entity\AbstractVerification; /** * Class ActivationVerification @@ -13,16 +17,17 @@ * * @package QUI\FrontendUsers */ -class ActivationVerification extends AbstractVerification +class ActivationLinkVerification extends AbstractFrontendUsersLinkVerificationHandler { /** * Get the duration of a Verification (minutes) * - * @return int|false - duration in minutes; + * @param AbstractVerification $verification + * @return int|null - duration in minutes; * if this method returns false use the module setting default value * @throws Exception */ - public function getValidDuration(): bool|int + public function getValidDuration(AbstractVerification $verification): ?int { $settings = Handler::getInstance()->getMailSettings(); return (int)$settings['verificationValidityDuration']; @@ -31,17 +36,18 @@ public function getValidDuration(): bool|int /** * Execute this method on successful verification * + * @param LinkVerification $verification * @return void + * @throws \Exception */ - public function onSuccess(): void + public function onSuccess(LinkVerification $verification): void { - $userId = $this->getIdentifier(); - try { - $User = QUI::getUsers()->get($userId); - $User->activate(false, QUI::getUsers()->getSystemUser()); + $userUuid = $verification->getCustomDataEntry('uuid'); + $User = QUI::getUsers()->get($userUuid); + $User->activate('', QUI::getUsers()->getSystemUser()); - Utils::setUserEmailVerified($User); + Utils::setDefaultUserEmailVerified($User); QUI::getEvents()->fireEvent( 'quiqqerFrontendUsersUserActivate', @@ -50,21 +56,20 @@ public function onSuccess(): void Handler::getInstance()->getRegistrar($User->getAttribute(Handler::USER_ATTR_REGISTRAR)) ] ); - } catch (QUI\Users\Exception $Exception) { - QUI\System\Log::addWarning( - 'quiqqer/frontend-users -> ActivationVerification :: ' . $Exception->getMessage() - ); } catch (\Exception $Exception) { QUI\System\Log::writeException($Exception); + throw $Exception; } } /** * Execute this method on unsuccessful verification * + * @param LinkVerification $verification + * @param VerificationErrorReason $reason * @return void */ - public function onError(): void + public function onError(LinkVerification $verification, VerificationErrorReason $reason): void { // nothing } @@ -72,10 +77,11 @@ public function onError(): void /** * This message is displayed to the user on successful verification * + * @param LinkVerification $verification * @return string * @throws Exception */ - public function getSuccessMessage(): string + public function getSuccessMessage(LinkVerification $verification): string { $registrationSetting = Handler::getInstance()->getRegistrationSettings(); @@ -91,10 +97,11 @@ public function getSuccessMessage(): string /** * This message is displayed to the user on unsuccessful verification * - * @param string $reason - The reason for the error (see \QUI\Verification\Verifier::REASON_) + * @param LinkVerification $verification + * @param VerificationErrorReason $reason * @return string */ - public function getErrorMessage(string $reason): string + public function getErrorMessage(LinkVerification $verification, VerificationErrorReason $reason): string { return ''; } @@ -102,23 +109,26 @@ public function getErrorMessage(string $reason): string /** * Automatically redirect the user to this URL on successful verification * - * @return string|false - If this method returns false, no redirection takes place + * @param LinkVerification $verification + * @return string|null - If this method returns false, no redirection takes place * @throws Exception */ - public function getOnSuccessRedirectUrl(): bool|string + public function getOnSuccessRedirectUrl(LinkVerification $verification): ?string { + $project = $this->getProject($verification); + + if (!$project) { + return null; + } + $RegistrarHandler = Handler::getInstance(); - $RegistrationSite = $RegistrarHandler->getRegistrationSignUpSite( - $this->getProject() - ); + $RegistrationSite = $RegistrarHandler->getRegistrationSignUpSite($project); if (empty($RegistrationSite)) { - $RegistrationSite = $RegistrarHandler->getRegistrationSite( - $this->getProject() - ); + $RegistrationSite = $RegistrarHandler->getRegistrationSite($project); if (empty($RegistrationSite)) { - return false; + return null; } } @@ -126,30 +136,35 @@ public function getOnSuccessRedirectUrl(): bool|string 'success' ], [ 'success' => 'activation', - 'registrar' => $this->getRegistrarHash() + 'registrar' => $this->getRegistrarHash($verification) ]); } /** * Automatically redirect the user to this URL on unsuccessful verification * - * @return string|false - If this method returns false, no redirection takes place + * @param LinkVerification $verification + * @param VerificationErrorReason $reason + * @return string|null - If this method returns false, no redirection takes place * @throws Exception + * @throws ExceptionStack */ - public function getOnErrorRedirectUrl(): bool|string + public function getOnErrorRedirectUrl(LinkVerification $verification, VerificationErrorReason $reason): ?string { $RegistrarHandler = Handler::getInstance(); - $RegistrationSite = $RegistrarHandler->getRegistrationSignUpSite( - $this->getProject() - ); + $project = $this->getProject($verification); + + if (!$project) { + return null; + } + + $RegistrationSite = $RegistrarHandler->getRegistrationSignUpSite($project); if (empty($RegistrationSite)) { - $RegistrationSite = $RegistrarHandler->getRegistrationSite( - $this->getProject() - ); + $RegistrationSite = $RegistrarHandler->getRegistrationSite($project); if (empty($RegistrationSite)) { - return false; + return null; } } @@ -157,35 +172,24 @@ public function getOnErrorRedirectUrl(): bool|string 'error' ], [ 'error' => 'activation', - 'registrar' => $this->getRegistrarHash() + 'registrar' => $this->getRegistrarHash($verification) ]); } - /** - * Get the Project this ActivationVerification is intended for - * - * @return QUI\Projects\Project - * @throws Exception - */ - protected function getProject(): QUI\Projects\Project - { - $additionalData = $this->getAdditionalData(); - return QUI::getProjectManager()->getProject($additionalData['project'], $additionalData['projectLang']); - } - /** * Get hash of registrar used for this Verification * + * @param LinkVerification $verification * @return string */ - protected function getRegistrarHash(): string + protected function getRegistrarHash(LinkVerification $verification): string { - $data = $this->getAdditionalData(); + $registrar = $verification->getCustomDataEntry('registrar'); - if (empty($data['registrar'])) { + if (empty($registrar)) { return ''; } - return $data['registrar']; + return $registrar; } } diff --git a/src/QUI/FrontendUsers/Console/AnonymiseUsers.php b/src/QUI/FrontendUsers/Console/AnonymiseUsers.php index 223c6f08ce8eda3107dfd8abf16ae3e3d8506071..7a680cef405877fd5f13155c7a50e3ee3fd5d001 100644 --- a/src/QUI/FrontendUsers/Console/AnonymiseUsers.php +++ b/src/QUI/FrontendUsers/Console/AnonymiseUsers.php @@ -59,13 +59,13 @@ public function execute(): void // EMAIL $this->writeLn( - "Use the following host handle for email-addresses [@foo.bar]: " + "Use the following host handle for email-addresses [@foobar.local]: " ); $emailHandle = $this->readInput(); if (empty($emailHandle)) { - $emailHandle = '@foo.bar'; + $emailHandle = '@foobar.local'; } // SUMMARY diff --git a/src/QUI/FrontendUsers/Console/SendUserMails.php b/src/QUI/FrontendUsers/Console/SendUserMails.php index 7fa403b912ba4a9022f16902496a7e2f0cd5c52c..f0027b3a4949ed5a7f5c1c543b7fde3cdeb2e10e 100644 --- a/src/QUI/FrontendUsers/Console/SendUserMails.php +++ b/src/QUI/FrontendUsers/Console/SendUserMails.php @@ -398,7 +398,7 @@ protected function setLimits(array $limits): void * * @return array|false - Limit config or false if limits not yet configured */ - protected function getLimits(): bool|array + protected function getLimits(): bool | array { try { $limitsFile = QUI::getPackage('quiqqer/frontend-users')->getVarDir() . 'send_user_mails_limits'; @@ -585,7 +585,7 @@ protected function isMailAllowed(): bool * @param string|null $testMailAddress (optional) - If set, a single test mail will be sent to this address * @return void */ - protected function sendMails(string $testMailAddress = null): void + protected function sendMails(null | string $testMailAddress = null): void { $Users = QUI::getUsers(); $SystemUser = $Users->getSystemUser(); @@ -630,7 +630,7 @@ protected function sendMails(string $testMailAddress = null): void ); sleep(60); - } while (!$mailAllowed); + } while (!$mailAllowed); // @phpstan-ignore-line } if (!empty($recipient['firstname']) && !empty($recipient['lastname'])) { diff --git a/src/QUI/FrontendUsers/Controls/Address/Address.Create.html b/src/QUI/FrontendUsers/Controls/Address/Address.Create.html index b267ff9b8069ce3670198dce4f681303c47dc3fd..42059bd8c9b6593843bd84833c57ab999fb10d7a 100644 --- a/src/QUI/FrontendUsers/Controls/Address/Address.Create.html +++ b/src/QUI/FrontendUsers/Controls/Address/Address.Create.html @@ -1,7 +1,5 @@ <section class="quiqqer-frontend-users-address-create"> - <header> - <h1>{locale group="quiqqer/frontend-users" var="dialog.frontend-users.title"}</h1> - </header> + <h2>{locale group="quiqqer/frontend-users" var="dialog.frontend-users.title"}</h2> {template_event name="quiqqer::frontend-users::user-address-create-begin" User=$User} @@ -55,12 +53,14 @@ {locale group="quiqqer/frontend-users" var="street_no"} {if $settings.street_no.required}*{/if} </span> - <input type="text" name="street" value="" autocomplete="shipping street-address" {if $settings.street_no.required}required{/if} - placeholder="{locale group='quiqqer/frontend-users' var='street'}" - /> - <input type="text" name="street_number" value="" - placeholder="{locale group='quiqqer/frontend-users' var='house_no'}" - /> + <div class="address-street--2-col"> + <input type="text" name="street" value="" autocomplete="shipping street-address" {if $settings.street_no.required}required{/if} + placeholder="{locale group='quiqqer/frontend-users' var='street'}" + /> + <input type="text" name="street_number" value="" + placeholder="{locale group='quiqqer/frontend-users' var='house_no'}" + /> + </div> </label> {/if} @@ -95,7 +95,7 @@ {/if} {if $settings.country.show} - <label class="quiqqer-frontendUsers-userdata-create-address-country"> + <label class="quiqqer-frontendUsers-userdata-address-country"> <span> {locale group="quiqqer/frontend-users" var="country"} {if $settings.country.required}*{/if} @@ -142,7 +142,7 @@ {template_event name="quiqqer::frontend-users::user-address-create-end" User=$User} - <button type="submit" name="createSave" value="1" class="quiqqer-frontend-users-address-edit-button"> + <button type="submit" name="createSave" value="1" class="btn btn-primary quiqqer-frontend-users-address-edit-button"> {locale group="quiqqer/frontend-users" var="dialog.frontend-users.create.address.btn"} </button> diff --git a/src/QUI/FrontendUsers/Controls/Address/Address.Delete.html b/src/QUI/FrontendUsers/Controls/Address/Address.Delete.html index 7b249d5a6b3adce8d3d14a718993f25072af86d1..5d6515f5d615515993e0da91c12e047653642679 100644 --- a/src/QUI/FrontendUsers/Controls/Address/Address.Delete.html +++ b/src/QUI/FrontendUsers/Controls/Address/Address.Delete.html @@ -1,17 +1,15 @@ <section class="quiqqer-frontend-users-address-delete"> - <header> - <h1>{locale group="quiqqer/frontend-users" var="ordering.step.title.Address.delete"}</h1> - </header> + <h2>{locale group="quiqqer/frontend-users" var="dialog.frontend-users.address.delete.title"}</h2> <div class="quiqqer-frontend-users-address-delete-container"> - {locale group="quiqqer/order" var="ordering.step.title.Address.delete.message"} + {locale group="quiqqer/frontend-users" var="dialog.frontend-users.address.delete.message"} {$Address->render()} <button type="submit" name="step" value="{$this->getName()}" class="quiqqer-frontend-users-address-delete-container-deleteBtn" > - {locale group="quiqqer/order" var="ordering.step.title.Address.delete.btn"} + {locale group="quiqqer/frontend-users" var="dialog.frontend-users.address.delete.btn"} </button> <input type="hidden" name="addressId" value="{$Address->getUUID()}"/> diff --git a/src/QUI/FrontendUsers/Controls/Address/Address.Edit.html b/src/QUI/FrontendUsers/Controls/Address/Address.Edit.html index b0515acadcd3d28d669f256f37caa32bc49bcd27..defe48efabd22619367de1832027b033e90863f8 100644 --- a/src/QUI/FrontendUsers/Controls/Address/Address.Edit.html +++ b/src/QUI/FrontendUsers/Controls/Address/Address.Edit.html @@ -1,7 +1,5 @@ <section class="quiqqer-frontend-users-address-edit"> - <header> - <h1>{locale group="quiqqer/frontend-users" var="dialog.frontend-users.edit.title"}</h1> - </header> + <h2>{locale group="quiqqer/frontend-users" var="dialog.frontend-users.edit.title"}</h2> {template_event name="quiqqer::frontend-users::user-address-edit-begin" User=$User Address=$Address} @@ -104,7 +102,7 @@ {/if} {if $settings.country.show} - <label> + <label class="quiqqer-frontendUsers-userdata-address-country"> <span> {locale group="quiqqer/system" var="country"} {if $settings.country.required}*{/if} diff --git a/src/QUI/FrontendUsers/Controls/Address/Address.css b/src/QUI/FrontendUsers/Controls/Address/Address.css index 5b07c91e67249b41fee74eaca76f1b8c93814d5c..bc1768911d3f194e789f75cb7272dcfa11c6bdce 100644 --- a/src/QUI/FrontendUsers/Controls/Address/Address.css +++ b/src/QUI/FrontendUsers/Controls/Address/Address.css @@ -1,19 +1,24 @@ +/* popup */ +.qui-window-popup--frontendUsers-profile { + --_qui-frontend-users-profile__popup__bg-color: var(--qui-frontend-users-profile__popup__bg-color, #f5f5f5); + --_qui-frontend-users-profile__popup__radius: var(--qui-frontend-users-profile__popup__radius, 0.5rem); + --_qui-frontend-users-profile__popup__conten-maxWidth: var(--qui-frontend-users-profile__popup__conten-maxWidth, 30rem); +} + +/***********/ +/* General */ +/***********/ .quiqqer-frontendUsers-manager, .quiqqer-frontend-users-address { - float: left; position: relative; width: 100%; } .quiqqer-frontend-users-address-description { - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - float: left; - padding-bottom: 10px; - width: 100%; + margin-bottom: 1.5rem; } .quiqqer-frontend-users-address-description-text { - float: left; width: 100%; } @@ -23,7 +28,7 @@ @media screen and (min-width: 768px) { .quiqqer-frontend-users-address-description-button { - float: right; + margin-left: auto; } } @@ -33,97 +38,26 @@ } } -/** container - ================================================== */ - -.quiqqer-frontend-users-address-container { - background: #fff; - border: 1px solid #dedede; - height: 100%; - left: -50px; - outline: none; - opacity: 0; - position: absolute; - top: 0; - width: 100%; -} - -.quiqqer-frontend-users-address-container-close { - cursor: pointer; - font-size: 20px; - line-height: 40px; - position: absolute; - right: 0; - text-align: center; - top: 0; - width: 40px; -} - -.quiqqer-frontend-users-address-container-header { - float: left; - padding: 20px 40px 0 20px; - width: 100%; -} - -.quiqqer-frontend-users-address-container-header header { - font-size: 22px; - padding: 0; -} - -.quiqqer-frontend-users-address-container-header header h1 { - font-size: 22px; -} - -.quiqqer-frontend-users-address-container-content { - float: left; - height: calc(100% - 40px); - margin-top: 40px; - overflow: auto; - width: 100%; -} - /** Address listing ================================================== */ -address { - font-style: normal; -} - -address .adr:not(:last-child) { - margin-bottom: 0.75rem; -} - -address .fa { - width: 1.5rem; -} - .quiqqer-frontend-users-address-list { - display: flex; - flex-wrap: wrap; - float: left; - padding-bottom: 20px; - width: 100%; + display: grid; + gap: 1rem; } .quiqqer-frontend-users-address-list-entry { + --_padding: 0.5rem 1rem; + --_radius: var(--radius, var(--_qui-frontend-users-profile__radius)); + --_border: 1px solid #ddd; + --_bg: var(--bg, var(--_qui-frontend-users-profile__bg-color)); + + min-width: 0; + border-radius: var(--_radius); display: flex; flex-direction: column; - margin-top: 10px; overflow: hidden; - width: calc(50% - 5px); -} - -@media screen and (hover: hover) { - .quiqqer-frontend-users-address-list-entry:hover { - background: rgba(0, 0, 0, 0.1); - } -} - -.quiqqer-frontend-users-address-list-entry:nth-child(odd) { - margin-right: 5px; -} - -.quiqqer-frontend-users-address-list-entry:nth-child(even) { - margin-left: 5px; + font-size: 0.875rem; + border: var(--_border); } .quiqqer-frontend-users-address-list-entry [type="radio"] { @@ -131,152 +65,69 @@ address .fa { margin: 7px; } -.quiqqer-frontend-users-address-list-entry header { - background: rgba(0, 0, 0, 0.1); - padding: 10px; - width: 100%; +.quiqqer-frontend-users-address-list-entry :where(header) { + padding: var(--_padding); + border-bottom: var(--_border); + font-weight: bold; + background: var(--_bg); } -.quiqqer-frontend-users-address-list-entry header label { +.quiqqer-frontend-users-address-list-entry :where(header label) { margin: 0; padding: 0; } .quiqqer-frontend-users-address-list-entry address { flex-grow: 1; - margin-bottom: 10px; - padding: 10px; -} - -.quiqqer-frontend-users-address-list-entry-buttons { - padding: 0 10px 10px 0; - text-align: right; -} - -/** address edit && create - ================================================== */ - -.quiqqer-frontend-users-address-create, -.quiqqer-frontend-users-address-edit { - max-width: 600px; - width: 100%; -} - -.quiqqer-frontend-users-address-create header, -.quiqqer-frontend-users-address-edit header { - margin-bottom: 20px; - padding: 0; -} - -.quiqqer-frontend-users-address-create label, -.quiqqer-frontend-users-address-edit label { - clear: both; - display: inline-block; - width: 100%; -} - -@media screen and (min-width: 768px) { - .quiqqer-frontend-users-address-create label span, - .quiqqer-frontend-users-address-edit label span { - float: left; - width: 50% - } - - .quiqqer-frontend-users-address-create input, - .quiqqer-frontend-users-address-create select, - .quiqqer-frontend-users-address-create .qui-select, - .quiqqer-frontend-users-address-edit input, - .quiqqer-frontend-users-address-edit select, - .quiqqer-frontend-users-address-edit .qui-select { - float: left; - width: 50% - } -} - -@media screen and (max-width: 767px) { - .quiqqer-frontend-users-address-edit select { - width: 100%; - } - - .quiqqer-frontend-users-address-create select, - .quiqqer-frontend-users-address-create .qui-select, - .quiqqer-frontend-users-address-edit .qui-select { - float: none; - width: 100%; - } + padding: var(--_padding); + font-style: normal; + font-size: 0.875rem; + line-height: 1.5; } -.quiqqer-frontend-users-address-create [name="street"], -.quiqqer-frontend-users-address-edit [name="street"] { - margin-bottom: 0.5rem; +.quiqqer-frontend-users-address-list-entry address .adr:not(:last-child) { + margin-bottom: 0.5em; } -@media screen and (min-width: 768px) { - .quiqqer-frontend-users-address-create [name="street"], - .quiqqer-frontend-users-address-edit [name="street"] { - width: 30%; - } - - .quiqqer-frontend-users-address-create [name="street_number"], - .quiqqer-frontend-users-address-edit [name="street_number"] { - margin-left: 10px; - width: calc(20% - 10px); - } - - .quiqqer-frontend-users-address-create [name="street"], - .quiqqer-frontend-users-address-edit [name="street"], - .quiqqer-frontend-users-address-create [name="street_number"], - .quiqqer-frontend-users-address-edit [name="street_number"] { - float: left; - } - - .quiqqer-frontend-users-address-edit-create, - .quiqqer-frontend-users-address-edit-button { - margin-left: 50%; - width: 50%; - } +.quiqqer-frontend-users-address-list-entry address .fa { + width: 1.5rem; } -@media screen and (max-width: 767px) { - .quiqqer-frontend-users-address-edit-create, - .quiqqer-frontend-users-address-edit-button { - width: 100%; - } +.quiqqer-frontend-users-address-list-entry-buttons { + text-align: right; + border-top: var(--_border); + padding: 0.5rem 1rem; + display: flex; + justify-content: flex-end; + gap: 0.5rem; + flex-wrap: wrap; } -.quiqqer-frontend-users-address-container-edit, -.quiqqer-frontend-users-address-container-create { - float: left; - padding: 0 20px 20px; - width: 100%; +/*****************/ +/* Address popup */ +/*****************/ +.qui-window-popup--frontendUsers-profile .quiqqer-frontend-users-address-create, +.qui-window-popup--frontendUsers-profile .quiqqer-frontend-users-address-edit { + max-width: var(--_qui-frontend-users-profile__popup__conten-maxWidth); + margin-inline: auto; } - -/** Rechnungsadresse löschen - ================================================== */ - -.quiqqer-frontend-users-address-delete { +/************************/ +/* Address delete popup */ +/************************/ +.qui-window-popup--frontendUsers-profile-address-delete .quiqqer-frontend-users-address-delete { margin: 0 auto; max-width: 600px; width: 100%; + display: flex; + flex-direction: column; + align-items: center; } -.quiqqer-frontend-users-address-delete-container-deleteBtn { - padding: 10px !important; - font-size: 14px !important; -} - -.quiqqer-frontend-users-address-delete-container address { - margin: 20px auto; -} - -/** Rechnungsadresse löschen - js - ================================================== */ - -.quiqqer-frontend-users-address-container-delete { - text-align: center; +.quiqqer-frontend-users-address-delete-container :where(address) { + margin-top: 2rem; + background: var(--_qui-frontend-users-profile__popup__bg-color); + border-radius: var(--_qui-frontend-users-profile__popup__radius); + padding: 1rem; + font-style: normal; } -.quiqqer-frontend-users-address-container-delete-message { - margin-bottom: 20px; - padding: 0 10px; -} diff --git a/src/QUI/FrontendUsers/Controls/Address/Address.html b/src/QUI/FrontendUsers/Controls/Address/Address.html index 88d5b61060e4b50a2d4f13f817d1e6e21c9dae4a..ce6e80261eeba7608c38c71f9d3e259410f06d97 100644 --- a/src/QUI/FrontendUsers/Controls/Address/Address.html +++ b/src/QUI/FrontendUsers/Controls/Address/Address.html @@ -1,16 +1,14 @@ {template_event name="quiqqer::frontend-users::user-address-top" User=$User} <section class="quiqqer-frontend-users-address quiqqer-frontendUsers-userdata-section"> - <header> - <h2>{locale group="quiqqer/frontend-users" var="profile.address.category.title"}</h2> - </header> + <h2>{locale group="quiqqer/frontend-users" var="profile.address.category.title"}</h2> <div class="quiqqer-frontend-users-address-description"> <div class="quiqqer-frontend-users-address-description-text"> {locale group="quiqqer/frontend-users" var="control.address.title.Address.description"} </div> - <button name="create" class="quiqqer-frontend-users-address-description-button"> + <button name="create" class="btn btn-secondary quiqqer-frontend-users-address-description-button"> <span class="fa fa-plus"></span> <span>{locale group="quiqqer/frontend-users" var="control.address.title.Address.addButton"}</span> </button> @@ -22,41 +20,39 @@ {if $User->getId()} {* Logged in user *} - {foreach $addresses as $Address} - <div class="quiqqer-frontend-users-address-list-entry"> - <header> - <label> - <span class="fa fa-address-card-o"></span> - {if $Address->getUUID() == $User->getAttribute('address')} - {locale group="quiqqer/frontend-users" var="control.address.header.defaultAddress.title"} - {else} - {locale group="quiqqer/frontend-users" var="control.address.header.title"} - {/if} - <input type="hidden" - name="address" - value="{$Address->getUUID()}" - > - </label> - </header> - - {$Address->render()} - - <div class="quiqqer-frontend-users-address-list-entry-buttons"> - <button type="submit" name="edit" value="{$Address->getUUID()}"> - <span class="fa fa-edit"></span> - <span> - {locale group="quiqqer/frontend-users" var="control.address.title.Address.editButton"} - </span> - </button> - <button type="submit" name="delete" value="{$Address->getUUID()}" class="btn btn-danger"> - <span class="fa fa-trash"></span> - <span> - {locale group="quiqqer/frontend-users" var="control.address.title.Address.deleteButton"} - </span> - </button> - </div> - </div> - {/foreach} + {foreach $addresses as $Address} + <div class="quiqqer-frontend-users-address-list-entry" data-name="address"> + <header> + <span class="fa fa-address-card-o"></span> + {if $Address->getUUID() == $User->getAttribute('address')} + {locale group="quiqqer/frontend-users" var="control.address.header.defaultAddress.title"} + {else} + {locale group="quiqqer/frontend-users" var="control.address.header.title"} + {/if} + <input type="hidden" + name="address" + value="{$Address->getUUID()}" + > + </header> + + {$Address->render()} + + <div class="quiqqer-frontend-users-address-list-entry-buttons"> + <button type="submit" name="edit" value="{$Address->getUUID()}"> + <span class="fa fa-edit"></span> + <span> + {locale group="quiqqer/frontend-users" var="control.address.title.Address.editButton"} + </span> + </button> + <button type="submit" name="delete" value="{$Address->getUUID()}" class="btn btn-danger"> + <span class="fa fa-trash"></span> + <span> + {locale group="quiqqer/frontend-users" var="control.address.title.Address.deleteButton"} + </span> + </button> + </div> + </div> + {/foreach} {/if} </div> diff --git a/src/QUI/FrontendUsers/Controls/Address/Address.php b/src/QUI/FrontendUsers/Controls/Address/Address.php index cf7b2f3f15e4384e313b1af7191e36add12cf2d2..0919d56c2cf85b63621e2be487cd99f8b8b68703 100644 --- a/src/QUI/FrontendUsers/Controls/Address/Address.php +++ b/src/QUI/FrontendUsers/Controls/Address/Address.php @@ -38,7 +38,7 @@ public function __construct(array $attributes = []) * @param null|QUI\Locale $Locale * @return string */ - public function getName(QUI\Locale $Locale = null): string + public function getName(null | QUI\Locale $Locale = null): string { return 'Address'; } @@ -463,10 +463,15 @@ protected function delete(): void * Validate if the order has an invoice address * * @param QUI\Users\Address $Address - * @throws QUI\ERP\Order\Exception + * @throws QUI\Exception */ public function validate(QUI\Users\Address $Address): void { + if (!class_exists('QUI\ERP\Order\Exception')) { + QUI\System\Log::addError('Class "QUI\ERP\Order\Exception" not found.'); + throw new QUI\Exception('An error occurred.'); + } + $exception = [ 'quiqqer/order', 'exception.missing.address.field' diff --git a/src/QUI/FrontendUsers/Controls/Login.css b/src/QUI/FrontendUsers/Controls/Login.css index fe63fee9084fb1750bd06117d13d0ef469c8f95e..8ccf509c6b63f1b7e078952c776b119b599c65b1 100644 --- a/src/QUI/FrontendUsers/Controls/Login.css +++ b/src/QUI/FrontendUsers/Controls/Login.css @@ -22,6 +22,10 @@ width: 100%; } +.quiqqer-fu-login-container__inner { + display: flow-root; +} + .quiqqer-fu-login-social-entry { border: 1px solid #ddd; border-radius: 100%; @@ -135,8 +139,6 @@ } .quiqqer-fu-login-forget-password-reset { - background: #fff; - height: 100%; opacity: 0; position: absolute; top: 0; @@ -175,13 +177,6 @@ width: 100%; } -.quiqqer-fu-login-forget-password-reset [name="cancel"] { - bottom: 0; - padding: 0.5rem 2rem; - position: absolute; - right: 0; -} - .quiqqer-fu-login-activation-info { margin: 20px 0 20px 0; } diff --git a/src/QUI/FrontendUsers/Controls/Login.html b/src/QUI/FrontendUsers/Controls/Login.html index 34a2319e85b81565df4534b026c107c7b50d2874..47f37a1fe45ac7af1d88e61c9d65611663c248b7 100644 --- a/src/QUI/FrontendUsers/Controls/Login.html +++ b/src/QUI/FrontendUsers/Controls/Login.html @@ -14,89 +14,92 @@ </div> {else} -<section class="quiqqer-fu-login-container" style="display: none"> - {if $this->getAttribute('header')} - <h2>{locale group="quiqqer/frontend-users" var="control.login.title"}</h2> - {/if} - - <div class="quiqqer-fu-login-social"> - {foreach $authenticators as $entry} - <form action="" - method="POST" - class="quiqqer-fu-login-social-entry" - data-authenticator="{$entry.class}" - data-authenticator-hash="{$entry.class|md5}" - > - <span class="quiqqer-fu-login-social-entry-icon"> - {if $entry.icon} - <span class="{$entry.Login->getAttribute('icon')}"></span> - {elseif $entry.image} - <img src="{$entry.Login->getAttribute('icon')}" alt="" /> - {/if} - </span> - <span class="quiqqer-fu-login-social-entry-loader"> - <span class="fa fa-spin fa-spinner fas fa-circle-notch"></span> - </span> - <div class="quiqqer-fu-login-social-entry-control"> - {$entry.Login->create()} - </div> - </form> - {/foreach} - </div> +<section class="quiqqer-fu-login-container" style="display: none" data-name="login-container"> + <div class="quiqqer-fu-login-container__inner" data-name="login-container-inner"> + {if $this->getAttribute('header')} + <h2 data-name="header">{locale group="quiqqer/frontend-users" var="control.login.title"}</h2> + {/if} - {if $this->getAttribute('mail')} - {if count($authenticators)} - <div class="quiqqer-fu-login-between-text"> - {locale group="quiqqer/frontend-users" var="control.login.message.between"} + <div class="quiqqer-fu-login-social"> + {foreach $authenticators as $entry} + <form action="" + method="POST" + class="quiqqer-fu-login-social-entry" + data-name="social-login-form" + data-authenticator="{$entry.class}" + data-authenticator-hash="{$entry.class|md5}" + > + <span class="quiqqer-fu-login-social-entry-icon" data-name="social-login-entry-icon"> + {if $entry.icon} + <span class="{$entry.Login->getAttribute('icon')}"></span> + {elseif $entry.image} + <img src="{$entry.Login->getAttribute('icon')}" alt="" /> + {/if} + </span> + <span class="quiqqer-fu-login-social-entry-loader" data-name="social-login-entry-loader"> + <span class="fa fa-spin fa-spinner fas fa-circle-notch"></span> + </span> + <div class="quiqqer-fu-login-social-entry-control" data-name="social-login-controlContainer"> + {$entry.Login->create()} + </div> + </form> + {/foreach} </div> + + {if $this->getAttribute('mail')} + {if count($authenticators)} + <div class="quiqqer-fu-login-between-text"> + {locale group="quiqqer/frontend-users" var="control.login.message.between"} + </div> + {/if} {/if} - {/if} - <div class="quiqqer-fu-login-activation-info"></div> + <div class="quiqqer-fu-login-activation-info" data-name="activation-info"></div> - {if $this->getAttribute('mail')} - <form name="quiqqer-fu-login-email" class="quiqqer-fu-login-email"> - <section class="quiqqer-fu-login-email-mailSection"> - <label> - <span class="label"> - {locale group="quiqqer/frontend-users" var="control.registration.sign.up.email.title"} - </span> - <span class="field"> - <span class="icon fa fa-envelope"></span> - <input type="text" name="username" required autocomplete="email"/> - </span> - </label> + {if $this->getAttribute('mail')} + <form name="quiqqer-fu-login-email" class="quiqqer-fu-login-email"> + <section class="quiqqer-fu-login-email-mailSection"> + <label> + <span class="label"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.email.title"} + </span> + <span class="field"> + <span class="icon fa fa-envelope"></span> + <input type="text" name="username" required autocomplete="email"/> + </span> + </label> - <label> - <span class="label"> - {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.title"} - </span> - <span class="field"> - <span class="icon fa fa-key"></span> - <input type="password" name="password" required autocomplete="off"/> - </span> - </label> + <label> + <span class="label"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.title"} + </span> + <span class="field"> + <span class="icon fa fa-key"></span> + <input type="password" name="password" required autocomplete="off"/> + </span> + </label> - <div class="quiqqer-fu-login-email-buttons"> - <button type="submit" name="mail-login"> - {locale group="quiqqer/frontend-users" var="control.login.button"} - </button> - </div> - </section> - </form> - {/if} + <div class="quiqqer-fu-login-email-buttons"> + <button type="submit" name="mail-login"> + {locale group="quiqqer/frontend-users" var="control.login.button"} + </button> + </div> + </section> + </form> + {/if} - {if $showPasswordReset} - <div class="quiqqer-fu-login-forget-password-link"> - <a href="#"> - {locale group="quiqqer/frontend-users" var="control.login.forgotten.password"} - </a> + {if $showPasswordReset} + <div class="quiqqer-fu-login-forget-password-link" data-name="forgot-password-link"> + <a href="#"> + {locale group="quiqqer/frontend-users" var="control.login.forgotten.password"} + </a> + </div> + {/if} </div> - {/if} {if $showPasswordReset} - <div class="quiqqer-fu-login-forget-password-reset" style="display: none;"> - <section> + <div class="quiqqer-fu-login-forget-password-reset" style="display: none;" data-name="password-reset"> + <section data-name="password-reset-inner"> <h2>{locale group="quiqqer/frontend-users" var="quiqqer.auth.login.password.title"}</h2> <p> diff --git a/src/QUI/FrontendUsers/Controls/Login.php b/src/QUI/FrontendUsers/Controls/Login.php index 43ed7482cb7ab91c3c225ca88c6f57cee73d6965..18ad1861d8dafd5f5d023c2d70803fe93deb1bad 100644 --- a/src/QUI/FrontendUsers/Controls/Login.php +++ b/src/QUI/FrontendUsers/Controls/Login.php @@ -39,7 +39,10 @@ public function __construct(array $attributes = []) $this->setAttributes($attributes); - $this->addCSSFile(dirname(__FILE__) . '/Login.css'); + if (!defined('QUIQQER_CONTROL_TEMPLATE_USE_BASIC') || QUIQQER_CONTROL_TEMPLATE_USE_BASIC !== true) { + $this->addCSSFile(dirname(__FILE__) . '/Login.css'); + } + $this->addCSSClass('quiqqer-fu-login'); $this->setJavaScriptControl( @@ -120,7 +123,7 @@ public function getBody(): string 'showPasswordReset' => $showPasswordReset ]); - return $Engine->fetch(dirname(__FILE__) . '/Login.html'); + return $Engine->fetch($this->getTemplateFile()); } /** @@ -148,7 +151,6 @@ protected function getAuthenticators(): array } $authenticators = array_filter($authenticators, function ($authenticator) use ($allowed) { - /** @var QUI\Users\AuthenticatorInterface $Authenticator */ return in_array($authenticator, $allowed); }); } catch (Exception $Exception) { @@ -160,7 +162,6 @@ protected function getAuthenticators(): array } return array_filter($authenticators, function ($authenticator) use ($filterRegistrars) { - /** @var QUI\Users\AuthenticatorInterface $Authenticator */ return in_array($authenticator, $filterRegistrars); }); } diff --git a/src/QUI/FrontendUsers/Controls/Login_basic.html b/src/QUI/FrontendUsers/Controls/Login_basic.html new file mode 100644 index 0000000000000000000000000000000000000000..47f37a1fe45ac7af1d88e61c9d65611663c248b7 --- /dev/null +++ b/src/QUI/FrontendUsers/Controls/Login_basic.html @@ -0,0 +1,128 @@ +<noscript class="quiqqer-fu-login-noscript"> + <div class="message-error"> + {locale group="quiqqer/frontend-users" var="registrars.email.javascript_required"} + </div> +</noscript> + +{if $SessionUser->getId() && $SessionUser->getId() >= 100} +<div class="quiqqer-fu-login-logged-in message-information"> + {locale + group="quiqqer/frontend-users" + var="message.already.loged.in" + username=$SessionUser->getName() + } +</div> +{else} + +<section class="quiqqer-fu-login-container" style="display: none" data-name="login-container"> + <div class="quiqqer-fu-login-container__inner" data-name="login-container-inner"> + {if $this->getAttribute('header')} + <h2 data-name="header">{locale group="quiqqer/frontend-users" var="control.login.title"}</h2> + {/if} + + <div class="quiqqer-fu-login-social"> + {foreach $authenticators as $entry} + <form action="" + method="POST" + class="quiqqer-fu-login-social-entry" + data-name="social-login-form" + data-authenticator="{$entry.class}" + data-authenticator-hash="{$entry.class|md5}" + > + <span class="quiqqer-fu-login-social-entry-icon" data-name="social-login-entry-icon"> + {if $entry.icon} + <span class="{$entry.Login->getAttribute('icon')}"></span> + {elseif $entry.image} + <img src="{$entry.Login->getAttribute('icon')}" alt="" /> + {/if} + </span> + <span class="quiqqer-fu-login-social-entry-loader" data-name="social-login-entry-loader"> + <span class="fa fa-spin fa-spinner fas fa-circle-notch"></span> + </span> + <div class="quiqqer-fu-login-social-entry-control" data-name="social-login-controlContainer"> + {$entry.Login->create()} + </div> + </form> + {/foreach} + </div> + + {if $this->getAttribute('mail')} + {if count($authenticators)} + <div class="quiqqer-fu-login-between-text"> + {locale group="quiqqer/frontend-users" var="control.login.message.between"} + </div> + {/if} + {/if} + + <div class="quiqqer-fu-login-activation-info" data-name="activation-info"></div> + + {if $this->getAttribute('mail')} + <form name="quiqqer-fu-login-email" class="quiqqer-fu-login-email"> + <section class="quiqqer-fu-login-email-mailSection"> + <label> + <span class="label"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.email.title"} + </span> + <span class="field"> + <span class="icon fa fa-envelope"></span> + <input type="text" name="username" required autocomplete="email"/> + </span> + </label> + + <label> + <span class="label"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.title"} + </span> + <span class="field"> + <span class="icon fa fa-key"></span> + <input type="password" name="password" required autocomplete="off"/> + </span> + </label> + + <div class="quiqqer-fu-login-email-buttons"> + <button type="submit" name="mail-login"> + {locale group="quiqqer/frontend-users" var="control.login.button"} + </button> + </div> + </section> + </form> + {/if} + + {if $showPasswordReset} + <div class="quiqqer-fu-login-forget-password-link" data-name="forgot-password-link"> + <a href="#"> + {locale group="quiqqer/frontend-users" var="control.login.forgotten.password"} + </a> + </div> + {/if} + </div> + + {if $showPasswordReset} + <div class="quiqqer-fu-login-forget-password-reset" style="display: none;" data-name="password-reset"> + <section data-name="password-reset-inner"> + <h2>{locale group="quiqqer/frontend-users" var="quiqqer.auth.login.password.title"}</h2> + + <p> + {locale group="quiqqer/system" var="quiqqer.auth.login.password.message"} + </p> + + <label> + <span> + {locale group="quiqqer/system" var="quiqqer.auth.login.label.email"} + </span> + <input type="email" value="" name="email"/> + </label> + + <button type="submit" class="login qui-button btn-green reset-password"> + <span>{locale group='quiqqer/core' value='controls.users.auth.quiqqerlogin.btn.password_reset'}</span> + </button> + + <button name="cancel" type="reset"> + {locale group='quiqqer/core' value='cancel'} + </button> + </section> + </div> + {/if} +</section> + +{/if} diff --git a/src/QUI/FrontendUsers/Controls/Profile.css b/src/QUI/FrontendUsers/Controls/Profile.css index 9d7fb0c18b11a7e7b38419363eb824def34b7294..a0ac2e4e39d0be9e57543b360756f602e0c5e98a 100644 --- a/src/QUI/FrontendUsers/Controls/Profile.css +++ b/src/QUI/FrontendUsers/Controls/Profile.css @@ -1,168 +1,220 @@ .quiqqer-frontendUsers-controls-profile { - float: left; - width: 100%; + --_qui-frontend-users-profile__bg-color: var(--qui-frontend-users-profile__bg-color, #f5f5f5); + --_qui-frontend-users-profile__radius: var(--qui-frontend-users-profile__radius, 0.5rem); + --_qui-frontend-users-profile__content-maxWidth: var(--qui-frontend-users-profile__content-maxWidth, 600px); + --_qui-frontend-users-profile__sidebar-width: var(--qui-frontend-users-profile__sidebar-width, 300px); + --_qui-frontend-users-profile__sidebar-nav-item-outline--hover: var(--qui-frontend-users-profile__sidebar-nav-item-outline--hover, 2px solid currentColor); + --_qui-frontend-users-profile__sidebar-nav-item-bg-color--hover: var(--qui-frontend-users-profile__sidebar-nav-item-bg-color--hover, #f5f5f5); + + display: flex; + gap: clamp(2rem, 10vw - 4rem, 4rem); } -/** categories / tabs - ==================================== */ +.quiqqer-frontendUsers-controls-profile__sidebar { + min-width: 0; + width: var(--_qui-frontend-users-profile__sidebar-width); + flex-shrink: 0; +} -.quiqqer-frontendUsers-controls-profile-categories { - float: left; - padding-right: 10px; - width: 30%; +.quiqqer-frontendUsers-controls-profile__content { + min-width: 0; + flex-grow: 1; } -.quiqqer-fupc-category { - float: left; - width: 100%; +@media screen and (max-width: 767px) { + .quiqqer-frontendUsers-controls-profile { + flex-direction: column; + gap: 2rem; + } + + .quiqqer-frontendUsers-controls-profile__sidebar, + .quiqqer-frontendUsers-controls-profile__content { + width: 100% + } } -.quiqqer-fupc-category-header { - padding: 5px; - position: relative; - width: 100%; +/* general */ +address { + font-style: normal; +} + +address :where(.adr:not(:last-child)) { + margin-bottom: 0.5rem; +} + +/*********************/ +/* categories / tabs */ +/*********************/ +.quiqqer-fupc-category:not(:last-child) { + margin-bottom: 1rem; } -.quiqqer-fupc-category-header-opener { +.quiqqer-fupc-category-header { + padding-block: 0.5em; cursor: pointer; - line-height: 30px; - position: absolute; - right: 0; + font-weight: bold; + display: flex; + align-items: baseline; +} + +.quiqqer-fupc-category-header__text { + flex-grow: 1; +} + +.quiqqer-fupc-category-header__icon { text-align: center; width: 30px; } -.quiqqer-fupc-category--open .quiqqer-fupc-category-header { - border-bottom: 1px solid #bebebe; - margin-bottom: 5px; +:where([data-open="1"]) .quiqqer-fupc-category-header__icon { + transform: rotate(90deg); } .quiqqer-fupc-category-items-item { - clear: both; display: none; - width: 100%; -} - -.quiqqer-fupc-category-items-item--active { - background: rgba(0, 0, 0, 0.2); + text-decoration: none; + font-weight: normal; + padding: 0.25em 0.5em; + border-radius: var(--_qui-frontend-users-profile__radius); + outline-offset: -2px; + color: inherit; + align-items: baseline; } -.quiqqer-fupc-category--open .quiqqer-fupc-category-items-item { - display: block; +.quiqqer-fupc-category-items-item:where([data-active]) { + background-color: var(--_qui-frontend-users-profile__sidebar-nav-item-bg-color--hover); } -.quiqqer-fupc-category-items-item:hover { - background: rgba(0, 0, 0, 0.2); +:where([data-open="1"]) .quiqqer-fupc-category-items-item { + display: flex; } .quiqqer-fupc-category-items-item-icon { - float: left; line-height: 30px; - text-align: center; - width: 50px; + margin-right: 0.5em; + flex-shrink: 0; +} + +:where(.quiqqer-fupc-category-items-item):hover { + outline: var(--_qui-frontend-users-profile__sidebar-nav-item-outline--hover); + color: inherit; } .quiqqer-fupc-category-items-item-text { - float: left; - width: calc(100% - 50px); + flex-grow: 1; } .quiqqer-frontendUsers-saveButton { cursor: pointer; } -/** category content - ==================================== */ -/*.quiqqer-frontendUsers-controls-profile-control > header:first-of-type > h2 {*/ -/* margin-bottom: 2rem;*/ -/*}*/ +/* mobile */ +.quiqqer-frontendUsers-controls-profile-categories-mobile { + display: none; +} + +@media screen and (max-width: 767px) { + .quiqqer-frontendUsers-controls-profile-categories-mobile { + display: block; + } + + .quiqqer-frontendUsers-controls-profile-categories-mobile-label { + width: 100%; + } +} + +/********************/ +/* category content */ +/********************/ +/* content */ +.quiqqer-frontendUsers-profile-section:not(:last-child) { + margin-bottom: 2rem; +} .quiqqer-frontendUsers-controls-profile-categoryContent { - float: left; - padding-left: 10px; position: relative; - width: 70%; + max-width: var(--_qui-frontend-users-profile__content-maxWidth); + margin-inline: auto; } .quiqqer-frontendUsers-controls-profile-categoryContentAnimation { - float: left; position: relative; width: 100%; } -.quiqqer-frontendUsers-controls-profile-control label { - clear: both; - float: left; +/* form basic styling */ +.quiqqer-frontendUsers-controls-profile-control :where(h2) { + margin-top: 0; +} + +.quiqqer-frontendUsers-controls-profile-control :where(label) { margin-bottom: 1rem; width: 100%; + display: block; } -@media screen and (min-width: 768px) { - .quiqqer-frontendUsers-label { - float: left; - width: 30%; - } +/* todo fix in qui, no float: left */ +.quiqqer-frontendUsers-userdata-address-country, +.quiqqer-frontendUsers-userdata-language, +.quiqqer-frontendUsers-userdata-invoiceaddress { + display: flow-root; } -/** user - ==================================== */ +.quiqqer-frontendUsers-controls-profile-control :where(label > span:first-child) { + font-size: 0.875em; + margin-bottom: 0.5em; + opacity: 0.75; +} + +.quiqqer-frontendUsers-controls-profile-control :where(input:not([type="checkbox"], [type="radio"]), select, textarea) { + width: 100%; +} -.package-intranet-profile-birthday { - float: left; - width: 66%; +/* form basic styling: user data */ +.quiqqer-frontendUsers-controls-profile-control :where(.address-street--2-col) { + display: grid; + grid-template-columns: 5fr minmax(5rem, 1fr); + gap: 1rem; } -.package-intranet-profile-birthday [name="birth_day"], -.package-intranet-profile-birthday [name="birth_year"] { - float: left; - width: calc(20% - 10px); +.quiqqer-frontendUsers-controls-profile-control select[name="country"] { + display: none; } -.package-intranet-profile-birthday [name="birth_month"] { - float: left; - margin: 0 10px; - width: 60%; +.quiqqer-frontendUsers-controls-profile-control .qui-select { + width: 100%; } -/** responsive - ==================================== */ +.quiqqer-frontendUsers-controls-profile-control :where(.qui-select .drop-icon) { + height: 36px; +} +/**************/ +/* responsive */ +/**************/ .quiqqer-frontendUsers-controls-profile-categories-mobile { display: none; } - @media (max-width: 767px) { - .quiqqer-frontendUsers-userdata-label { - display: block; + .quiqqer-frontendUsers-controls-profile-control input:not([type="checkbox"], [type="radio"]), + .quiqqer-frontendUsers-userdata-address-country-select, + .quiqqer-frontendUsers-controls-profile-categories-mobile select, + .package-intranet-profile-birthday, + .quiqqer-frontendUsers-userdata-email, + .quiqqer-frontendUsers-userdata-field { width: 100%; - margin-bottom: 0.25rem; } - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="firstname"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="lastname"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="street_no"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="zip"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="city"],*/ - .quiqqer-frontendUsers-controls-profile-control input, .quiqqer-frontendUsers-userdata-address-country-select, .package-intranet-profile-birthday, .quiqqer-frontendUsers-userdata-email, .quiqqer-frontendUsers-userdata-field { display: block; - width: 100%; } - .quiqqer-frontendUsers-controls-profile-categories-mobile { - clear: both; - display: inline; - float: left; - width: 100%; - } - - .quiqqer-frontendUsers-controls-profile-categories-mobile select { - clear: both; + display: block; width: 100%; } @@ -171,8 +223,7 @@ } .quiqqer-frontendUsers-controls-profile-categoryContent { - padding: 0; - width: 100%; + max-width: 100%; } .quiqqer-frontendUsers-saveButton { diff --git a/src/QUI/FrontendUsers/Controls/Profile.html b/src/QUI/FrontendUsers/Controls/Profile.html index 33524ce733ec8c5e21854d279c9f23cae3096079..4fad8c34c074c99d64315c67faf8e5cdb70af388 100644 --- a/src/QUI/FrontendUsers/Controls/Profile.html +++ b/src/QUI/FrontendUsers/Controls/Profile.html @@ -1,70 +1,81 @@ {if !$Category} -<p> - {locale group="quiqqer/frontend-users" var="profile.no_categories_available"} -</p> + <div class="quiqqer-frontendUsers-controls-profile__noCategoriesInfo"> + <p> + {locale group="quiqqer/frontend-users" var="profile.no_categories_available"} + </p> + </div> {else} + <div class="quiqqer-frontendUsers-controls-profile__sidebar"> + {if $this->getAttribute('menu')} + <div class="quiqqer-frontendUsers-controls-profile-categories"> + {foreach $categories as $category} + <div class="quiqqer-fupc-category" + data-category="{$category.name}" + data-name="nav-category" + data-open="1" + > + <div class="quiqqer-fupc-category-header" data-name="header"> + <span class="quiqqer-fupc-category-header__text">{$category.title}</span> + <span class="fa-solid fa-angle-right quiqqer-fupc-category-header__icon" data-name="opener"></span> + </div> + <div class="quiqqer-fu-profile-categories-category-items"> + {foreach $category.items as $setting} + {assign var=active value=false} + {if $currentCategory == $category.name && $currentSetting == $setting.name} + {assign var=active value=true} + {/if} + <a href="{$Site->getUrlRewritten()}/{$category.name}/{$setting.name}" + class="quiqqer-fupc-category-items-item" + {if $active}data-active{/if} + data-name="nav-category-item" + data-setting="{$setting.name}" + > + {if $setting.icon} + <span class="fa-fw quiqqer-fupc-category-items-item-icon {$setting.icon}"></span> + {/if} + <span>{$setting.title}</span> + </a> + {/foreach} + </div> + </div> + {/foreach} + </div> + {/if} -{if $this->getAttribute('menu')} -<div class="quiqqer-frontendUsers-controls-profile-categories"> - {foreach $categories as $category} - <div class="quiqqer-fupc-category quiqqer-fupc-category--open" - data-category="{$category.name}" - > - <div class="quiqqer-fupc-category-header"> - {$category.title} - </div> - <div class="quiqqer-fu-profile-categories-category-items"> - {foreach $category.items as $setting} - - {assign var=active value=""} - {if $currentCategory == $category.name && $currentSetting == $setting.name} - {assign var=active value=" quiqqer-fupc-category-items-item--active"} - {/if} - <a href="{$Site->getUrlRewritten()}/{$category.name}/{$setting.name}" - class="quiqqer-fupc-category-items-item{$active}" - data-setting="{$setting.name}" - > - {if $setting.icon} - <span class="quiqqer-fupc-category-items-item-icon {$setting.icon}"></span> - {/if} - <span>{$setting.title}</span> - </a> - {/foreach} - </div> + {* mobile categories *} + <form action="" method="post" class="quiqqer-frontendUsers-controls-profile-categories-mobile"> + <label class="quiqqer-frontendUsers-controls-profile-categories-mobile-label"> + <select name="profile-categories-mobile"> + {foreach $categories as $category} + <optgroup label="{$category.title}"> + {foreach $category.items as $setting} + {assign var=active value=""} + {if $currentCategory == $category.name && $currentSetting == $setting.name} + {assign var=active value="selected"} + {/if} + <option value="{$category.name}:{$setting.name}" {$active}> + {$setting.title} + </option> + {/foreach} + </optgroup> + {/foreach} + </select> + </label> + </form> </div> - {/foreach} -</div> -{/if} - -<form action="" method="post"> - <label class="quiqqer-frontendUsers-controls-profile-categories-mobile"> - <select name="profile-categories"> - {foreach $categories as $category} - <optgroup label="{$category.title}"> - {foreach $category.items as $setting} - {assign var=active value=""} - {if $currentCategory== $category.name} - {assign var=active value=" selected"} - {/if} - <option value="{$category.name}:{$setting.name}"{$active}> - {$setting.title} - </option> - {/foreach} - </optgroup> - {/foreach} - </select> - </label> -</form> -<form class="quiqqer-frontendUsers-controls-profile-categoryContent" - method="post" - action="" - data-category="{$currentCategory}" - data-setting="{$currentSetting}" -> - <div class="quiqqer-frontendUsers-controls-profile-categoryContentAnimation"> - {$Category->create()} - <input type="hidden" name="profile-save" value="1"> + <div class="quiqqer-frontendUsers-controls-profile__content"> + <form class="quiqqer-frontendUsers-controls-profile-categoryContent" + data-name="form" + method="post" + action="" + data-category="{$currentCategory}" + data-setting="{$currentSetting}" + > + <div class="quiqqer-frontendUsers-controls-profile-categoryContentAnimation" data-name="content-animated"> + {$Category->create()} + <input type="hidden" name="profile-save" value="1"> + </div> + </form> </div> -</form> {/if} \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Profile.php b/src/QUI/FrontendUsers/Controls/Profile.php index 4c14aa214254d015e8f4b07146749e8867dbcff6..abdec5ed99aca5e57f3e1ace026533d792543ed0 100644 --- a/src/QUI/FrontendUsers/Controls/Profile.php +++ b/src/QUI/FrontendUsers/Controls/Profile.php @@ -35,8 +35,10 @@ public function __construct(array $attributes = []) parent::__construct($attributes); +// if (!defined('QUIQQER_CONTROL_TEMPLATE_USE_BASIC') || QUIQQER_CONTROL_TEMPLATE_USE_BASIC !== true) { $this->addCSSFile(dirname(__FILE__) . '/Profile.css'); $this->addCSSClass('quiqqer-frontendUsers-controls-profile'); +// } $this->setAttribute( 'data-qui', @@ -82,7 +84,7 @@ public function getBody(): string * @param bool $category * @return bool */ - $getFirstCategorySetting = function ($array, bool|string|int $category = false) use ($getFirstCategory) { + $getFirstCategorySetting = function ($array, bool | string | int $category = false) use ($getFirstCategory) { if ($category === false) { $category = $getFirstCategory($array); } @@ -145,7 +147,9 @@ public function getBody(): string return ''; } - $Control->setAttribute('User', $this->getUser()); + if (method_exists($Control, 'setAttribute')) { + $Control->setAttribute('User', $this->getUser()); + } if ($Request->request->get('profile-save')) { try { @@ -171,7 +175,7 @@ public function getBody(): string 'this' => $this ]); - return $Engine->fetch(dirname(__FILE__) . '/Profile.html'); + return $Engine->fetch($this->getTemplateFile()); } /** diff --git a/src/QUI/FrontendUsers/Controls/Profile/Address.html b/src/QUI/FrontendUsers/Controls/Profile/Address.html index 3802f1c277c9cfe6e21a5b0ddb180d303f37276f..5360bdfae01b3b2df6f4f9a9b11aceec71885b8b 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/Address.html +++ b/src/QUI/FrontendUsers/Controls/Profile/Address.html @@ -1,23 +1,27 @@ {if isset($manager) && $manager} -<div class="quiqqer-frontendUsers-manager" - data-qui="package/quiqqer/frontend-users/bin/frontend/controls/address/Manager" -></div> + <div class="quiqqer-frontendUsers-manager" + data-qui="package/quiqqer/frontend-users/bin/frontend/controls/address/Manager" + ></div> {else} -{foreach $addressFields as $field => $options} -<label> - <span class="quiqqer-frontendUsers-label"> - {locale group="quiqqer/system" var=$field} - {if $options['required']}*{/if} - </span> - {if $field == 'country'} - {$CountrySelect->create()} - {else} - <input name="{$field}" value="{$options['value']}"/> - {/if} -</label> -{/foreach} + <section class="quiqqer-frontendUsers-profile-section"> + <h2>{locale group="quiqqer/frontend-users" var="profile.address.category.title"}</h2> -<button type="submit" class="quiqqer-frontendUsers-saveButton"> - {locale group="quiqqer/frontend-users" var="profile.address.btn.submit"} -</button> + {foreach $addressFields as $field => $options} + <label> + <span class="quiqqer-frontendUsers-label"> + {locale group="quiqqer/system" var=$field} + {if $options['required']}*{/if} + </span> + {if $field == 'country'} + {$CountrySelect->create()} + {else} + <input name="{$field}" value="{$options['value']}"/> + {/if} + </label> + {/foreach} + </section> + + <button type="submit" class="btn btn-primary quiqqer-frontendUsers-saveButton"> + {locale group="quiqqer/frontend-users" var="profile.address.btn.submit"} + </button> {/if} diff --git a/src/QUI/FrontendUsers/Controls/Profile/Address.php b/src/QUI/FrontendUsers/Controls/Profile/Address.php index 820433c14f87208ea59c5c63ee59adf61ef1551e..3196715188b2c99c33f0b5c82211799754be51a6 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/Address.php +++ b/src/QUI/FrontendUsers/Controls/Profile/Address.php @@ -126,7 +126,7 @@ public function getBody(): string 'addressFields' => $addressFields ]); - return $Engine->fetch(dirname(__FILE__) . '/Address.html'); + return $Engine->fetch($this->getTemplateFile()); } /** diff --git a/src/QUI/FrontendUsers/Controls/Profile/Address_basic.html b/src/QUI/FrontendUsers/Controls/Profile/Address_basic.html new file mode 100644 index 0000000000000000000000000000000000000000..5360bdfae01b3b2df6f4f9a9b11aceec71885b8b --- /dev/null +++ b/src/QUI/FrontendUsers/Controls/Profile/Address_basic.html @@ -0,0 +1,27 @@ +{if isset($manager) && $manager} + <div class="quiqqer-frontendUsers-manager" + data-qui="package/quiqqer/frontend-users/bin/frontend/controls/address/Manager" + ></div> +{else} + <section class="quiqqer-frontendUsers-profile-section"> + <h2>{locale group="quiqqer/frontend-users" var="profile.address.category.title"}</h2> + + {foreach $addressFields as $field => $options} + <label> + <span class="quiqqer-frontendUsers-label"> + {locale group="quiqqer/system" var=$field} + {if $options['required']}*{/if} + </span> + {if $field == 'country'} + {$CountrySelect->create()} + {else} + <input name="{$field}" value="{$options['value']}"/> + {/if} + </label> + {/foreach} + </section> + + <button type="submit" class="btn btn-primary quiqqer-frontendUsers-saveButton"> + {locale group="quiqqer/frontend-users" var="profile.address.btn.submit"} + </button> +{/if} diff --git a/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.css b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.css index a172ceb808d000c7be1157122567f255475cd85b..6823ba2575ead50fb270d791a4d7378814b09d49 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.css +++ b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.css @@ -1,12 +1,5 @@ .quiqqer-frontendUsers-changepassword-error, .quiqqer-frontendUsers-changepassword-success { display: none; - margin-bottom: 20px; + margin-bottom: 1rem; } - -@media screen and (min-width: 768px) { - .quiqqer-frontendUsers-controls-profile-changepassword input { - float: left; - width: 70%; - } -} \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.html b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.html index 651137e64afc16789973d7755d7301f58512e5d4..eec50fa05d6dcff036e3138e2fbc00a455485b59 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.html +++ b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.html @@ -1,29 +1,31 @@ -<h2>{locale group="quiqqer/frontend-users" var="profile.changepassword.title"}</h2> +<div class="quiqqer-frontendUsers-profile-section"> + <h2>{locale group="quiqqer/frontend-users" var="profile.changepassword.title"}</h2> -<div class="quiqqer-frontendUsers-changepassword-error content-message-error"></div> -<div class="quiqqer-frontendUsers-changepassword-success content-message-success"></div> + <div class="quiqqer-frontendUsers-changepassword-error content-message-error" data-name="msg-error"></div> + <div class="quiqqer-frontendUsers-changepassword-success content-message-success" data-name="msg-success"></div> -<label> - <span class="quiqqer-frontendUsers-label"> - {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_old"} - </span> - <input name="passwordOld" type="password"/> -</label> + <label> + <span class="quiqqer-frontendUsers-label"> + {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_old"} + </span> + <input name="passwordOld" type="password"/> + </label> -<label> - <span class="quiqqer-frontendUsers-label"> - {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_new"} - </span> - <input name="passwordNew" type="password"/> -</label> + <label> + <span class="quiqqer-frontendUsers-label"> + {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_new"} + </span> + <input name="passwordNew" type="password"/> + </label> -<label> - <span class="quiqqer-frontendUsers-label"> - {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_new_confirm"} - </span> - <input name="passwordNewConfirm" type="password"/> -</label> + <label> + <span class="quiqqer-frontendUsers-label"> + {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_new_confirm"} + </span> + <input name="passwordNewConfirm" type="password"/> + </label> +</div> -<button type="submit" class="quiqqer-frontendUsers-saveButton"> +<button type="submit" class="btn btn-primary quiqqer-frontendUsers-saveButton"> {locale group="quiqqer/frontend-users" var="profile.changepassword.btn.submit"} </button> \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.php b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.php index fa0dadf052554614b020693edc54ecc0bd6439db..6c567c906f75d9d97fbf160aa030aeba3ba0925f 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.php +++ b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword.php @@ -27,6 +27,10 @@ public function __construct(array $attributes = []) $this->addCSSClass('quiqqer-frontendUsers-controls-profile-changepassword'); $this->addCSSClass('quiqqer-frontendUsers-controls-profile-control'); + if (!defined('QUIQQER_CONTROL_TEMPLATE_USE_BASIC') || QUIQQER_CONTROL_TEMPLATE_USE_BASIC !== true) { + $this->addCSSFile(dirname(__FILE__) . '/ChangePassword.css'); + } + $this->setJavaScriptControl('package/quiqqer/frontend-users/bin/frontend/controls/profile/ChangePassword'); } @@ -41,9 +45,7 @@ public function getBody(): string 'User' => QUI::getUserBySession() ]); - $this->addCSSFile(dirname(__FILE__) . '/ChangePassword.css'); - - return $Engine->fetch(dirname(__FILE__) . '/ChangePassword.html'); + return $Engine->fetch($this->getTemplateFile()); } /** diff --git a/src/QUI/FrontendUsers/Controls/Profile/ChangePassword_basic.html b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword_basic.html new file mode 100644 index 0000000000000000000000000000000000000000..b4ffd1db8cb90a1a29bda1bffccea1986e1ea22d --- /dev/null +++ b/src/QUI/FrontendUsers/Controls/Profile/ChangePassword_basic.html @@ -0,0 +1,31 @@ +<div class="quiqqer-frontendUsers-profile-section"> + <h2>{locale group="quiqqer/frontend-users" var="profile.changepassword.title"}</h2> + + <div class="content-message-error" data-name="msg-error"></div> + <div class="content-message-success" data-name="msg-success"></div> + + <label class="qui-form-label"> + <span class="qui-form-label__text"> + {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_old"} + </span> + <input name="passwordOld" type="password" class="qui-form-label__input"/> + </label> + + <label class="qui-form-label"> + <span class="qui-form-label__text"> + {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_new"} + </span> + <input name="passwordNew" type="password" class="qui-form-label__input"/> + </label> + + <label class="qui-form-label"> + <span class="qui-form-label__text"> + {locale group="quiqqer/frontend-users" var="profile.changepassword.label.password_new_confirm"} + </span> + <input name="passwordNewConfirm" type="password" class="qui-form-label__input"/> + </label> +</div> + +<button type="submit" class="quiqqer-frontendUsers-saveButton"> + {locale group="quiqqer/frontend-users" var="profile.changepassword.btn.submit"} +</button> \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.html b/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.html index f4d723916bd308907aac53caa4548d6ed86092c9..c9bc58ef1399dbc0533de85741c4897317f39eb9 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.html +++ b/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.html @@ -1,34 +1,38 @@ -<h2>{locale group="quiqqer/frontend-users" var="profile.deleteaccount.title"}</h2> +<div class="quiqqer-frontendUsers-profile-section"> + <h2>{locale group="quiqqer/frontend-users" var="profile.deleteaccount.title"}</h2> -{if $action == 'deleteaccount_error'} -<div class="content-message-error"> - {locale group="quiqqer/frontend-users" var="profile.deleteaccount.message.error"} -</div> -{/if} + {if $action == 'deleteaccount_error'} + <div class="content-message-error"> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.message.error"} + </div> + {/if} -{if $action == 'deleteaccount_confirm_wait'} -<div class="content-message-information"> - {locale group="quiqqer/frontend-users" var="profile.deleteaccount.message.confirm_wait"} + {if $action == 'deleteaccount_confirm_wait'} + <div class="content-message-information"> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.message.confirm_wait"} + </div> + {else} + <p> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.info"} + </p> + {/if} </div> -<button type="submit" class="quiqqer-frontendUsers-saveButton"> - <span class="fa fa-envelope"></span> - {locale group="quiqqer/frontend-users" var="profile.deleteaccount.btn.submit_again"} -</button> +{if $action == 'deleteaccount_confirm_wait'} + <button type="submit" class="quiqqer-frontendUsers-saveButton"> + <span class="fa fa-envelope"></span> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.btn.submit_again"} + </button> -<script> - require(['qui/QUI'], function (QUI) { - QUI.fireEvent('quiqqerFrontendUsersAccountDeleteStart'); - QUI.setAttribute('QUIQQER_FRONTEND_USERS_ACCOUNT_DELETE_START', true); - }); -</script> + <script> + require(['qui/QUI'], function (QUI) { + QUI.fireEvent('quiqqerFrontendUsersAccountDeleteStart'); + QUI.setAttribute('QUIQQER_FRONTEND_USERS_ACCOUNT_DELETE_START', true); + }); + </script> {else} -<p> - {locale group="quiqqer/frontend-users" var="profile.deleteaccount.info"} -</p> - -<button type="submit" class="quiqqer-frontendUsers-saveButton btn btn-danger"> - <span class="fa fa-trash"></span> - {locale group="quiqqer/frontend-users" var="profile.deleteaccount.btn.submit"} -</button> + <button type="submit" class="quiqqer-frontendUsers-saveButton btn btn-danger"> + <span class="fa fa-trash"></span> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.btn.submit"} + </button> {/if} \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.php b/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.php index 78dd4f5ef888986c05bc80e1d2b65d9f8878dd03..c994093f23e45f8c4081ea21d28817cccc6386e6 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.php +++ b/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount.php @@ -10,7 +10,8 @@ use QUI; use QUI\FrontendUsers\Handler; use QUI\System\Log; -use QUI\Verification\Verifier; +use QUI\Verification\Interface\VerificationRepositoryInterface; +use QUI\Verification\VerificationRepository; /** * Class DeleteAccount @@ -23,13 +24,22 @@ class DeleteAccount extends AbstractProfileControl * DeleteAccount constructor. * @param array $attributes */ - public function __construct(array $attributes = []) - { + public function __construct( + array $attributes = [], + private ?VerificationRepositoryInterface $verificationRepository = null + ) { + if (is_null($this->verificationRepository)) { + $this->verificationRepository = new VerificationRepository(); + } + parent::__construct($attributes); $this->addCSSClass('quiqqer-frontendUsers-controls-profile-deleteaccount'); $this->addCSSClass('quiqqer-frontendUsers-controls-profile-control'); - $this->addCSSFile(dirname(__FILE__) . '/DeleteAccount.css'); + + if (!defined('QUIQQER_CONTROL_TEMPLATE_USE_BASIC') || QUIQQER_CONTROL_TEMPLATE_USE_BASIC !== true) { + $this->addCSSFile(dirname(__FILE__) . '/DeleteAccount.css'); + } $this->setJavaScriptControl('package/quiqqer/frontend-users/bin/frontend/controls/profile/DeleteAccount'); $this->setJavaScriptControlOption('username', QUI::getUserBySession()->getUsername()); @@ -44,17 +54,17 @@ public function getBody(): string $action = false; try { - $DeleteVerification = Verifier::getVerificationByIdentifier( - QUI::getUserBySession()->getUUID(), - QUI\FrontendUsers\UserDeleteConfirmVerification::getType(), - true + $verification = $this->verificationRepository->findByIdentifier( + 'confirmdelete-' . QUI::getUserBySession()->getUUID() ); - if (Verifier::isVerificationValid($DeleteVerification)) { - $action = 'deleteaccount_confirm_wait'; - $this->setJavaScriptControlOption('deletestarted', 1); - } else { - Verifier::removeVerification($DeleteVerification); + if ($verification) { + if ($verification->isValid()) { + $action = 'deleteaccount_confirm_wait'; + $this->setJavaScriptControlOption('deletestarted', 1); + } else { + $this->verificationRepository->delete($verification); + } } } catch (Exception) { // nothing - no active user delete verification @@ -69,7 +79,7 @@ public function getBody(): string 'action' => $action ]); - return $Engine->fetch(dirname(__FILE__) . '/DeleteAccount.html'); + return $Engine->fetch($this->getTemplateFile()); } /** diff --git a/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount_basic.html b/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount_basic.html new file mode 100644 index 0000000000000000000000000000000000000000..c9bc58ef1399dbc0533de85741c4897317f39eb9 --- /dev/null +++ b/src/QUI/FrontendUsers/Controls/Profile/DeleteAccount_basic.html @@ -0,0 +1,38 @@ +<div class="quiqqer-frontendUsers-profile-section"> + <h2>{locale group="quiqqer/frontend-users" var="profile.deleteaccount.title"}</h2> + + {if $action == 'deleteaccount_error'} + <div class="content-message-error"> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.message.error"} + </div> + {/if} + + {if $action == 'deleteaccount_confirm_wait'} + <div class="content-message-information"> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.message.confirm_wait"} + </div> + {else} + <p> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.info"} + </p> + {/if} +</div> + +{if $action == 'deleteaccount_confirm_wait'} + <button type="submit" class="quiqqer-frontendUsers-saveButton"> + <span class="fa fa-envelope"></span> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.btn.submit_again"} + </button> + + <script> + require(['qui/QUI'], function (QUI) { + QUI.fireEvent('quiqqerFrontendUsersAccountDeleteStart'); + QUI.setAttribute('QUIQQER_FRONTEND_USERS_ACCOUNT_DELETE_START', true); + }); + </script> +{else} + <button type="submit" class="quiqqer-frontendUsers-saveButton btn btn-danger"> + <span class="fa fa-trash"></span> + {locale group="quiqqer/frontend-users" var="profile.deleteaccount.btn.submit"} + </button> +{/if} \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.css b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.css index d34eb1e9828fd5ed71fb58cb83dd12b9eb8954ec..df487bc934fd3e73997e7d000e2b3cd6326eb3ae 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.css +++ b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.css @@ -23,8 +23,8 @@ } .quiqqer-frontendUsers-userAvatar-upload-control { - height: 30px; - margin-bottom: 2rem; + display: flow-root; + margin-bottom: 1rem; } .quiqqer-frontendUsers-userAvatar-gravatar-section { @@ -32,11 +32,5 @@ } .quiqqer-frontendUsers-userAvatar-gravatar input { - float: left; -} - -@media screen and (max-width: 767px) { - .quiqqer-frontendUsers-userAvatar-saveButton { - width: 100%; - } + margin-right: 0.5em; } \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.html b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.html index 3dd0409d92081ea24f0c928dc8e7a2398b10f611..f42b974ada4191416ada5eed8b8f86620ac8e1fb 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.html +++ b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.html @@ -1,18 +1,16 @@ -<section class="quiqqer-frontendUsers-userAvatar-section"> - <header> - <h2> - {locale group="quiqqer/frontend-users" var="profile.avatar.title"} - </h2> - </header> +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userAvatar-section"> + <h2> + {locale group="quiqqer/frontend-users" var="profile.avatar.title"} + </h2> {if $AvatarImage} <div class="quiqqer-frontendUsers-userAvatar-icon" title="{$User->getName()}" {$avatarImageUrl}></div> {else} - <span class="quiqqer-frontendUsers-userAvatar-icon-none content-message-information"> + <div class="quiqqer-frontendUsers-userAvatar-icon-none content-message-information"> {locale group="quiqqer/frontend-users" var="profile.no.avatar.message"} - </span> + </div> {/if} {if $uploadEnabled} @@ -29,29 +27,28 @@ </section> {if $gravatarEnabled} -<section class="quiqqer-frontendUsers-userAvatar-gravatar-section"> - <header> - <h2> - {locale group="quiqqer/frontend-users" var="profile.avatar.gravatar.title"} - </h2> - </header> +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userAvatar-gravatar-section"> + <h2> + {locale group="quiqqer/frontend-users" var="profile.avatar.gravatar.title"} + </h2> <div class="quiqqer-frontendUsers-userAvatar-gravatar"> <label> + <input name="useGravatar" type="checkbox" {if $userGravatarIcon} checked{/if}/> + <span> {locale group="quiqqer/frontend-users" - var="profile.useravatar.gravatar_option" - email=$User->getAttribute('email') + var="profile.useravatar.gravatar_option" + email=$User->getAttribute('email') } </span> - <input name="useGravatar" type="checkbox" {if $userGravatarIcon} checked{/if}/> </label> </div> </section> {/if} {if $uploadEnabled || $gravatarEnabled} -<button type="submit" class="quiqqer-frontendUsers-userAvatar-saveButton"> +<button type="submit" class="btn btn-primary quiqqer-frontendUsers-saveButton"> {locale group="quiqqer/frontend-users" var="profile.userdata.save"} </button> {/if} diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.php b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.php index 3b3b43d978e7ee9677794a3f85246ae605d620fb..0f047467fc021488ef80c87c7f9ca04535d532dc 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.php +++ b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar.php @@ -28,8 +28,12 @@ public function __construct(array $attributes = []) $this->setAttribute('data-qui', 'package/quiqqer/frontend-users/bin/frontend/controls/profile/UserAvatar'); + $this->addCSSClass('quiqqer-frontendUsers-controls-profile-control'); $this->addCSSClass('quiqqer-frontendUsers-UserAvatar'); - $this->addCSSFile(dirname(__FILE__) . '/UserAvatar.css'); + + if (!defined('QUIQQER_CONTROL_TEMPLATE_USE_BASIC') || QUIQQER_CONTROL_TEMPLATE_USE_BASIC !== true) { + $this->addCSSFile(dirname(__FILE__) . '/UserAvatar.css'); + } } /** @@ -55,7 +59,7 @@ public function getBody(): string if (!empty($userGravatarIcon) && $gravatarEnabled && !empty($userEmail)) { $userGravatarIcon = true; $AvatarImage = new ExternalImage(Utils::getGravatarUrl($userEmail, 100)); - $url = $AvatarImage->getSizeCacheUrl(100, 100); + $url = $AvatarImage->getSizeCacheUrl(); $Engine->assign([ 'avatarImageUrl' => ' style="background-image: url(\'' . $url . '\')"' @@ -103,7 +107,7 @@ public function getBody(): string ) ); - return $Engine->fetch(dirname(__FILE__) . '/UserAvatar.html'); + return $Engine->fetch($this->getTemplateFile()); } /** diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserAvatarUpload.php b/src/QUI/FrontendUsers/Controls/Profile/UserAvatarUpload.php index 324574ae5f993720e3aa7b9dc30f29e135dfb5e3..3a2850325e8b67ed7f8d1fb983f526713fc14cd8 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/UserAvatarUpload.php +++ b/src/QUI/FrontendUsers/Controls/Profile/UserAvatarUpload.php @@ -114,7 +114,10 @@ public function onFileFinish($file, $params): void ); $File->activate(QUI::getUsers()->getSystemUser()); - $File->setTitle($SessionUser->getUsername()); + + if (method_exists($File, 'setTitle')) { + $File->setTitle($SessionUser->getUsername()); + } $SessionUser->setAttribute('avatar', $File->getUrl()); $SessionUser->save(); diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserAvatar_basic.html b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar_basic.html new file mode 100644 index 0000000000000000000000000000000000000000..d336868820601e67ed752ea36a451de585e48fc9 --- /dev/null +++ b/src/QUI/FrontendUsers/Controls/Profile/UserAvatar_basic.html @@ -0,0 +1,54 @@ +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userAvatar-section"> + <h2> + {locale group="quiqqer/frontend-users" var="profile.avatar.title"} + </h2> + + {if $AvatarImage} + <div class="quiqqer-frontendUsers-userAvatar-icon" + title="{$User->getName()}" + {$avatarImageUrl}></div> + {else} + <div class="content-message-information"> + {locale group="quiqqer/frontend-users" var="profile.no.avatar.message"} + </div> + {/if} + + {if $uploadEnabled} + <div class="quiqqer-frontendUsers-userAvatar-upload"> + <h3> + {locale group="quiqqer/frontend-users" var="profile.avatar.upload.title"} + </h3> + + <div class="quiqqer-frontendUsers-userAvatar-upload-control"> + {$AvatarUpload->create()} + </div> + </div> + {/if} +</section> + +{if $gravatarEnabled} +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userAvatar-gravatar-section"> + <h2> + {locale group="quiqqer/frontend-users" var="profile.avatar.gravatar.title"} + </h2> + + <div class="quiqqer-frontendUsers-userAvatar-gravatar"> + <label> + <input name="useGravatar" type="checkbox" {if $userGravatarIcon} checked{/if}/> + + <span> + {locale group="quiqqer/frontend-users" + var="profile.useravatar.gravatar_option" + email=$User->getAttribute('email') + } + </span> + </label> + </div> +</section> +{/if} + +{if $uploadEnabled || $gravatarEnabled} +<button type="submit" class="btn btn-primary quiqqer-frontendUsers-saveButton"> + {locale group="quiqqer/frontend-users" var="profile.userdata.save"} +</button> +{/if} diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserData.css b/src/QUI/FrontendUsers/Controls/Profile/UserData.css index 224e493e237e2ea2972916ec126d76e22615ed1e..bc505fee63123d8d6918cfbb9d587540aeb11dc7 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/UserData.css +++ b/src/QUI/FrontendUsers/Controls/Profile/UserData.css @@ -1,140 +1,20 @@ -/** -1. because children are floated and we don't want to set float: left on this element - we need to create a new stacking context - */ -.quiqqer-frontendUsers-userdata-section, -.quiqqer-frontendUsers-userdata-address, -.quiqqer-frontendUsers-userdata-address-additional { - clear: both; - width: 100%; - max-width: 100%; - margin-bottom: 1rem; - display: flow-root; /* 1 */ -} - -.quiqqer-frontendUsers-userdata-saveButton { - clear: both; - display: block; -} - -.quiqqer-frontendUsers-userdata-email__hidden { +[data-hidden] { display: none; } -.quiqqer-frontendUsers-userdata-email-new-hint { - clear: both; - width: 100%; - float: left; -} - .package-intranet-profile-birthday { - width: 100%; -} - -/*.quiqqer-frontendUsers-controls-profile-userdata input[name="firstname"],*/ -/*.quiqqer-frontendUsers-controls-profile-userdata input[name="lastname"],*/ -/*.quiqqer-frontendUsers-controls-profile-userdata input[name="street_no"],*/ -/*.quiqqer-frontendUsers-controls-profile-userdata input[name="zip"],*/ -/*.quiqqer-frontendUsers-controls-profile-userdata input[name="city"],*/ -/*.quiqqer-frontendUsers-controls-profile-userdata input[name="tel"],*/ -/*.quiqqer-frontendUsers-controls-profile-userdata input,*/ -/*.quiqqer-frontendUsers-userdata-address-country-select,*/ -/*.quiqqer-frontendUsers-userdata-field {*/ -/* float: left;*/ -/* width: 70%;*/ -/*}*/ - -@media screen and (min-width: 768px) { - .quiqqer-frontendUsers-userdata-label { - float: left; - width: 30%; - } - - .quiqqer-frontendUsers-controls-profile-userdata input, - .quiqqer-frontendUsers-userdata-address-country-select, - .quiqqer-frontendUsers-userdata-field { - float: left; - width: 70%; - } -} - -.quiqqer-frontendUsers-controls-profile-userdata input[name="street"] { - float: left; - width: 50%; -} - -.quiqqer-frontendUsers-controls-profile-userdata input[name="street_number"] { - float: left; - margin-left: 10px; - width: calc(20% - 10px); -} - -.quiqqer-frontendUsers-controls-profile-userdata select[name="country"] { - display: none; -} - -.quiqqer-frontendUsers-controls-profile-userdata .qui-select { - width: 100%; -} - -/*.quiqqer-frontendUsers-userdata-address-country,*/ -/*.quiqqer-frontendUsers-controls-profile-userdata .qui-select,*/ -/*.quiqqer-frontendUsers-controls-profile-userdata .qui-select .text {*/ -/* height: 40px;*/ -/* line-height: 36px;*/ -/*}*/ - -.quiqqer-frontendUsers-controls-profile-userdata .qui-select .drop-icon { - height: 36px; + display: grid; + grid-template-columns: 2fr 3fr 2fr; + gap: 1rem; } .quiqqer-frontendUsers-userdata-email { - display: block; - width: 100%; -} - -.quiqqer-frontendUsers-userdata-email input { - float: left; - width: calc(100% - 60px); + display: grid; + grid-template-columns: 1fr auto; + gap: 1rem; } .quiqqer-frontendUsers-userdata-email-edit { - border-width: 1px 1px 1px 0; - border-color: #ced9d9; - border-style: solid; - cursor: pointer; - float: left; - line-height: 38px; text-align: center; - width: 60px; -} - -.quiqqer-order-customerData-edit-vatId > select { - width: 100%; - max-width: 100%; -} - -@media (max-width: 767px) { - /*.quiqqer-frontendUsers-userdata-label {*/ - /* display: block;*/ - /* width: 100%;*/ - /* margin-bottom: 0.25rem;*/ - /*}*/ - - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="firstname"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="lastname"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="street_no"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="zip"],*/ - /*.quiqqer-frontendUsers-controls-profile-userdata input[name="city"],*/ - /*.quiqqer-frontendUsers-userdata-address-country-select,*/ - /*.package-intranet-profile-birthday,*/ - /*.quiqqer-frontendUsers-userdata-email,*/ - /*.quiqqer-frontendUsers-userdata-field {*/ - /* display: block;*/ - /* width: 100%;*/ - /*}*/ - - .quiqqer-frontendUsers-userdata-saveButton { - width: 100%; - } + width: 3em; } diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserData.html b/src/QUI/FrontendUsers/Controls/Profile/UserData.html index c2a7646b49b0648a7ab22872dc0214eb15fa4831..88dc4f5d634da90f68e3c43a9fd397af2d959670 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/UserData.html +++ b/src/QUI/FrontendUsers/Controls/Profile/UserData.html @@ -1,6 +1,6 @@ <noscript> <style> - .quiqqer-frontendUsers-userdata-email__hidden { + [data-hidden] { display: inline-block; } </style> @@ -26,10 +26,8 @@ </div> {/if} -<section class="quiqqer-frontendUsers-userdata-address"> - <header> - <h2>{locale group="quiqqer/frontend-users" var="profile.userdata.title"}</h2> - </header> +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userdata-address"> + <h2>{locale group="quiqqer/frontend-users" var="profile.userdata.title"}</h2> {template_event name="quiqqer::frontend-users::profile::customer-data-begin" User=$User Address=$Address} @@ -61,14 +59,16 @@ </span> {if $Address->getAttribute('street_no') === ''} - <input type="text" name="street" value="" autocomplete="street-address"/> - <input type="text" name="street_number" value=""/> + <div class="address-street--2-col"> + <input type="text" name="street" value="" autocomplete="street-address"/> + <input type="text" name="street_number" value=""/> + </div> {else} - <input type="text" - name="street_no" - autocomplete="street-address" - value="{$Address->getAttribute('street_no')|escape:'html'}" - /> + <input type="text" + name="street_no" + autocomplete="street-address" + value="{$Address->getAttribute('street_no')|escape:'html'}" + /> {/if} </label> @@ -123,10 +123,8 @@ {template_event name="quiqqer::frontend-users::profile::user-data-middle" User=$User Address=$Address} -<section class="quiqqer-frontendUsers-userdata-address-additional quiqqer-frontendUsers-userdata-section"> - <header> - <h2>{locale group="quiqqer/frontend-users" var="profile.userdata.additional.title"}</h2> - </header> +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userdata-address-additional"> + <h2>{locale group="quiqqer/frontend-users" var="profile.userdata.additional.title"}</h2> <label> <span class="quiqqer-frontendUsers-userdata-label"> @@ -187,11 +185,15 @@ value="{$User->getAttribute('email')|escape:'html'}" disabled /> - <span class="fa fa-edit quiqqer-frontendUsers-userdata-email-edit"></span> + <button type="button" data-name="email-edit" + class="btn btn-secondary btn-icon quiqqer-frontendUsers-userdata-email-edit" + > + <span class="fa fa-edit"></span> + </button> </div> </label> - <label class="quiqqer-frontendUsers-userdata-email-new quiqqer-frontendUsers-userdata-email__hidden"> + <label class="quiqqer-frontendUsers-userdata-email-new" data-name="email-new" data-hidden> <span class="quiqqer-frontendUsers-userdata-label"> {locale group="quiqqer/frontend-users" var="profile.userdata.email_new"} </span> @@ -204,7 +206,7 @@ {template_event name="quiqqer::frontend-users::profile::user-data-end" User=$User Address=$Address} -<button type="submit" class="quiqqer-frontendUsers-userdata-saveButton"> +<button type="submit" class="btn btn-primary quiqqer-frontendUsers-saveButton"> {locale group="quiqqer/frontend-users" var="profile.userdata.save"} </button> diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserData.php b/src/QUI/FrontendUsers/Controls/Profile/UserData.php index f8f82d7d174143e949fef786f9d915e393caa341..7e8a0b259a11eab043e2233db391a1250618787d 100644 --- a/src/QUI/FrontendUsers/Controls/Profile/UserData.php +++ b/src/QUI/FrontendUsers/Controls/Profile/UserData.php @@ -10,6 +10,9 @@ use QUI; use QUI\FrontendUsers\Handler as FrontendUsersHandler; use QUI\Utils\Security\Orthos; +use QUI\Verification\Enum\VerificationStatus; +use QUI\Verification\Interface\VerificationRepositoryInterface; +use QUI\Verification\VerificationRepository; use function array_filter; use function array_keys; @@ -29,13 +32,22 @@ class UserData extends AbstractProfileControl * UserData constructor. * @param array $attributes */ - public function __construct(array $attributes = []) - { + public function __construct( + array $attributes = [], + private ?VerificationRepositoryInterface $verificationRepository = null + ) { + if (is_null($this->verificationRepository)) { + $this->verificationRepository = new VerificationRepository(); + } + parent::__construct($attributes); $this->addCSSClass('quiqqer-frontendUsers-controls-profile-userdata'); $this->addCSSClass('quiqqer-frontendUsers-controls-profile-control'); - $this->addCSSFile(dirname(__FILE__) . '/UserData.css'); + + if (!defined('QUIQQER_CONTROL_TEMPLATE_USE_BASIC') || QUIQQER_CONTROL_TEMPLATE_USE_BASIC !== true) { + $this->addCSSFile(dirname(__FILE__) . '/UserData.css'); + } $this->setJavaScriptControl('package/quiqqer/frontend-users/bin/frontend/controls/profile/UserData'); } @@ -60,11 +72,13 @@ public function getBody(): string } try { - QUI\Verification\Verifier::getVerificationByIdentifier( - $User->getUUID(), - QUI\FrontendUsers\EmailConfirmVerification::getType(), - true + $verification = $this->verificationRepository->findByIdentifier( + 'confirmemail-' . $User->getUUID() ); + + if (is_null($verification) || $verification->status !== VerificationStatus::PENDING) { + $emailChangeRequested = false; + } } catch (Exception) { $emailChangeRequested = false; } @@ -91,7 +105,7 @@ public function getBody(): string 'showLanguageChangeInProfile' => $Config->getValue('userProfile', 'showLanguageChangeInProfile') ]); - return $Engine->fetch(dirname(__FILE__) . '/UserData.html'); + return $Engine->fetch($this->getTemplateFile()); } /** diff --git a/src/QUI/FrontendUsers/Controls/Profile/UserData_basic.html b/src/QUI/FrontendUsers/Controls/Profile/UserData_basic.html new file mode 100644 index 0000000000000000000000000000000000000000..3935830d75e7a3e921b6e53bea9f993f59aa6103 --- /dev/null +++ b/src/QUI/FrontendUsers/Controls/Profile/UserData_basic.html @@ -0,0 +1,213 @@ +<noscript> + <style> + [data-hidden] { + display: inline-block; + } + </style> +</noscript> + +{template_event name="quiqqer::frontend-users::profile::user-data-begin" User=$User Address=$Address} + +{if $changeMailRequest} +<div class="content-message-information"> + {locale group="quiqqer/frontend-users" var="profile.userdata.message.change_email_request"} +</div> +{/if} + +{if $action == 'change_email_success'} +<div class="content-message-success"> + {locale group="quiqqer/frontend-users" var="profile.userdata.message.change_email_success"} +</div> +{/if} + +{if $action == 'change_email_error'} +<div class="content-message-error"> + {locale group="quiqqer/frontend-users" var="profile.userdata.message.change_email_error"} +</div> +{/if} + +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userdata-address"> + <h2>{locale group="quiqqer/frontend-users" var="profile.userdata.title"}</h2> + + {template_event name="quiqqer::frontend-users::profile::customer-data-begin" User=$User Address=$Address} + + <label class="quiqqer-frontendUsers-userdata-address-firstname"> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="profile.userdata.firstname"} + </span> + <input type="text" + name="firstname" + autocomplete="given-name" + value="{$User->getAttribute('firstname')|escape:'html'}" + /> + </label> + + <label class="quiqqer-frontendUsers-userdata-address-lastname"> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="profile.userdata.lastname"} + </span> + <input type="text" + name="lastname" + autocomplete="family-name" + value="{$User->getAttribute('lastname')|escape:'html'}" + /> + </label> + + <label class="quiqqer-frontendUsers-userdata-address-street"> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="street_no"} + </span> + + {if $Address->getAttribute('street_no') === ''} + <div class="quiqqer-frontendUsers-userdata-address-street-2-col"> + <input type="text" name="street" value="" autocomplete="street-address"/> + <input type="text" name="street_number" value=""/> + </div> + {else} + <input type="text" + name="street_no" + autocomplete="street-address" + value="{$Address->getAttribute('street_no')|escape:'html'}" + /> + {/if} + </label> + + <label class="quiqqer-frontendUsers-userdata-address-zip"> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="zip"} + </span> + <input type="text" + name="zip" + autocomplete="postal-code" + value="{$Address->getAttribute('zip')|escape:'html'}" + /> + </label> + + <label class="quiqqer-frontendUsers-userdata-address-city"> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="city"} + </span> + <input type="text" + name="city" + autocomplete="address-level2" + value="{$Address->getAttribute('city')|escape:'html'}" + /> + </label> + + <label class="quiqqer-frontendUsers-userdata-address-country"> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/system" var="country"} + </span> + <span class="quiqqer-frontendUsers-userdata-address-country-select"> + {control + name="country" + control="QUI\Countries\Controls\Select" + selected=$Address->getAttribute('country') + } + </span> + </label> + + <label class="quiqqer-frontendUsers-userdata-address-phone"> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/system" var="phone"} + </span> + <input type="text" + name="tel" + autocomplete="address-phone" + value="{$Address->getPhone()|escape:'html'}" + /> + </label> + + {template_event name="quiqqer::frontend-users::profile::customer-data-end" User=$User Address=$Address} +</section> + +{template_event name="quiqqer::frontend-users::profile::user-data-middle" User=$User Address=$Address} + +<section class="quiqqer-frontendUsers-profile-section quiqqer-frontendUsers-userdata-additional"> + <h2>{locale group="quiqqer/frontend-users" var="profile.userdata.additional.title"}</h2> + + <label> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="profile.userdata.dateOfBirth"} + </span> + + {assign var=day value=$User->getAttribute('birthday')|date_format:"%d"} + {assign var=month value=$User->getAttribute('birthday')|date_format:"%m"} + {assign var=year value=$User->getAttribute('birthday')|date_format:"%Y"} + + <div class="package-intranet-profile-birthday"> + {birthday type="day" + name="birth_day" + id="intranet-profile-birthday-day" + value=$day + } + + {birthday type="month" + name="birth_month" + id="intranet-profile-birthday-month" + value=$month + } + + {birthday type="year" + name="birth_year" + id="intranet-profile-birthday-year" + value=$year + } + </div> + </label> + + {if $username} + <label> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="profile.userdata.username"} + </span> + <input name="username" value="{$User->getUsername()}"/> + </label> + {/if} + + {if $showLanguageChangeInProfile} + <label> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="profile.userdata.language"} + </span> + <div class="quiqqer-frontendUsers-userdata-language"> + <input data-qui="controls/lang/Select" name="language" value="{$User->getLang()}"/> + </div> + </label> + {/if} + + <label class="qui-form-label"> + <span class="qui-form-label__text"> + {locale group="quiqqer/frontend-users" var="profile.userdata.email"} + </span> + <div class="quiqqer-frontendUsers-userdata-email"> + <input name="email" autocomplete="email" class="qui-form-label__input" + value="{$User->getAttribute('email')|escape:'html'}" + disabled + /> + <button type="button" data-name="email-edit" + class="btn btn-secondary btn-icon quiqqer-frontendUsers-userdata-email-edit" + > + <span class="fa fa-edit"></span> + </button> + </div> + </label> + + <label class="qui-frontendUsers-userdata-email-new" data-name="email-new" data-hidden> + <span class="quiqqer-frontendUsers-userdata-label"> + {locale group="quiqqer/frontend-users" var="profile.userdata.email_new"} + </span> + <input name="emailNew"/> + <span class="quiqqer-frontendUsers-userdata-email-new-hint"> + {locale group="quiqqer/frontend-users" var="profile.userdata.email_new_hint"} + </span> + </label> +</section> + +{template_event name="quiqqer::frontend-users::profile::user-data-end" User=$User Address=$Address} + +<button type="submit" class="quiqqer-frontendUsers-saveButton"> + {locale group="quiqqer/frontend-users" var="profile.userdata.save"} +</button> + +{template_event name="quiqqer::frontend-users::profile::user-data-last" User=$User Address=$Address} diff --git a/src/QUI/FrontendUsers/Controls/Profile_basic.html b/src/QUI/FrontendUsers/Controls/Profile_basic.html new file mode 100644 index 0000000000000000000000000000000000000000..85d8645b7e099df479a27b4daea1e2e715494acb --- /dev/null +++ b/src/QUI/FrontendUsers/Controls/Profile_basic.html @@ -0,0 +1,81 @@ +{if !$Category} + <div class="quiqqer-frontendUsers-controls-profile__noCategoriesInfo"> + <p> + {locale group="quiqqer/frontend-users" var="profile.no_categories_available"} + </p> + </div> +{else} + <div class="quiqqer-frontendUsers-controls-profile__sidebar"> + {if $this->getAttribute('menu')} + <div class="quiqqer-frontendUsers-controls-profile-categories"> + {foreach $categories as $category} + <div class="quiqqer-fupc-category" + data-category="{$category.name}" + data-name="nav-category" + data-open="1" + > + <div class="quiqqer-fupc-category-header" data-name="header"> + <span>{$category.title}</span> + <span class="fa-solid fa-angle-right quiqqer-fupc-category-header-opener" data-name="opener"></span> + </div> + <div class="quiqqer-fu-profile-categories-category-items"> + {foreach $category.items as $setting} + {assign var=active value=false} + {if $currentCategory == $category.name && $currentSetting == $setting.name} + {assign var=active value=true} + {/if} + <a href="{$Site->getUrlRewritten()}/{$category.name}/{$setting.name}" + class="quiqqer-fupc-category-items-item" + {if $active}data-active{/if} + data-name="nav-category-item" + data-setting="{$setting.name}" + > + {if $setting.icon} + <span class="fa-fw quiqqer-fupc-category-items-item-icon {$setting.icon}"></span> + {/if} + <span>{$setting.title}</span> + </a> + {/foreach} + </div> + </div> + {/foreach} + </div> + {/if} + + {* mobile categories *} + <form action="" method="post" class="quiqqer-frontendUsers-controls-profile-categories-mobile"> + <label class="quiqqer-frontendUsers-controls-profile-categories-mobile-label"> + <select name="profile-categories-mobile"> + {foreach $categories as $category} + <optgroup label="{$category.title}"> + {foreach $category.items as $setting} + {assign var=active value=""} + {if $currentCategory == $category.name && $currentSetting == $setting.name} + {assign var=active value="selected"} + {/if} + <option value="{$category.name}:{$setting.name}" {$active} data-test="{$currentCategory} - {$category.name}" > + {$setting.title} + </option> + {/foreach} + </optgroup> + {/foreach} + </select> + </label> + </form> + </div> + + <div class="quiqqer-frontendUsers-controls-profile__content"> + <form class="quiqqer-frontendUsers-controls-profile-categoryContent" + data-name="form" + method="post" + action="" + data-category="{$currentCategory}" + data-setting="{$currentSetting}" + > + <div class="quiqqer-frontendUsers-controls-profile-categoryContentAnimation" data-name="content-animated"> + {$Category->create()} + <input type="hidden" name="profile-save" value="1"> + </div> + </form> + </div> +{/if} \ No newline at end of file diff --git a/src/QUI/FrontendUsers/Controls/Registration.html b/src/QUI/FrontendUsers/Controls/Registration.html index d991fe1265d878d34a782b1e262bd1f4a4a015d0..53c96b0aeeca7deea5bc955ca9e2046e2de586d3 100644 --- a/src/QUI/FrontendUsers/Controls/Registration.html +++ b/src/QUI/FrontendUsers/Controls/Registration.html @@ -25,9 +25,13 @@ {if !$instantRedirect} {$Registrar->getSuccessMessage()} {/if} - {if $redirectUrl} - <div class="quiqqer-frontendUsers-redirect" data-url="{$redirectUrl}" data-instant="{if $instantRedirect}1{else}0{/if}"> - {if !$instantRedirect} + {if $redirectUrl || $instantReload} + <div class="quiqqer-frontendUsers-redirect" + {if !empty($redirectUrl)}data-url="{$redirectUrl}"{/if} + data-instant="{if $instantRedirect}1{else}0{/if}" + data-reload="{if $instantReload}1{else}0{/if}" + > + {if !$instantRedirect && !$instantReload} {locale group="quiqqer/frontend-users" var="control.registration.auto_redirect" url=$redirectUrl} {/if} </div> diff --git a/src/QUI/FrontendUsers/Controls/Registration.php b/src/QUI/FrontendUsers/Controls/Registration.php index db44d25aafdb83d381238baa315c1f51f9a116f5..7b1a1e438132c8042df81ac6933dcc952da368bc 100644 --- a/src/QUI/FrontendUsers/Controls/Registration.php +++ b/src/QUI/FrontendUsers/Controls/Registration.php @@ -129,7 +129,7 @@ public function getBody(): string $Engine->assign( 'error', - QUI::getLocale()->get('quiqqer/frontend-user', 'controls.Registation.general_error') + QUI::getLocale()->get('quiqqer/frontend-users', 'controls.Registation.general_error') ); } } @@ -157,12 +157,16 @@ public function getBody(): string // redirect directives $redirectUrl = false; $instantRedirect = false; + $instantReload = false; if ($success) { + $instantReload = !empty($registrationSettings['reloadOnSuccess']); + if ( - $this->RegisteredUser - && $this->RegisteredUser->isActive() - && $registrationSettings['autoLoginOnActivation'] + !$instantReload && + $this->RegisteredUser && + $this->RegisteredUser->isActive() && + $registrationSettings['autoLoginOnActivation'] ) { // instantly redirect (only used on auto-login) $loginSettings = $RegistrarHandler->getLoginSettings(); @@ -180,7 +184,9 @@ public function getBody(): string } catch (Exception $Exception) { QUI\System\Log::writeException($Exception); } - } elseif (!empty($registrationSettings['autoRedirectOnSuccess'][$projectLang])) { + } + + if (!$instantReload && !$redirectUrl && !empty($registrationSettings['autoRedirectOnSuccess'][$projectLang])) { // show success message and redirect after 10 seconds try { $RedirectSite = QUI\Projects\Site\Utils::getSiteByLink( @@ -333,6 +339,7 @@ public function getBody(): string 'success' => $success, 'redirectUrl' => $redirectUrl, 'instantRedirect' => $instantRedirect, + 'instantReload' => $instantReload, 'Login' => $Login, 'termsOfUseLabel' => $termsOfUseLabel, 'termsOfUseRequired' => $termsOfUseRequired, @@ -551,8 +558,17 @@ public function register() break; case $RegistrarHandler::ACTIVATION_MODE_AUTO: + case $RegistrarHandler::ACTIVATION_MODE_AUTO_WITH_EMAIL_CONFIRM: if (!$NewUser->isActive()) { - $NewUser->activate(false, $SystemUser); + $NewUser->activate('', $SystemUser); + } + + if ($registrarSettings['activationMode'] == $RegistrarHandler::ACTIVATION_MODE_AUTO_WITH_EMAIL_CONFIRM) { + $RegistrarHandler->sendEmailConfirmationMail( + $NewUser, + $NewUser->getAttribute('email'), + $Registrar->getProject() + ); } break; } diff --git a/src/QUI/FrontendUsers/Controls/RegistrationSignUp.html b/src/QUI/FrontendUsers/Controls/RegistrationSignUp.html index cfc8405777b8d3786224fbe55e6be2a274aedb09..79bb67e43252101cf15e2627d2ceef3b297d61e2 100644 --- a/src/QUI/FrontendUsers/Controls/RegistrationSignUp.html +++ b/src/QUI/FrontendUsers/Controls/RegistrationSignUp.html @@ -6,8 +6,8 @@ {if isset($fireUserActivationEvent)} <script> - require(['qui/QUI'], function(QUI) { - (function() { + require(['qui/QUI'], function (QUI) { + (function () { QUI.fireEvent('quiqqerFrontendUsersUserActivate', [ '{$User->getUUID()}', '{$registrarHash}', @@ -23,64 +23,62 @@ style="display: none" > {if $showLoggedInWarning} - <div class="quiqqer-fu-registrationSignUp-registration-logged-in message-information"> - {locale group="quiqqer/frontend-users" var="message.types.registration.already_registered"} - </div> + <div class="quiqqer-fu-registrationSignUp-registration-logged-in message-information"> + {locale group="quiqqer/frontend-users" var="message.types.registration.already_registered"} + </div> {else} - {if $msgSuccess} - <div class="content-message-success"> - {$msgSuccess} - {if $redirect} - <span data-redirecturl="{$redirect}" class="quiqqer-fu-registrationSignUp-registration-redirect"> + {if $msgSuccess} + <div class="content-message-success"> + {$msgSuccess} + {if $redirect} + <span data-redirecturl="{$redirect}" class="quiqqer-fu-registrationSignUp-registration-redirect"> {locale group="quiqqer/frontend-users" var="RegistrationSignUp.message.redirect" url=$redirect} </span> - {elseif $nextLinksText} - <p class="quiqqer-fu-registrationSignUp-registration-nextlinks"> - {$nextLinksText} - </p> - {/if} - </div> - {elseif $msgError} - <div class="content-message-error"> - {$msgError} - </div> - {else} - {if $this->getAttribute('header')} - <h2>{locale group="quiqqer/frontend-users" var="control.registration.sign.up.title"}</h2> - {/if} - <div class="quiqqer-fu-registrationSignUp-registration-social"> - {foreach $Registrars as $Registrar} - <form action="" - method="POST" - class="quiqqer-fu-registrationSignUp-registration-social-entry {$Registrar->getAttribute('icon-css-class')}" - data-registrar="{$Registrar->getHash()}" - data-registration_id="{$registrationId}" - > - {$this->getRegistrarIcon($Registrar)} - <input type="hidden" name="registrar" value="{$Registrar->getHash()}"/> - <input type="hidden" name="registration" value="1"/> - <input type="hidden" name="registration_id" value="{$registrationId}"/> - </form> - {/foreach} - </div> - - {if !$msgSuccess && $Email} - {if $Registrars->count()} - {locale group="quiqqer/frontend-users" var="control.registration.sign.up.message.between"} - {/if} - - <form name="quiqqer-fu-registrationSignUp-email" - class="quiqqer-fu-registrationSignUp-registration-email" - data-registrar="{$Email->getHash()}" - data-registration_id="{$registrationId}" - > - <section class="quiqqer-fu-registrationSignUp-email-mailSection"> - <label> + {elseif $nextLinksText} + <p class="quiqqer-fu-registrationSignUp-registration-nextlinks"> + {$nextLinksText} + </p> + {/if} + </div> + {elseif $msgError} + <div class="content-message-error"> + {$msgError} + </div> + {else} + {if $this->getAttribute('header')} + <h2>{locale group="quiqqer/frontend-users" var="control.registration.sign.up.title"}</h2> + {/if} + <div class="quiqqer-fu-registrationSignUp-registration-social"> + {foreach $Registrars as $Registrar} + <form action="" + method="POST" + class="quiqqer-fu-registrationSignUp-registration-social-entry {$Registrar->getAttribute('icon-css-class')}" + data-registrar="{$Registrar->getHash()}" + data-registration_id="{$registrationId}" + > + {$this->getRegistrarIcon($Registrar)} + <input type="hidden" name="registrar" value="{$Registrar->getHash()}"/> + <input type="hidden" name="registration" value="1"/> + <input type="hidden" name="registration_id" value="{$registrationId}"/> + </form> + {/foreach} + </div> + {if !$msgSuccess && $Email} + {if $Registrars->count()} + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.message.between"} + {/if} + <form name="quiqqer-fu-registrationSignUp-email" + class="quiqqer-fu-registrationSignUp-registration-email" + data-registrar="{$Email->getHash()}" + data-registration_id="{$registrationId}" + > + <section class="quiqqer-fu-registrationSignUp-email-mailSection"> + <label> <span class="label"> {locale group="quiqqer/frontend-users" var="control.registration.sign.up.email.title"} </span> - <span class="field"> + <span class="field"> <span class="icon fa fa-envelope"></span> <input type="email" name="email" @@ -89,106 +87,109 @@ {if $this->getAttribute('autocomplete')}autocomplete="email"{else}autocomplete="false"{/if} /> </span> - </label> - - {if $useCaptcha && $isCaptchaInvisible} - <div class="visible"> - {$captchaHTML} - </div> - {/if} - - <div class="quiqqer-fu-registrationSignUp-email-buttons"> - {if $RegistrationTrial && $this->getAttribute('registration-trial')} - <button type="submit" name="trial-account"> - {locale group="quiqqer/frontend-users" var="control.registration.sign.up.try"} - </button> - <input type="hidden" - name="registration-trial-registrator" - value="{$RegistrationTrial->getHash()}" - /> + </label> + + {if $useCaptcha && $isCaptchaInvisible} + <div class="visible"> + {$captchaHTML} + </div> + {/if} + + <div class="quiqqer-fu-registrationSignUp-email-buttons"> + {if $RegistrationTrial && $this->getAttribute('registration-trial')} + <button type="submit" name="trial-account"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.try"} + </button> + <input type="hidden" + name="registration-trial-registrator" + value="{$RegistrationTrial->getHash()}" + /> + {/if} + <button type="submit" name="email-next"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.next"} + </button> + </div> + </section> + + {if $useCaptcha && $isCaptchaInvisible === false} + {$captchaHTML} {/if} - <button type="submit" name="email-next"> - {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.next"} - </button> - </div> - </section> - {if $useCaptcha && $isCaptchaInvisible === false} - {$captchaHTML} - {/if} - - {if $fullnameInput !== 'none'} - <section class="quiqqer-fu-registrationSignUp-email-fullnameSection"> - <label> + {if $fullnameInput !== 'none'} + <section class="quiqqer-fu-registrationSignUp-email-fullnameSection"> + <label> <span class="label"> {locale group="quiqqer/frontend-users" var="control.registration.sign.up.firstname.title"} </span> - <span class="field"> + <span class="field"> <span class="icon fa fa-user"></span> - <input type="text" name="firstname"{if $fullnameInput === 'firstname_required' || $fullnameInput === 'fullname_required'} data-required="1"{/if}/> + <input type="text" + name="firstname"{if $fullnameInput === 'firstname_required' || $fullnameInput === 'fullname_required'} data-required="1"{/if}/> </span> - </label> + </label> - {if $fullnameInput === 'fullname_optional' || fullname_optional === 'fullname_required'} - <label> + {if $fullnameInput === 'fullname_optional' || fullname_optional === 'fullname_required'} + <label> <span class="label"> {locale group="quiqqer/frontend-users" var="control.registration.sign.up.lastname.title"} </span> - <span class="field"> + <span class="field"> <span class="icon fa fa-user"></span> - <input type="text" name="lastname"{if $fullnameInput === 'fullname_required'} data-required="1"{/if}/> + <input type="text" + name="lastname"{if $fullnameInput === 'fullname_required'} data-required="1"{/if}/> </span> - </label> - {/if} - - <div class="quiqqer-fu-registrationSignUp-email-buttons"> - <button type="submit" name="fullname-next"> - {locale group="quiqqer/frontend-users" var="control.registration.sign.up.fullname.next"} - </button> - </div> - </section> - {/if} + </label> + {/if} + + <div class="quiqqer-fu-registrationSignUp-email-buttons"> + <button type="submit" name="fullname-next"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.fullname.next"} + </button> + </div> + </section> + {/if} - {if $passwordInput !== 'none'} - <section class="quiqqer-fu-registrationSignUp-email-passwordSection"> - <label> + {if $passwordInput !== 'none'} + <section class="quiqqer-fu-registrationSignUp-email-passwordSection"> + <label> <span class="label"> {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.title"} </span> - <span class="field"> + <span class="field"> <span class="icon fa fa-key"></span> <input type="password" name="password" required autocomplete="off"/> </span> - </label> - - <div class="quiqqer-fu-registrationSignUp-email-buttons"> - <button type="submit" name="create-account"> - {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.next"} - </button> - </div> - </section> + </label> + + <div class="quiqqer-fu-registrationSignUp-email-buttons"> + <button type="submit" name="create-account"> + {locale group="quiqqer/frontend-users" var="control.registration.sign.up.password.next"} + </button> + </div> + </section> + {/if} + </form> {/if} - </form> - {/if} - - <div class="quiqqer-fu-registrationSignUp-terms"> + <div class="quiqqer-fu-registrationSignUp-terms"> <span class="quiqqer-fu-registrationSignUp-terms-text"> {$termsPrivacyMessage} + {template_event name="quiqqer::frontend-users::RegistrationSignUp::termsAfter"} + <button name="decline" title="{locale group='quiqqer/frontend-users' var='control.registration.sign.up.button.terms.decline'}" > <span class="fa fa-close"></span> </button> </span> - <div class="quiqqer-fu-registrationSignUp-terms-buttons"></div> - </div> - {/if} + <div class="quiqqer-fu-registrationSignUp-terms-buttons"></div> + </div> + {/if} {/if} </section> {if !empty($this->getAttribute('content')) && $showContent && !$msgSuccess && !$msgError} -<section class="quiqqer-fu-registrationSignUp-info" style="display: none"> - {$this->getAttribute('content')} -</section> + <section class="quiqqer-fu-registrationSignUp-info" style="display: none"> + {$this->getAttribute('content')} + </section> {/if} diff --git a/src/QUI/FrontendUsers/Controls/RegistrationSignUp.php b/src/QUI/FrontendUsers/Controls/RegistrationSignUp.php index bb8ebe1cfa15abfda06c5670e16365e8849240f9..9c8cdda44994c2f42263408da90db2d6fcf9fb64 100644 --- a/src/QUI/FrontendUsers/Controls/RegistrationSignUp.php +++ b/src/QUI/FrontendUsers/Controls/RegistrationSignUp.php @@ -415,8 +415,10 @@ protected function siteTermsPrivacy(QUI\Interfaces\Template\EngineInterface $Eng 'control.sign.up.terms_of_use_and_privacy_policy.label', [ 'termsOfUseUrl' => $SiteTerms->getUrlRewritten(), + 'termsOfUseSiteId' => $SiteTerms->getId(), 'termsOfUseSiteTitle' => $SiteTerms->getAttribute('title'), 'privacyPolicyUrl' => $SitePrivacy->getUrlRewritten(), + 'privacyPolicySiteId' => $SitePrivacy->getId(), 'privacyPolicySiteTitle' => $SitePrivacy->getAttribute('title') ] ); @@ -426,6 +428,7 @@ protected function siteTermsPrivacy(QUI\Interfaces\Template\EngineInterface $Eng 'control.sign.up.terms_of_use.label', [ 'termsOfUseUrl' => $SiteTerms->getUrlRewritten(), + 'termsOfUseSiteId' => $SiteTerms->getId(), 'termsOfUseSiteTitle' => $SiteTerms->getAttribute('title') ] ); @@ -435,6 +438,7 @@ protected function siteTermsPrivacy(QUI\Interfaces\Template\EngineInterface $Eng 'control.sign.up.privacy_policy.label', [ 'privacyPolicyUrl' => $SitePrivacy->getUrlRewritten(), + 'privacyPolicySiteId' => $SitePrivacy->getId(), 'privacyPolicySiteTitle' => $SitePrivacy->getAttribute('title') ] ); diff --git a/src/QUI/FrontendUsers/Controls/UserIcon.php b/src/QUI/FrontendUsers/Controls/UserIcon.php index 15646984d3bf9ead8f1cb15b3c20cc1cb5482620..a2813b232cf4a2f4fbeb219a34b475ef2ebe40da 100644 --- a/src/QUI/FrontendUsers/Controls/UserIcon.php +++ b/src/QUI/FrontendUsers/Controls/UserIcon.php @@ -81,7 +81,7 @@ public function getBody(): string $AvatarImage = new ExternalImage(Utils::getGravatarUrl($userEmail, $iconHeight)); $Engine->assign([ - 'avatarImageUrl' => $AvatarImage->getSizeCacheUrl($iconWidth, $iconHeight) + 'avatarImageUrl' => $AvatarImage->getSizeCacheUrl() ]); } else { $avatarMediaUrl = $User->getAttribute('avatar'); @@ -133,7 +133,7 @@ public function getBody(): string * * @return false|QUI\Projects\Media\Image|string */ - protected function getDefaultAvatarImage(): QUI\Projects\Media\Image|bool|string + protected function getDefaultAvatarImage(): QUI\Projects\Media\Image | bool | string { try { $Conf = QUI::getPackage('quiqqer/frontend-users')->getConfig(); diff --git a/src/QUI/FrontendUsers/EmailConfirmVerification.php b/src/QUI/FrontendUsers/EmailConfirmLinkVerification.php similarity index 55% rename from src/QUI/FrontendUsers/EmailConfirmVerification.php rename to src/QUI/FrontendUsers/EmailConfirmLinkVerification.php index 9ddf5af0fcdcf54f816a344ef4dc9bd1a4033e86..ef085a23ca31a7d72a5ae583211501c961843e51 100644 --- a/src/QUI/FrontendUsers/EmailConfirmVerification.php +++ b/src/QUI/FrontendUsers/EmailConfirmLinkVerification.php @@ -4,7 +4,9 @@ use QUI; use QUI\Exception; -use QUI\Verification\AbstractVerification; +use QUI\Verification\Entity\LinkVerification; +use QUI\Verification\Enum\VerificationErrorReason; +use QUI\Verification\Entity\AbstractVerification; /** * Class EmailConfirmVerification @@ -13,16 +15,17 @@ * * @package QUI\FrontendUsers */ -class EmailConfirmVerification extends AbstractVerification +class EmailConfirmLinkVerification extends AbstractFrontendUsersLinkVerificationHandler { /** * Get the duration of a Verification (minutes) * + * @param AbstractVerification $verification * @return int - duration in minutes; * if this method returns false use the module setting default value * @throws Exception */ - public function getValidDuration(): int + public function getValidDuration(AbstractVerification $verification): int { $settings = Handler::getInstance()->getMailSettings(); return (int)$settings['verificationValidityDuration']; @@ -31,21 +34,21 @@ public function getValidDuration(): int /** * Execute this method on successful verification * + * @param LinkVerification $verification * @return void */ - public function onSuccess(): void + public function onSuccess(LinkVerification $verification): void { - $userId = $this->getIdentifier(); - try { $RegistrarHandler = QUI\FrontendUsers\Handler::getInstance(); - $User = QUI::getUsers()->get($userId); - $newEmail = $this->additionalData['newEmail']; + $userUuid = $verification->getCustomDataEntry('uuid'); + $User = QUI::getUsers()->get($userUuid); + $newEmail = $verification->getCustomDataEntry('newEmail'); + $oldEmail = $User->getAttribute('email'); // if users cannot set their own username -> change username as well // if it equals the old email-address if (!$RegistrarHandler->isUsernameInputAllowed()) { - $oldEmail = $User->getAttribute('email'); $username = $User->getUsername(); if ($oldEmail === $username) { @@ -55,9 +58,11 @@ public function onSuccess(): void $User->setAttribute('email', $newEmail); $User->save(QUI::getUsers()->getSystemUser()); + + QUI::getEvents()->fireEvent('quiqqerFrontendUsersEmailChanged', [$User, $oldEmail, $newEmail]); } catch (\Exception $Exception) { QUI\System\Log::addError( - self::class . ' :: onSuccess -> Could not find user #' . $userId + self::class . ' :: onSuccess -> Could not find user #' . $userUuid ); QUI\System\Log::writeException($Exception); @@ -67,9 +72,11 @@ public function onSuccess(): void /** * Execute this method on unsuccessful verification * + * @param LinkVerification $verification + * @param VerificationErrorReason $reason * @return void */ - public function onError(): void + public function onError(LinkVerification $verification, VerificationErrorReason $reason): void { // nothing } @@ -77,9 +84,10 @@ public function onError(): void /** * This message is displayed to the user on successful verification * + * @param LinkVerification $verification * @return string */ - public function getSuccessMessage(): string + public function getSuccessMessage(LinkVerification $verification): string { return ''; } @@ -87,10 +95,11 @@ public function getSuccessMessage(): string /** * This message is displayed to the user on unsuccessful verification * - * @param string $reason - The reason for the error (see \QUI\Verification\Verifier::REASON_) + * @param LinkVerification $verification + * @param VerificationErrorReason $reason * @return string */ - public function getErrorMessage(string $reason): string + public function getErrorMessage(LinkVerification $verification, VerificationErrorReason $reason): string { return ''; } @@ -98,17 +107,22 @@ public function getErrorMessage(string $reason): string /** * Automatically redirect the user to this URL on successful verification * - * @return string|false - If this method returns false, no redirection takes place + * @param LinkVerification $verification + * @return string|null - If this method returns false, no redirection takes place * @throws Exception */ - public function getOnSuccessRedirectUrl(): bool|string + public function getOnSuccessRedirectUrl(LinkVerification $verification): ?string { - $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite( - $this->getProject() - ); + $project = $this->getProject($verification); + + if (!$project) { + return null; + } + + $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite($project); if (!$RegistrationSite) { - return false; + return null; } return $RegistrationSite->getUrlRewritten([], [ @@ -119,37 +133,23 @@ public function getOnSuccessRedirectUrl(): bool|string /** * Automatically redirect the user to this URL on unsuccessful verification * - * @return string|false - If this method returns false, no redirection takes place + * @param LinkVerification $verification + * @param VerificationErrorReason $reason + * @return string|null - If this method returns false, no redirection takes place * @throws Exception */ - public function getOnErrorRedirectUrl(): bool|string + public function getOnErrorRedirectUrl(LinkVerification $verification, VerificationErrorReason $reason): ?string { - $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite( - $this->getProject() - ); + $project = $this->getProject($verification); + + $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite($project); if (!$RegistrationSite) { - return false; + return null; } return $RegistrationSite->getUrlRewritten([], [ 'error' => 'emailconfirm' ]); } - - /** - * Get the Project this Verification is intended for - * - * @return QUI\Projects\Project - * @throws Exception - */ - protected function getProject(): QUI\Projects\Project - { - $additionalData = $this->getAdditionalData(); - - return QUI::getProjectManager()->getProject( - $additionalData['project'], - $additionalData['projectLang'] - ); - } } diff --git a/src/QUI/FrontendUsers/EmailVerification.php b/src/QUI/FrontendUsers/EmailVerification.php new file mode 100644 index 0000000000000000000000000000000000000000..3c0d1d6c4bbce3e7fc2081d6e0cd2fd868be29f1 --- /dev/null +++ b/src/QUI/FrontendUsers/EmailVerification.php @@ -0,0 +1,134 @@ +<?php + +namespace QUI\FrontendUsers; + +use QUI; +use QUI\Exception; +use QUI\Verification\Entity\LinkVerification; +use QUI\Verification\Enum\VerificationErrorReason; +use QUI\Verification\Entity\AbstractVerification; + +/** + * User verification to confirm an e-mail-address + */ +class EmailVerification extends AbstractFrontendUsersLinkVerificationHandler +{ + /** + * Get the duration of a Verification (minutes) + * + * @param AbstractVerification $verification + * @return int - duration in minutes; + * if this method returns false use the module setting default value + * @throws Exception + */ + public function getValidDuration(AbstractVerification $verification): int + { + $settings = Handler::getInstance()->getMailSettings(); + return (int)$settings['verificationValidityDuration']; + } + + /** + * Execute this method on successful verification + * + * @param LinkVerification $verification + * @return void + * @throws \Exception + */ + public function onSuccess(LinkVerification $verification): void + { + try { + $User = QUI::getUsers()->get($verification->getCustomDataEntry('uuid')); + $email = $verification->getCustomDataEntry('email'); + + // Set primary email as verified + if ($email === $User->getAttribute('email')) { + Utils::setDefaultUserEmailVerified($User); + } + + Utils::setEmailAddressAsVerfifiedForUser($email, $User); + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + throw $Exception; + } + } + + /** + * Execute this method on unsuccessful verification + * + * @param LinkVerification $verification + * @param VerificationErrorReason $reason + * @return void + */ + public function onError(LinkVerification $verification, VerificationErrorReason $reason): void + { + // nothing + } + + /** + * This message is displayed to the user on successful verification + * + * @param LinkVerification $verification + * @return string + */ + public function getSuccessMessage(LinkVerification $verification): string + { + return ''; + } + + /** + * This message is displayed to the user on unsuccessful verification + * + * @param LinkVerification $verification + * @param VerificationErrorReason $reason + * @return string + */ + public function getErrorMessage(LinkVerification $verification, VerificationErrorReason $reason): string + { + return ''; + } + + /** + * Automatically redirect the user to this URL on successful verification + * + * @param LinkVerification $verification + * @return string|null - If this method returns false, no redirection takes place + * @throws Exception + */ + public function getOnSuccessRedirectUrl(LinkVerification $verification): ?string + { + $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite( + $this->getProject($verification) + ); + + if (!$RegistrationSite) { + return null; + } + + return $RegistrationSite->getUrlRewritten([], [ + 'success' => 'emailconfirm' + ]); + } + + /** + * Automatically redirect the user to this URL on unsuccessful verification + * + * @param LinkVerification $verification + * @param VerificationErrorReason $reason + * @return string|null - If this method returns false, no redirection takes place + * @throws Exception + */ + public function getOnErrorRedirectUrl(LinkVerification $verification, VerificationErrorReason $reason): ?string + { + $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite( + $this->getProject($verification) + ); + + if (!$RegistrationSite) { + return null; + } + + return $RegistrationSite->getUrlRewritten([], [ + 'error' => 'emailconfirm' + ]); + } +} diff --git a/src/QUI/FrontendUsers/ErpProvider.php b/src/QUI/FrontendUsers/ErpProvider.php index 21da7a92dff456be401619ebdb190e06f0fe5cba..fce15d2ff5ded07ffc5920ef647c938ee301c249 100644 --- a/src/QUI/FrontendUsers/ErpProvider.php +++ b/src/QUI/FrontendUsers/ErpProvider.php @@ -54,6 +54,23 @@ public static function getMailLocale(): array 'content.description' => ['quiqqer/frontend-users', 'mail.registration_activation.body.description'] ], + [ + 'title' => QUI::getLocale()->get( + 'quiqqer/frontend-users', + 'mail.text.confirmEmail.title' + ), + 'description' => QUI::getLocale()->get( + 'quiqqer/frontend-users', + 'mail.text.confirmEmail.description' + ), + 'subject' => ['quiqqer/frontend-users', 'mail.confirm_email_address.subject'], + 'content' => ['quiqqer/frontend-users', 'mail.confirm_email_address.body'], + + 'subject.description' => ['quiqqer/frontend-users', 'mail.confirm_email_address.subject.description'], + 'content.description' => ['quiqqer/frontend-users', 'mail.confirm_email_address.body.description'] + ], + + [ 'title' => QUI::getLocale()->get( 'quiqqer/frontend-users', diff --git a/src/QUI/FrontendUsers/Events.php b/src/QUI/FrontendUsers/Events.php index a5c0d482e37f8453f0db23953456ce0ea9f5b206..2e695ccad0f09ec9160eb90498eb772dd7f49e1b 100644 --- a/src/QUI/FrontendUsers/Events.php +++ b/src/QUI/FrontendUsers/Events.php @@ -5,8 +5,8 @@ use QUI; use QUI\Exception; use QUI\Interfaces\Users\User; -use QUI\Verification\Verifier; use QUI\Smarty\Collector; +use QUI\Verification\VerificationRepository; use function base64_encode; use function json_encode; @@ -62,8 +62,9 @@ public static function onSiteSave(QUI\Interfaces\Projects\Site $Site): void { // register path if ( - $Site->getAttribute('active') && - $Site->getAttribute('type') == 'quiqqer/frontend-users:types/profile' + $Site->getAttribute('active') + && $Site->getAttribute('type') == 'quiqqer/frontend-users:types/profile' + && method_exists($Site, 'getLocation') ) { $url = $Site->getLocation(); $url = str_replace(QUI\Rewrite::URL_DEFAULT_SUFFIX, '', $url); @@ -196,7 +197,7 @@ public static function autoLogin(QUI\Interfaces\Users\User $User, bool $checkEli 'user_agent' => $useragent, 'secHash' => $secHash ], - ['id' => $User->getUUID()] + ['uuid' => $User->getUUID()] ); QUI::getEvents()->fireEvent( @@ -234,13 +235,16 @@ public static function onUserDelete(User $User): void { // delete Verification for user (if not yet deleted by quiqqer/verification cron) try { - $Verification = Verifier::getVerificationByIdentifier( - $User->getUUID(), - ActivationVerification::getType() + $repository = new VerificationRepository(); + $verification = $repository->findByIdentifier( + 'activate-' . $User->getUUID() ); - Verifier::removeVerification($Verification); - } catch (\Exception) { + // if Verification not found it does not have to be deleted + if ($verification) { + $repository->delete($verification); + } + } catch (\Throwable) { // nothing -> if Verification not found it does not have to be deleted } } diff --git a/src/QUI/FrontendUsers/Exception/EmailAddressNotVerifiableException.php b/src/QUI/FrontendUsers/Exception/EmailAddressNotVerifiableException.php new file mode 100644 index 0000000000000000000000000000000000000000..f057515c7fa9e4d91bcf3ac666db248a6d0c3d93 --- /dev/null +++ b/src/QUI/FrontendUsers/Exception/EmailAddressNotVerifiableException.php @@ -0,0 +1,10 @@ +<?php + +namespace QUI\FrontendUsers\Exception; + +use QUI\FrontendUsers\Exception; + +class EmailAddressNotVerifiableException extends Exception +{ + protected $code = 50002; +} diff --git a/src/QUI/FrontendUsers/Handler.php b/src/QUI/FrontendUsers/Handler.php index 114793fdebcdaf03987097fb1e4f29da624d3d62..036f8d2849ed015b5e989a2e94cc94892e643038 100644 --- a/src/QUI/FrontendUsers/Handler.php +++ b/src/QUI/FrontendUsers/Handler.php @@ -1,17 +1,16 @@ <?php -/** - * This file contains QUI\FrontendUsers\Handler - */ - namespace QUI\FrontendUsers; use QUI; +use QUI\Interfaces\Users\User as QUIUserInterface; use QUI\Mail\Mailer; use QUI\Utils\Singleton; -use QUI\Verification\Verifier; +use QUI\Verification\Interface\VerificationFactoryInterface; +use QUI\Verification\VerificationFactory; use function array_filter; +use function time; /** * Class Registration Handling @@ -33,6 +32,7 @@ class Handler extends Singleton */ const ACTIVATION_MODE_MAIL = 'mail'; const ACTIVATION_MODE_AUTO = 'auto'; + const ACTIVATION_MODE_AUTO_WITH_EMAIL_CONFIRM = 'autoWithEmailConfirm'; const ACTIVATION_MODE_MANUAL = 'manual'; /** @@ -75,6 +75,7 @@ class Handler extends Singleton const USER_ATTR_REGISTRAR = 'quiqqer.frontendUsers.registrar'; const USER_ATTR_ACTIVATION_LOGIN_EXECUTED = 'quiqqer.frontendUsers.activationLoginExecuted'; const USER_ATTR_EMAIL_VERIFIED = 'quiqqer.frontendUsers.emailVerified'; + const USER_ATTR_EMAIL_ADDRESSES_VERIFIED = 'quiqqer.frontendUsers.emailAddressesVerified'; const USER_ATTR_USER_ACTIVATION_REQUIRED = 'quiqqer.frontendUsers.userActivationRequired'; /** @@ -97,8 +98,13 @@ class Handler extends Singleton /** * Handler constructor. */ - public function __construct() - { + public function __construct( + private ?VerificationFactoryInterface $verificationFactory = null + ) { + if (is_null($this->verificationFactory)) { + $this->verificationFactory = new VerificationFactory(); + } + $this->Registrar = new RegistrarCollection(); } @@ -135,7 +141,7 @@ public function getRegistrars(): RegistrarCollection * @param string $registrar - Registrar type * @return false|RegistrarInterface */ - public function getRegistrar(string $registrar): bool|RegistrarInterface + public function getRegistrar(string $registrar): bool | RegistrarInterface { /** @var RegistrarInterface $Registrar */ foreach ($this->getAvailableRegistrars() as $Registrar) { @@ -153,7 +159,7 @@ public function getRegistrar(string $registrar): bool|RegistrarInterface * @param QUI\Interfaces\Users\User $User * @return RegistrarInterface|false */ - public function getRegistrarByUser(QUI\Interfaces\Users\User $User): bool|RegistrarInterface + public function getRegistrarByUser(QUI\Interfaces\Users\User $User): bool | RegistrarInterface { $registrar = $User->getAttribute(self::USER_ATTR_REGISTRAR); @@ -170,7 +176,7 @@ public function getRegistrarByUser(QUI\Interfaces\Users\User $User): bool|Regist * @param string $hash * @return false|RegistrarInterface */ - public function getRegistrarByHash(string $hash): bool|RegistrarInterface + public function getRegistrarByHash(string $hash): bool | RegistrarInterface { /** @var RegistrarInterface $Registrar */ foreach ($this->getAvailableRegistrars() as $Registrar) { @@ -341,10 +347,10 @@ public function getMailSettings(): array /** * Get settings for one or all Registrars * - * @param string|null $registrarClass (optional) - Registar class path (namespace) + * @param string|null $registrarClass (optional) - Registrar class path (namespace) * @return array */ - public function getRegistrarSettings(string $registrarClass = null): array + public function getRegistrarSettings(null | string $registrarClass = null): array { try { $Conf = QUI::getPackage('quiqqer/frontend-users')->getConfig(); @@ -412,13 +418,17 @@ public function sendActivationMail(QUI\Interfaces\Users\User $User, RegistrarInt { $Project = $Registrar->getProject(); - $ActivationVerification = new ActivationVerification($User->getUUID(), [ - 'project' => $Project->getName(), - 'projectLang' => $Project->getLang(), - 'registrar' => $Registrar->getHash() - ]); - - $activationLink = Verifier::startVerification($ActivationVerification, true); + $verification = $this->verificationFactory->createLinkVerification( + 'activate-' . $User->getUUID(), + new ActivationLinkVerification(), + [ + 'uuid' => $User->getUUID(), + 'project' => $Project->getName(), + 'projectLang' => $Project->getLang(), + 'registrar' => $Registrar->getHash() + ], + true + ); $L = QUI::getLocale(); $lg = 'quiqqer/frontend-users'; @@ -445,7 +455,7 @@ public function sendActivationMail(QUI\Interfaces\Users\User $User, RegistrarInt 'userLastName' => $User->getAttribute('lastname') ?: '', 'email' => $User->getAttribute('email'), 'date' => $L->formatDate(time()), - 'activationLink' => $activationLink + 'activationLink' => $verification->getVerificationUrl() ]) ] ); @@ -474,7 +484,7 @@ public function sendActivationMail(QUI\Interfaces\Users\User $User, RegistrarInt public function sendWelcomeMail( QUI\Interfaces\Users\User $User, QUI\Projects\Project $Project, - string $userPassword = null + null | string $userPassword = null ): void { $email = $User->getAttribute('email'); @@ -590,10 +600,10 @@ public function sendRegistrationNotice(QUI\Interfaces\Users\User $User, QUI\Proj } /** - * Send activtion mail for a user account + * Send deactivation mail for a user account * - * @param QUI\Users\User $User - * @param string $newEmail - New E-Mail-Adress + * @param QUI\Interfaces\Users\User $User + * @param string $newEmail - New E-Mail-Address * @param QUI\Projects\Project $Project - The QUIQQER Project where the change action took place * @return void * @throws QUI\Exception @@ -603,13 +613,17 @@ public function sendChangeEmailAddressMail( string $newEmail, QUI\Projects\Project $Project ): void { - $EmailConfirmVerification = new EmailConfirmVerification($User->getUUID(), [ - 'project' => $Project->getName(), - 'projectLang' => $Project->getLang(), - 'newEmail' => $newEmail - ]); - - $confirmLink = Verifier::startVerification($EmailConfirmVerification, true); + $verification = $this->verificationFactory->createLinkVerification( + 'confirmemail-' . $User->getUUID(), + new EmailConfirmLinkVerification(), + [ + 'uuid' => $User->getUUID(), + 'project' => $Project->getName(), + 'projectLang' => $Project->getLang(), + 'newEmail' => $newEmail + ], + true + ); $L = QUI::getLocale(); $lg = 'quiqqer/frontend-users'; @@ -632,9 +646,71 @@ public function sendChangeEmailAddressMail( 'username' => $User->getUsername(), 'userFirstName' => $User->getAttribute('firstname') ?: '', 'userLastName' => $User->getAttribute('lastname') ?: '', - 'newEmail' => $newEmail, + 'email' => $newEmail, + 'date' => $L->formatDate(time()), + 'confirmLink' => $verification->getVerificationUrl() + ]) + ] + ); + } catch (\Exception $Exception) { + QUI\System\Log::addError( + self::class . ' :: sendChangeEmailAddressMail -> Send mail failed' + ); + + QUI\System\Log::writeException($Exception); + } + } + + /** + * Send email to confirm an email address. + * + * @param QUIUserInterface $User + * @param string $email - New E-Mail-Adress + * @param QUI\Projects\Project $Project - The QUIQQER Project where the change action took place + * @return void + * + * @throws QUI\Exception + */ + public function sendEmailConfirmationMail( + QUIUserInterface $User, + string $email, + QUI\Projects\Project $Project + ): void { + $verification = $this->verificationFactory->createLinkVerification( + 'confirmemail-' . $User->getUUID(), + new EmailVerification(), + [ + 'uuid' => $User->getUUID(), + 'project' => $Project->getName(), + 'projectLang' => $Project->getLang(), + 'email' => $email + ] + ); + + $L = QUI::getLocale(); + $lg = 'quiqqer/frontend-users'; + $tplDir = QUI::getPackage('quiqqer/frontend-users')->getDir() . 'templates/'; + $host = $Project->getVHost(); + + try { + $this->sendMail( + [ + 'subject' => $L->get($lg, 'mail.confirm_email_address.subject') + ], + [ + $email + ], + $tplDir . 'mail.confirm_email_address.html', + [ + 'body' => $L->get($lg, 'mail.confirm_email_address.body', [ + 'host' => $host, + 'userId' => $User->getUUID(), + 'username' => $User->getUsername(), + 'userFirstName' => $User->getAttribute('firstname') ?: '', + 'userLastName' => $User->getAttribute('lastname') ?: '', + 'email' => $email, 'date' => $L->formatDate(time()), - 'confirmLink' => $confirmLink + 'confirmLink' => $verification->getVerificationUrl() ]) ] ); @@ -659,12 +735,15 @@ public function sendChangeEmailAddressMail( */ public function sendDeleteUserConfirmationMail(QUI\Interfaces\Users\User $User, QUI\Projects\Project $Project): void { - $DeleteUserVerification = new UserDeleteConfirmVerification($User->getUUID(), [ - 'project' => $Project->getName(), - 'projectLang' => $Project->getLang() - ]); - - $confirmLink = Verifier::startVerification($DeleteUserVerification, true); + $verification = $this->verificationFactory->createLinkVerification( + 'confirmdelete-' . $User->getUUID(), + new UserDeleteConfirmLinkVerification(), + [ + 'uuid' => $User->getUUID(), + 'project' => $Project->getName(), + 'projectLang' => $Project->getLang() + ] + ); $L = QUI::getLocale(); $lg = 'quiqqer/frontend-users'; @@ -688,7 +767,7 @@ public function sendDeleteUserConfirmationMail(QUI\Interfaces\Users\User $User, 'userFirstName' => $User->getAttribute('firstname') ?: '', 'userLastName' => $User->getAttribute('lastname') ?: '', 'date' => $L->formatDate(time()), - 'confirmLink' => $confirmLink + 'confirmLink' => $verification->getVerificationUrl() ]) ] ); @@ -759,7 +838,7 @@ public function sendMail(array $mailData, array $recipients, string $templateFil * @return QUI\Projects\Site|false - Site object or false if no ACTIVE registration site found * @throws QUI\Exception */ - public function getRegistrationSite(QUI\Projects\Project $Project = null): bool|QUI\Projects\Site + public function getRegistrationSite(null | QUI\Projects\Project $Project = null): bool | QUI\Projects\Site { if (is_null($Project)) { $Project = QUI::getProjectManager()->getStandard(); @@ -786,7 +865,7 @@ public function getRegistrationSite(QUI\Projects\Project $Project = null): bool| * @return QUI\Projects\Site|false - Site object or false if no ACTIVE registration site found * @throws QUI\Exception */ - public function getRegistrationSignUpSite(QUI\Projects\Project $Project = null): bool|QUI\Projects\Site + public function getRegistrationSignUpSite(null | QUI\Projects\Project $Project = null): bool | QUI\Projects\Site { if (is_null($Project)) { $Project = QUI::getProjectManager()->getStandard(); @@ -813,7 +892,7 @@ public function getRegistrationSignUpSite(QUI\Projects\Project $Project = null): * @return QUI\Projects\Site|false - Site object or false if no ACTIVE login site found * @throws QUI\Exception */ - public function getLoginSite(QUI\Projects\Project $Project = null): bool|QUI\Projects\Site + public function getLoginSite(null | QUI\Projects\Project $Project = null): bool | QUI\Projects\Site { if (is_null($Project)) { $Project = QUI::getProjectManager()->getStandard(); @@ -840,7 +919,7 @@ public function getLoginSite(QUI\Projects\Project $Project = null): bool|QUI\Pro * @return QUI\Projects\Site|false - Site object or false if no ACTIVE profile site found * @throws QUI\Exception */ - public function getProfileSite(QUI\Projects\Project $Project = null): bool|QUI\Projects\Site + public function getProfileSite(null | QUI\Projects\Project $Project = null): bool | QUI\Projects\Site { if (is_null($Project)) { $Project = QUI::getProjectManager()->getStandard(); @@ -865,7 +944,7 @@ public function getProfileSite(QUI\Projects\Project $Project = null): bool|QUI\P * * @return false|QUI\Projects\Site */ - public function getRedirectOnActivationSite(): bool|QUI\Projects\Site + public function getRedirectOnActivationSite(): bool | QUI\Projects\Site { try { $registrationSettings = $this->getRegistrationSettings(); diff --git a/src/QUI/FrontendUsers/RegistrarInterface.php b/src/QUI/FrontendUsers/RegistrarInterface.php index e8b76bf1e2ab1b1a0e5643f67a5bbdbe2b923753..2a54cf8825e961ecb6d9b1141274978ad54ba00f 100644 --- a/src/QUI/FrontendUsers/RegistrarInterface.php +++ b/src/QUI/FrontendUsers/RegistrarInterface.php @@ -98,7 +98,7 @@ public function getControl(): Control; * @param ?QUI\Locale $Locale (optional) - If omitted use QUI::getLocale() * @return string */ - public function getTitle(QUI\Locale $Locale = null): string; + public function getTitle(null | QUI\Locale $Locale = null): string; /** * Get description @@ -106,7 +106,7 @@ public function getTitle(QUI\Locale $Locale = null): string; * @param ?QUI\Locale $Locale (optional) - If omitted use QUI::getLocale() * @return string */ - public function getDescription(QUI\Locale $Locale = null): string; + public function getDescription(null | QUI\Locale $Locale = null): string; /** * Set current Project the Registrar works for diff --git a/src/QUI/FrontendUsers/Registrars/Email/Registrar.php b/src/QUI/FrontendUsers/Registrars/Email/Registrar.php index 6e90350ac0a703825315127bb658018355410d8d..cb0f6a7c984c4a56de34dd93d234db9a387c1ead 100644 --- a/src/QUI/FrontendUsers/Registrars/Email/Registrar.php +++ b/src/QUI/FrontendUsers/Registrars/Email/Registrar.php @@ -37,7 +37,6 @@ public function onRegistered(QUI\Interfaces\Users\User $User): void $registrationSettings = $RegistrarHandler->getRegistrationSettings(); $useAddress = boolval($registrationSettings['addressInput']); - /** @var QUI\Users\User $User */ $firstname = $this->getAttribute('firstname'); $lastname = $this->getAttribute('lastname'); @@ -382,7 +381,7 @@ public function getTitle(?QUI\Locale $Locale = null): string * @param QUI\Locale|null $Locale (optional) - If omitted use QUI::getLocale() * @return string */ - public function getDescription(QUI\Locale $Locale = null): string + public function getDescription(null | QUI\Locale $Locale = null): string { if (is_null($Locale)) { $Locale = QUI::getLocale(); diff --git a/src/QUI/FrontendUsers/RegistrationUtils.php b/src/QUI/FrontendUsers/RegistrationUtils.php index 3b79a742672c4061bed00bc691b863838b205849..31fac37e77b741b3466bf15288dc0a5d3df8856f 100644 --- a/src/QUI/FrontendUsers/RegistrationUtils.php +++ b/src/QUI/FrontendUsers/RegistrationUtils.php @@ -19,7 +19,7 @@ class RegistrationUtils * @param Project|null $Project $Project (optional) - QUIQQER Project [default: QUI::getRewrite()->getProject()] * @return string */ - public static function getFurtherLinksText(Project $Project = null): string + public static function getFurtherLinksText(null | Project $Project = null): string { try { if (empty($Project)) { diff --git a/src/QUI/FrontendUsers/Rest/Routes/GetRegisterRequiredFields.php b/src/QUI/FrontendUsers/Rest/Routes/GetRegisterRequiredFields.php index 2ab20745d75822281b2f7e7f76eea4e5cfaf9bf0..a72d9bde0f8d64ea2cd4fe7d3f1187f2440956fd 100644 --- a/src/QUI/FrontendUsers/Rest/Routes/GetRegisterRequiredFields.php +++ b/src/QUI/FrontendUsers/Rest/Routes/GetRegisterRequiredFields.php @@ -22,6 +22,10 @@ class GetRegisterRequiredFields */ public static function call(SlimRequest $Request, SlimResponse $Response, array $args): SlimResponse { + if (!class_exists('QUI\REST\ResponseFactory')) { + throw new QUI\Exception('Class "QUI\REST\ResponseFactory" not found.'); + } + $ResponseFactory = new QUI\REST\ResponseFactory(); try { diff --git a/src/QUI/FrontendUsers/Rest/Routes/PostRegister.php b/src/QUI/FrontendUsers/Rest/Routes/PostRegister.php index b596fa49802ddf5841028bb7d24b0f6c42187bdf..18da1763bd19490cc6b067d6f6c3170c80a9ebe4 100644 --- a/src/QUI/FrontendUsers/Rest/Routes/PostRegister.php +++ b/src/QUI/FrontendUsers/Rest/Routes/PostRegister.php @@ -6,9 +6,9 @@ use Psr\Http\Message\ResponseInterface as SlimResponse; use Psr\Http\Message\ServerRequestInterface as SlimRequest; use QUI; -use QUI\FrontendUsers\ActivationVerification; +use QUI\FrontendUsers\ActivationLinkVerification; use QUI\FrontendUsers\Exception; -use QUI\Verification\Verifier; +use QUI\Verification\VerificationFactory; use function boolval; use function explode; @@ -212,19 +212,23 @@ protected static function addRegistrationDataToUser( /** * @throws QUI\Exception - * @throws QUI\Verification\Exception + * @throws QUI\Verification\Exception|\DateMalformedStringException */ protected static function sendActivationMail( QUI\Interfaces\Users\User $User, QUI\Projects\Project $Project ): bool { // TODO: Verification uses Project from QUI::getRewrite instead of the parameter, therefore the default project is always used (see quiqqer/verification#5) - $ActivationVerification = new ActivationVerification($User->getUUID(), [ - 'project' => $Project->getName(), - 'projectLang' => $Project->getLang() - ]); - - $activationLink = Verifier::startVerification($ActivationVerification, true); + $verificationFactory = new VerificationFactory(); + $verification = $verificationFactory->createLinkVerification( + 'activate-' . $User->getUUID(), + new ActivationLinkVerification(), + [ + 'uuid' => $User->getUUID(), + 'project' => $Project->getName(), + 'projectLang' => $Project->getLang() + ] + ); $L = QUI::getLocale(); $lg = 'quiqqer/frontend-users'; @@ -253,7 +257,7 @@ protected static function sendActivationMail( 'userLastName' => $User->getAttribute('lastname') ?: '', 'email' => $User->getAttribute('email'), 'date' => $L->formatDate(time()), - 'activationLink' => $activationLink + 'activationLink' => $verification->getVerificationUrl() ]) ] ); diff --git a/src/QUI/FrontendUsers/UserDeleteConfirmVerification.php b/src/QUI/FrontendUsers/UserDeleteConfirmLinkVerification.php similarity index 59% rename from src/QUI/FrontendUsers/UserDeleteConfirmVerification.php rename to src/QUI/FrontendUsers/UserDeleteConfirmLinkVerification.php index b2a6cd2e64aeaf37b98f5522ea309a38ed8aa9e6..74aaa17dd8ca925fef15f2a6ecc7c5dbd879593a 100644 --- a/src/QUI/FrontendUsers/UserDeleteConfirmVerification.php +++ b/src/QUI/FrontendUsers/UserDeleteConfirmLinkVerification.php @@ -5,7 +5,9 @@ use QUI; use QUI\Exception; use QUI\ExceptionStack; -use QUI\Verification\AbstractVerification; +use QUI\Verification\Entity\AbstractVerification; +use QUI\Verification\Entity\LinkVerification; +use QUI\Verification\Enum\VerificationErrorReason; /** * Class UserDeleteConfirmVerification @@ -14,16 +16,17 @@ * * @package QUI\FrontendUsers */ -class UserDeleteConfirmVerification extends AbstractVerification +class UserDeleteConfirmLinkVerification extends AbstractFrontendUsersLinkVerificationHandler { /** * Get the duration of a Verification (minutes) * - * @return int|false - duration in minutes; + * @param AbstractVerification $verification + * @return int|null - duration in minutes; * if this method returns false use the module setting default value * @throws Exception */ - public function getValidDuration(): bool|int + public function getValidDuration(AbstractVerification $verification): ?int { $settings = Handler::getInstance()->getMailSettings(); return (int)$settings['verificationValidityDuration']; @@ -32,16 +35,17 @@ public function getValidDuration(): bool|int /** * Execute this method on successful verification * + * @param LinkVerification $verification * @return void * @throws \Exception */ - public function onSuccess(): void + public function onSuccess(LinkVerification $verification): void { - $userId = $this->getIdentifier(); + $userUuid = $verification->getCustomDataEntry('uuid'); $userProfileSettings = Handler::getInstance()->getUserProfileSettings(); try { - $User = QUI::getUsers()->get($userId); + $User = QUI::getUsers()->get($userUuid); switch ($userProfileSettings['userDeleteMode']) { case 'delete': @@ -68,7 +72,7 @@ public function onSuccess(): void QUI\System\Log::writeException($Exception); QUI\System\Log::addError( - self::class . ' :: onSuccess -> Could not find/delete user #' . $userId + self::class . ' :: onSuccess -> Could not find/delete user #' . $userUuid ); QUI\System\Log::writeException($Exception); @@ -80,18 +84,21 @@ public function onSuccess(): void /** * Execute this method on unsuccessful verification * + * @param LinkVerification $verification + * @param VerificationErrorReason $reason * @return void */ - public function onError(): void + public function onError(LinkVerification $verification, VerificationErrorReason $reason): void { } /** * This message is displayed to the user on successful verification * + * @param LinkVerification $verification * @return string */ - public function getSuccessMessage(): string + public function getSuccessMessage(LinkVerification $verification): string { return QUI::getLocale()->get( 'quiqqer/frontend-users', @@ -102,10 +109,11 @@ public function getSuccessMessage(): string /** * This message is displayed to the user on unsuccessful verification * - * @param string $reason - The reason for the error (see \QUI\Verification\Verifier::REASON_) + * @param LinkVerification $verification + * @param VerificationErrorReason $reason - The reason for the error (see \QUI\Verification\Verifier::REASON_) * @return string */ - public function getErrorMessage(string $reason): string + public function getErrorMessage(LinkVerification $verification, VerificationErrorReason $reason): string { return ''; } @@ -113,18 +121,23 @@ public function getErrorMessage(string $reason): string /** * Automatically redirect the user to this URL on successful verification * - * @return string|false - If this method returns false, no redirection takes place + * @param LinkVerification $verification + * @return string|null - If this method returns false, no redirection takes place * @throws Exception * @throws ExceptionStack */ - public function getOnSuccessRedirectUrl(): bool|string + public function getOnSuccessRedirectUrl(LinkVerification $verification): ?string { - $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite( - $this->getProject() - ); + $project = $this->getProject($verification); + + if (!$project) { + return null; + } + + $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite($project); if (!$RegistrationSite) { - return false; + return null; } return $RegistrationSite->getUrlRewritten([], [ @@ -135,37 +148,27 @@ public function getOnSuccessRedirectUrl(): bool|string /** * Automatically redirect the user to this URL on unsuccessful verification * - * @return string|false - If this method returns false, no redirection takes place + * @param LinkVerification $verification + * @param VerificationErrorReason $reason + * @return string|null - If this method returns false, no redirection takes place * @throws Exception */ - public function getOnErrorRedirectUrl(): bool|string + public function getOnErrorRedirectUrl(LinkVerification $verification, VerificationErrorReason $reason): ?string { - $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite( - $this->getProject() - ); + $project = $this->getProject($verification); + + if (!$project) { + return null; + } + + $RegistrationSite = Handler::getInstance()->getRegistrationSignUpSite($project); if (!$RegistrationSite) { - return false; + return null; } return $RegistrationSite->getUrlRewritten([], [ 'error' => 'userdelete' ]); } - - /** - * Get the Project this Verification is intended for - * - * @return QUI\Projects\Project - * @throws Exception - */ - protected function getProject(): QUI\Projects\Project - { - $additionalData = $this->getAdditionalData(); - - return QUI::getProjectManager()->getProject( - $additionalData['project'], - $additionalData['projectLang'] - ); - } } diff --git a/src/QUI/FrontendUsers/Utils.php b/src/QUI/FrontendUsers/Utils.php index 32b051562bf9f49aa847dc9080dd237cff4dbd70..393756dd5c136f52e658611500706d2fd3d93ae9 100644 --- a/src/QUI/FrontendUsers/Utils.php +++ b/src/QUI/FrontendUsers/Utils.php @@ -1,17 +1,16 @@ <?php -/** - * This file contains QUI\FrontendUsers\Utils - */ - namespace QUI\FrontendUsers; use QUI; use QUI\FrontendUsers\Controls\Profile\ControlInterface; use QUI\Package\Package; use QUI\Permissions; +use QUI\Interfaces\Users\User as QUIUserInterface; +use QUI\FrontendUsers\Exception\EmailAddressNotVerifiableException; use function class_exists; +use function in_array; use function is_a; use function json_decode; @@ -32,7 +31,6 @@ public static function getFrontendUsersPackages(): array $packages = QUI::getPackageManager()->getInstalled(); $list = []; - /* @var $Package <Package */ foreach ($packages as $package) { try { $Package = QUI::getPackage($package['name']); @@ -73,7 +71,6 @@ public static function getProfileCategories(): array $packages = self::getFrontendUsersPackages(); $Engine = QUI::getTemplateManager()->getEngine(); - /** @var Package $Package */ foreach ($packages as $Package) { $Parser = new QUI\Utils\XML\Settings(); $Parser->setXMLPath('//quiqqer/frontend-users/profile'); @@ -137,7 +134,7 @@ public static function getProfileCategories(): array * * @throws Exception */ - public static function getProfileSetting(string $category, bool|string $settings = false): array + public static function getProfileSetting(string $category, bool | string $settings = false): array { if ($category) { $categories = [self::getProfileCategory($category)]; @@ -168,8 +165,10 @@ public static function getProfileSetting(string $category, bool|string $settings * * @throws Exception */ - public static function getProfileSettingControl(string $category, bool|string $settings = false): ?ControlInterface - { + public static function getProfileSettingControl( + string $category, + bool | string $settings = false + ): ?ControlInterface { $setting = self::getProfileSetting($category, $settings); $Control = null; @@ -292,8 +291,8 @@ public static function getProfileBarCategorySettings(): array */ public static function hasPermissionToViewCategory( string $category, - bool|string $setting = false, - QUI\Interfaces\Users\User $User = null + bool | string $setting = false, + null | QUI\Interfaces\Users\User $User = null ): bool { if ($User === null) { $User = QUI::getUserBySession(); @@ -347,8 +346,10 @@ public static function loadTranslationForCategories(array $categories = []): arr * @param null|QUI\Projects\Project $Project * @return array */ - public static function setUrlsToCategorySettings(array $categories = [], QUI\Projects\Project $Project = null): array - { + public static function setUrlsToCategorySettings( + array $categories = [], + null | QUI\Projects\Project $Project = null + ): array { try { if ($Project === null) { $Project = QUI::getRewrite()->getProject(); @@ -426,12 +427,24 @@ public static function getGravatarUrl(string $email, int $s = 80): string } /** - * Check if the standard e-mail address of a user is verified + * Check if the STANDARD e-mail address of a user is verified * - * @param QUI\Users\User $User + * @param QUIUserInterface $User * @return bool + * @deprecated use isDefaultUserEmailVerified */ - public static function isUserEmailVerified(QUI\Users\User $User): bool + public static function isUserEmailVerified(QUIUserInterface $User): bool + { + return self::isDefaultUserEmailVerified($User); + } + + /** + * Check if the STANDARD e-mail address of a user is verified + * + * @param QUIUserInterface $User + * @return bool + */ + public static function isDefaultUserEmailVerified(QUIUserInterface $User): bool { $email = $User->getAttribute('email'); @@ -442,21 +455,124 @@ public static function isUserEmailVerified(QUI\Users\User $User): bool return $User->getAttribute(Handler::USER_ATTR_EMAIL_VERIFIED); } + /** + * Check if any e-mail address (user, user address) is verified for a specific user. + * + * @param string $email + * @param QUIUserInterface $User + * @return bool + */ + public static function isEmailAddressVerifiedForUser(string $email, QUIUserInterface $User): bool + { + $verifiedEmailAddresses = $User->getAttribute(Handler::USER_ATTR_EMAIL_ADDRESSES_VERIFIED); + + if (empty($verifiedEmailAddresses)) { + return false; + } + + return in_array($email, $verifiedEmailAddresses); + } + + /** + * Set a specific email address as verified for a user. + * + * @param string $email + * @param QUIUserInterface $User + * @return void + * + * @throws EmailAddressNotVerifiableException + */ + public static function setEmailAddressAsVerfifiedForUser(string $email, QUIUserInterface $User): void + { + if (self::isEmailAddressVerifiedForUser($email, $User)) { + return; + } + + if (empty($email)) { + throw new EmailAddressNotVerifiableException('Cannot verify empty email address.'); + } + + if (!QUI\Utils\Security\Orthos::checkMailSyntax($email)) { + throw new EmailAddressNotVerifiableException("Cannot verify invalid email address $email."); + } + + if (!self::doesUserHaveEmailAddress($email, $User)) { + throw new EmailAddressNotVerifiableException( + "Cannot verify email address $email for user {$User->getId()}, because this email address" + . " is not associated with this user (neither saved in user or user addresses)." + ); + } + + $verifiedEmailAddresses = $User->getAttribute(Handler::USER_ATTR_EMAIL_ADDRESSES_VERIFIED); + + if (empty($verifiedEmailAddresses)) { + $verifiedEmailAddresses = []; + } + + $verifiedEmailAddresses[] = $email; + + $User->setAttribute(Handler::USER_ATTR_EMAIL_ADDRESSES_VERIFIED, $verifiedEmailAddresses); + $User->save(QUI::getUsers()->getSystemUser()); + } + + /** + * Check if a user has a specific email address (either in user or one of user addresses). + * + * @param string $email + * @param QUIUserInterface $User + * @return bool + */ + public static function doesUserHaveEmailAddress(string $email, QUIUserInterface $User): bool + { + $userEmail = $User->getAttribute('email'); + + if ($email === $userEmail) { + return true; + } + + foreach ($User->getAddressList() as $Address) { + if (!($Address instanceof QUI\Users\Address)) { + continue; + } + + $addressEmails = $Address->getMailList(); + + if (in_array($email, $addressEmails)) { + return true; + } + } + + return false; + } + /** * Set the standard e-mail address of a user to status "verified" * - * @param QUI\Users\User $User + * @param QUIUserInterface $User * @return void + * @throws EmailAddressNotVerifiableException + * @deprecated use setDefaultUserEmailVerified + */ + public static function setUserEmailVerified(QUIUserInterface $User): void + { + self::setDefaultUserEmailVerified($User); + } + + /** + * Set the standard e-mail address of a user to status "verified" * - * @throws QUI\Exception + * @param QUIUserInterface $User + * @return void + * @throws EmailAddressNotVerifiableException */ - public static function setUserEmailVerified(QUI\Users\User $User): void + public static function setDefaultUserEmailVerified(QUIUserInterface $User): void { + self::setEmailAddressAsVerfifiedForUser($User->getAttribute('email'), $User); + $User->setAttribute(Handler::USER_ATTR_EMAIL_VERIFIED, true); $User->save(QUI::getUsers()->getSystemUser()); } - public static function getMissingAddressFields(QUI\Users\Address $Address): array { $missing = []; diff --git a/templates/mail.confirm_email_address.html b/templates/mail.confirm_email_address.html new file mode 100644 index 0000000000000000000000000000000000000000..734c938f1726958422ec4329c01504b851ac9744 --- /dev/null +++ b/templates/mail.confirm_email_address.html @@ -0,0 +1 @@ +{$body} \ No newline at end of file diff --git a/tests/phpunit-bootstrap.php b/tests/phpunit-bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..eca92fd67bed8ae4ec424ed82d300119d792f042 --- /dev/null +++ b/tests/phpunit-bootstrap.php @@ -0,0 +1,11 @@ +<?php + +if (!defined('QUIQQER_SYSTEM')) { + define('QUIQQER_SYSTEM', true); +} + +if (!defined('QUIQQER_AJAX')) { + define('QUIQQER_AJAX', true); +} + +require_once __DIR__ . '/../../../../bootstrap.php'; diff --git a/types/profile.php b/types/profile.php index f2056707e0ca5743622b592fe709f9941bb4fed8..4acafce8852fe4ddedc34eac96f15bc41db67160 100644 --- a/types/profile.php +++ b/types/profile.php @@ -12,7 +12,7 @@ $SessionUser = QUI::getUserBySession(); if (QUI::getUsers()->isNobodyUser($SessionUser)) { - $Control = new QUI\Users\Controls\Login(); + $Control = new QUI\FrontendUsers\Controls\Login(); } else { $_REQUEST['_url'] = ltrim($_REQUEST['_url'], '/'); // nginx fix $_REQUEST['_url'] = urldecode($_REQUEST['_url']); diff --git a/user.xml b/user.xml index 80e9f515bc6e8cf803972f1a7753a119c54a0f9f..b086a9eb99a5b29277c107b800d8b0934ba7bca4 100644 --- a/user.xml +++ b/user.xml @@ -7,7 +7,8 @@ <attribute>quiqqer.frontendUsers.registrar</attribute> <attribute>quiqqer.frontendUsers.userActivationRequired</attribute> <attribute>quiqqer.frontendUsers.activationLoginExecuted</attribute> - <attribute>quiqqer.frontendUsers.emailVerified</attribute> + <attribute>quiqqer.frontendUsers.emailVerified</attribute> <!-- main email address --> + <attribute>quiqqer.frontendUsers.emailAddressesVerified</attribute> <!-- additional email addresses --> <attribute>quiqqer.frontendUsers.useGravatarIcon</attribute> </attributes> </user> \ No newline at end of file