From 05f00b407a3e0cb04e6037e9b5c8b5cf70fc84b3 Mon Sep 17 00:00:00 2001 From: Eike Ahmels Date: Mon, 30 Aug 2021 07:23:37 +0200 Subject: [PATCH 1/4] client html side of user password change --- .gitignore | 3 + .../Html/Account/Profile/Account/Standard.php | 154 ++++++++++++++ .../Client/Html/Account/Profile/Standard.php | 2 +- .../account/profile/account-body-standard.php | 190 ++++++++++++++++++ 4 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 client/html/src/Client/Html/Account/Profile/Account/Standard.php create mode 100644 client/html/templates/account/profile/account-body-standard.php diff --git a/.gitignore b/.gitignore index 16f1349e2..d23cf16a8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ client/html/tests/tmp *.junit.xml *.log *.ser + +.idea +vendor diff --git a/client/html/src/Client/Html/Account/Profile/Account/Standard.php b/client/html/src/Client/Html/Account/Profile/Account/Standard.php new file mode 100644 index 000000000..4fa954f3c --- /dev/null +++ b/client/html/src/Client/Html/Account/Profile/Account/Standard.php @@ -0,0 +1,154 @@ +/subparts = array( "subclient1", "subclient2" ) + * + * You can also remove one or more parts if they shouldn't be rendered: + * + * client/html//subparts = array( "subclient1" ) + * + * As the clients only generates structural HTML, the layout defined via CSS + * should support adding, removing or readdressing content by a fluid like + * design. + * + * @param array List of sub-client names + * @since 2019.07 + * @category Developer + */ + private $subPartPath = 'client/html/account/profile/account/subparts'; + private $subPartNames = []; + + protected function getSubClientNames(): array + { + return []; + } + + public function getSubClient(string $type, string $name = null): \Aimeos\Client\Html\Iface + { + // TODO: Implement getSubClient() method. + } + + /** + * Returns the HTML code for insertion into the body. + * + * @param string $uid Unique identifier for the output if the content is placed more than once on the same page + * @return string HTML code + */ + public function getBody(string $uid = ''): string + { + $view = $this->getView(); + + $html = ''; + foreach( $this->getSubClients() as $subclient ) { + $html .= $subclient->setView( $view )->getBody( $uid ); + } + $view->addressBody = $html; + + /** client/html/account/profile/address/template-body + * Relative path to the HTML body template of the account profile address client. + * + * The template file contains the HTML code and processing instructions + * to generate the result shown in the body of the frontend. The + * configuration string is the path to the template file relative + * to the templates directory (usually in client/html/templates). + * + * You can overwrite the template file configuration in extensions and + * provide alternative templates. These alternative templates should be + * named like the default one but with the string "standard" replaced by + * an unique name. You may use the name of your project for this. If + * you've implemented an alternative client class as well, "standard" + * should be replaced by the name of the new class. + * + * @param string Relative path to the template creating code for the HTML page body + * @since 2019.07 + * @category Developer + * @see client/html/account/profile/address/template-header + */ + $tplconf = 'client/html/account/profile/account/template-body'; + $default = 'account/profile/account-body-standard'; + + return $view->render( $view->config( $tplconf, $default ) ); + } + + /** + * Processes the input, e.g. store given values. + * + * A view must be available and this method doesn't generate any output + * besides setting view variables if necessary. + */ + public function process() + { + $view = $this->getView(); + + if( !$view->param( 'account/save' ) ) { + return parent::process(); + } + + /** @var \Aimeos\Controller\Frontend\Customer\Standard $cntl */ + $cntl = \Aimeos\Controller\Frontend::create( $this->getContext(), 'customer' ); + $oldPassword = $cntl->get()->getPassword(); + $values = $view->param('account', []); + + $isNew = $values['customer.newpassword'] !== $values['customer.oldpassword']; + $confirmed = $values['customer.newpassword'] === $values['customer.confirmnewpassword']; + + $errors = []; + + if (!$isNew) { + $errors['isNew'] = "The given password is not new!"; + } + + if (!$confirmed) { + $errors["confirm"] = "New passwords doesnt match!"; + } + + $cntl = $cntl->add($values); + + if ( $oldPassword === $cntl->get()->getPassword() ) { + $errors['oldPassword'] = "Wrong password!"; + } + + $view->passwordChanged = count(array_keys($errors)) === 0; + + if (count(array_keys($errors)) > 0) { + $view->passwordErrors = $errors; + } + + $cntl->store(); + + parent::process(); + } +} diff --git a/client/html/src/Client/Html/Account/Profile/Standard.php b/client/html/src/Client/Html/Account/Profile/Standard.php index df03a0f68..f3685a83c 100644 --- a/client/html/src/Client/Html/Account/Profile/Standard.php +++ b/client/html/src/Client/Html/Account/Profile/Standard.php @@ -66,7 +66,7 @@ class Standard * @since 2019.10 * @category Developer */ - private $subPartNames = ['address']; + private $subPartNames = ['address', 'account']; private $view; diff --git a/client/html/templates/account/profile/account-body-standard.php b/client/html/templates/account/profile/account-body-standard.php new file mode 100644 index 000000000..e051336b4 --- /dev/null +++ b/client/html/templates/account/profile/account-body-standard.php @@ -0,0 +1,190 @@ +encoder(); + +/** client/html/account/profile/url/target + * Destination of the URL where the controller specified in the URL is known + * + * The destination can be a page ID like in a content management system or the + * module of a software development framework. This "target" must contain or know + * the controller that should be called by the generated URL. + * + * @param string Destination of the URL + * @since 2019.10 + * @category Developer + * @see client/html/account/profile/url/controller + * @see client/html/account/profile/url/action + * @see client/html/account/profile/url/config + */ + +/** client/html/account/profile/url/controller + * Name of the controller whose action should be called + * + * In Model-View-Controller (MVC) applications, the controller contains the methods + * that create parts of the output displayed in the generated HTML page. Controller + * names are usually alpha-numeric. + * + * @param string Name of the controller + * @since 2019.10 + * @category Developer + * @see client/html/account/profile/url/target + * @see client/html/account/profile/url/action + * @see client/html/account/profile/url/config + */ + +/** client/html/account/profile/url/action + * Name of the action that should create the output + * + * In Model-View-Controller (MVC) applications, actions are the methods of a + * controller that create parts of the output displayed in the generated HTML page. + * Action names are usually alpha-numeric. + * + * @param string Name of the action + * @since 2019.10 + * @category Developer + * @see client/html/account/profile/url/target + * @see client/html/account/profile/url/controller + * @see client/html/account/profile/url/config + */ + +/** client/html/account/profile/url/config + * Associative list of configuration options used for generating the URL + * + * You can specify additional options as key/value pairs used when generating + * the URLs, like + * + * client/html//url/config = array( 'absoluteUri' => true ) + * + * The available key/value pairs depend on the application that embeds the e-commerce + * framework. This is because the infrastructure of the application is used for + * generating the URLs. The full list of available config options is referenced + * in the "see also" section of this page. + * + * @param string Associative list of configuration options + * @since 2019.10 + * @category Developer + * @see client/html/account/profile/url/target + * @see client/html/account/profile/url/controller + * @see client/html/account/profile/url/action + * @see client/html/url/config + */ + +/** client/html/account/profile/url/filter + * Removes parameters for the detail page before generating the URL + * + * For SEO, it's nice to have URLs which contains only required parameters. + * This setting removes the listed parameters from the URLs. Keep care to + * remove no required parameters! + * + * @param array List of parameter names to remove + * @since 2019.10 + * @category User + * @category Developer + * @see client/html/account/profile/url/target + * @see client/html/account/profile/url/controller + * @see client/html/account/profile/url/action + * @see client/html/account/profile/url/config + */ + +$passwordErrors = $this->get('passwordErrors', []); + +?> +block()->start( 'account/profile/account' ) ?> + + From 079280d419528d1d2e9b2a275e9e9b36d25fe0a3 Mon Sep 17 00:00:00 2001 From: Eike Ahmels Date: Tue, 31 Aug 2021 14:12:48 +0200 Subject: [PATCH 2/4] Fixed described errors and bugs --- .../Html/Account/Profile/Account/Standard.php | 162 +++++++++++++++--- .../account/profile/account-body-standard.php | 25 +-- 2 files changed, 150 insertions(+), 37 deletions(-) diff --git a/client/html/src/Client/Html/Account/Profile/Account/Standard.php b/client/html/src/Client/Html/Account/Profile/Account/Standard.php index 4fa954f3c..a4ddd65ec 100644 --- a/client/html/src/Client/Html/Account/Profile/Account/Standard.php +++ b/client/html/src/Client/Html/Account/Profile/Account/Standard.php @@ -2,10 +2,12 @@ namespace Aimeos\Client\Html\Account\Profile\Account; -use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Validator; +use Laravel\Fortify\Rules\Password; +use Laravel\Jetstream\Jetstream; /** - * Default implementation of acount profile address HTML client. + * Default implementation of acount profile account HTML client. * * @package Client * @subpackage Html @@ -15,8 +17,8 @@ class Standard implements \Aimeos\Client\Html\Common\Client\Factory\Iface { - /** client/html/account/profile/address/subparts - * List of HTML sub-clients rendered within the account profile address section + /** client/html/account/profile/account/subparts + * List of HTML sub-clients rendered within the account profile account section * * The output of the frontend is composed of the code generated by the HTML * clients. Each HTML client can consist of serveral (or none) sub-clients @@ -26,13 +28,13 @@ class Standard * the output that is placed inside the container of its parent. * * At first, always the HTML code generated by the parent is printed, then - * the HTML code of its sub-clients. The address of the HTML sub-clients - * determines the address of the output of these sub-clients inside the parent + * the HTML code of its sub-clients. The account of the HTML sub-clients + * determines the account of the output of these sub-clients inside the parent * container. If the configured list of clients is * * array( "subclient1", "subclient2" ) * - * you can easily change the address of the output by readdressing the subparts: + * you can easily change the account of the output by readdressing the subparts: * * client/html//subparts = array( "subclient1", "subclient2" ) * @@ -51,14 +53,101 @@ class Standard private $subPartPath = 'client/html/account/profile/account/subparts'; private $subPartNames = []; - protected function getSubClientNames(): array + /** + * Returns the list of sub-client names configured for the client. + * + * @return array List of HTML client names + */ + protected function getSubClientNames() : array { - return []; + return $this->getContext()->getConfig()->get( $this->subPartPath, $this->subPartNames ); } - public function getSubClient(string $type, string $name = null): \Aimeos\Client\Html\Iface + /** + * Returns the sub-client given by its name. + * + * @param string $type Name of the client type + * @param string|null $name Name of the sub-client (Default if null) + * @return \Aimeos\Client\Html\Iface Sub-client object + * @throws \Aimeos\Client\Html\Exception + */ + public function getSubClient( string $type, string $name = null ) : \Aimeos\Client\Html\Iface { - // TODO: Implement getSubClient() method. + /** client/html/account/profile/account/decorators/excludes + * Excludes decorators added by the "common" option from the account profile account html client + * + * Decorators extend the functionality of a class by adding new aspects + * (e.g. log what is currently done), executing the methods of the underlying + * class only in certain conditions (e.g. only for logged in users) or + * modify what is returned to the caller. + * + * This option allows you to remove a decorator added via + * "client/html/common/decorators/default" before they are wrapped + * around the html client. + * + * client/html/account/profile/account/decorators/excludes = array( 'decorator1' ) + * + * This would remove the decorator named "decorator1" from the list of + * common decorators ("\Aimeos\Client\Html\Common\Decorator\*") added via + * "client/html/common/decorators/default" to the html client. + * + * @param array List of decorator names + * @since 2019.07 + * @category Developer + * @see client/html/common/decorators/default + * @see client/html/account/profile/account/decorators/global + * @see client/html/account/profile/account/decorators/local + */ + + /** client/html/account/profile/account/decorators/global + * Adds a list of globally available decorators only to the account profile account html client + * + * Decorators extend the functionality of a class by adding new aspects + * (e.g. log what is currently done), executing the methods of the underlying + * class only in certain conditions (e.g. only for logged in users) or + * modify what is returned to the caller. + * + * This option allows you to wrap global decorators + * ("\Aimeos\Client\Html\Common\Decorator\*") around the html client. + * + * client/html/account/profile/account/decorators/global = array( 'decorator1' ) + * + * This would add the decorator named "decorator1" defined by + * "\Aimeos\Client\Html\Common\Decorator\Decorator1" only to the html client. + * + * @param array List of decorator names + * @since 2019.07 + * @category Developer + * @see client/html/common/decorators/default + * @see client/html/account/profile/account/decorators/excludes + * @see client/html/account/profile/account/decorators/local + */ + + /** client/html/account/profile/account/decorators/local + * Adds a list of local decorators only to the account profile account html client + * + * Decorators extend the functionality of a class by adding new aspects + * (e.g. log what is currently done), executing the methods of the underlying + * class only in certain conditions (e.g. only for logged in users) or + * modify what is returned to the caller. + * + * This option allows you to wrap local decorators + * ("\Aimeos\Client\Html\Account\Decorator\*") around the html client. + * + * client/html/account/profile/account/decorators/local = array( 'decorator2' ) + * + * This would add the decorator named "decorator2" defined by + * "\Aimeos\Client\Html\Account\Decorator\Decorator2" only to the html client. + * + * @param array List of decorator names + * @since 2019.07 + * @category Developer + * @see client/html/common/decorators/default + * @see client/html/account/profile/account/decorators/excludes + * @see client/html/account/profile/account/decorators/global + */ + + return $this->createSubClient( 'account/profile/account/' . $type, $name ); } /** @@ -66,6 +155,7 @@ public function getSubClient(string $type, string $name = null): \Aimeos\Client\ * * @param string $uid Unique identifier for the output if the content is placed more than once on the same page * @return string HTML code + * @throws \Aimeos\MW\View\Exception|\Aimeos\Client\Html\Exception */ public function getBody(string $uid = ''): string { @@ -75,10 +165,10 @@ public function getBody(string $uid = ''): string foreach( $this->getSubClients() as $subclient ) { $html .= $subclient->setView( $view )->getBody( $uid ); } - $view->addressBody = $html; + $view->accountBody = $html; - /** client/html/account/profile/address/template-body - * Relative path to the HTML body template of the account profile address client. + /** client/html/account/profile/account/template-body + * Relative path to the HTML body template of the account profile account client. * * The template file contains the HTML code and processing instructions * to generate the result shown in the body of the frontend. The @@ -95,7 +185,7 @@ public function getBody(string $uid = ''): string * @param string Relative path to the template creating code for the HTML page body * @since 2019.07 * @category Developer - * @see client/html/account/profile/address/template-header + * @see client/html/account/profile/account/template-header */ $tplconf = 'client/html/account/profile/account/template-body'; $default = 'account/profile/account-body-standard'; @@ -108,13 +198,15 @@ public function getBody(string $uid = ''): string * * A view must be available and this method doesn't generate any output * besides setting view variables if necessary. + * @throws \Aimeos\Client\Html\Exception|\Aimeos\Controller\Frontend\Exception */ public function process() { $view = $this->getView(); - if( !$view->param( 'account/save' ) ) { - return parent::process(); + if( !$view->param( 'account/save' , false) ) { + parent::process(); + return; } /** @var \Aimeos\Controller\Frontend\Customer\Standard $cntl */ @@ -127,17 +219,35 @@ public function process() $errors = []; - if (!$isNew) { - $errors['isNew'] = "The given password is not new!"; + $isValid = true; + try { + Validator::make([ + 'password' => $values['customer.newpassword'], + 'password_confirmation' => $values['customer.confirmnewpassword'] + ], [ + 'password' => ['required', 'string', new Password, 'confirmed'] + ])->validate(); + } catch (\Exception $ex) { + $isValid = false; + $errors['passwordRules'] = "The password does not meet the password requirements!"; } - if (!$confirmed) { - $errors["confirm"] = "New passwords doesnt match!"; - } + if ($isValid) { + /** is the password realy new? */ + if (!$isNew) { + $errors['isNew'] = "The given password is not new!"; + } + + /** does the confirm password is the same? */ + if (!$confirmed) { + $errors["confirm"] = "New passwords doesnt match!"; + } - $cntl = $cntl->add($values); + $cntl = $cntl->add($values); - if ( $oldPassword === $cntl->get()->getPassword() ) { + } + /** if the pasword is new and confirmed, but is not changed, the given old password must be wrong */ + if ($isValid && $isNew && $confirmed && $oldPassword === $cntl->get()->getPassword() ) { $errors['oldPassword'] = "Wrong password!"; } @@ -145,10 +255,10 @@ public function process() if (count(array_keys($errors)) > 0) { $view->passwordErrors = $errors; + } else { + $cntl->store(); } - $cntl->store(); - parent::process(); } } diff --git a/client/html/templates/account/profile/account-body-standard.php b/client/html/templates/account/profile/account-body-standard.php index e051336b4..46f53ee8e 100644 --- a/client/html/templates/account/profile/account-body-standard.php +++ b/client/html/templates/account/profile/account-body-standard.php @@ -95,13 +95,13 @@

html( $this->translate( 'client', 'account' ) ) ?>

csrf()->formfield() ?> - get('passwordChanged', '') === 'true' ) : ?> + get('passwordChanged', null) === true ) : ?>
-

Password changed successfull!

+

translate('client', 'Password changed successfull!') ?>

- get('passwordChanged', '') === 'false') :?> + get('passwordChanged', null) === false) :?>
-

Error(s) occured!

+

translate('client', 'Error(s) occured!') ?>

@@ -109,7 +109,6 @@

html( $this->translate( 'client', 'Password' ) ) ?>

- @@ -121,7 +120,7 @@ > - + html($this->translate('client', $passwordErrors['oldPassword'])) ?>
@@ -140,19 +139,23 @@ > - + html($this->translate('client', $passwordErrors['confirm'])) ?> - + html($this->translate('client', $passwordErrors['isNew'])) ?> + + + + + html($this->translate('client', $passwordErrors['passwordRules'])) ?>
- @@ -164,12 +167,12 @@ > - + html($this->translate('client', $passwordErrors['confirm'])) ?> - + html($this->translate('client', $passwordErrors['isNew'])) ?>
From 8b6fb55e928ed07d26270e056a5ddff66ec94c90 Mon Sep 17 00:00:00 2001 From: Eike Ahmels Date: Tue, 31 Aug 2021 14:33:47 +0200 Subject: [PATCH 3/4] fixed css and a php check --- .../account/profile/account-body-standard.php | 17 +++++++-------- client/html/themes/default/aimeos.css | 21 ++++++++++++++++++- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/client/html/templates/account/profile/account-body-standard.php b/client/html/templates/account/profile/account-body-standard.php index 46f53ee8e..44f312943 100644 --- a/client/html/templates/account/profile/account-body-standard.php +++ b/client/html/templates/account/profile/account-body-standard.php @@ -95,15 +95,14 @@

html( $this->translate( 'client', 'account' ) ) ?>

csrf()->formfield() ?> - get('passwordChanged', null) === true ) : ?> -
-

translate('client', 'Password changed successfull!') ?>

-
- get('passwordChanged', null) === false) :?> -
-

translate('client', 'Error(s) occured!') ?>

+ +
+ get('passwordChanged', null) === true ) : ?> +

translate('client', 'Password changed successfull!') ?>

+ get('passwordChanged', null) === false) : ?> +

translate('client', 'Error(s) occured!') ?>

+
-

html( $this->translate( 'client', 'Password' ) ) ?>

@@ -179,7 +178,7 @@
-
+
diff --git a/client/html/themes/default/aimeos.css b/client/html/themes/default/aimeos.css index 4ea9ee4b3..65dddaf87 100644 --- a/client/html/themes/default/aimeos.css +++ b/client/html/themes/default/aimeos.css @@ -4108,6 +4108,25 @@ html.no-js .catalog-filter-price:hover .price-lists { min-width: 0; } +.account-profile-account .container-fluid { + padding: 1rem; +} + +.account-profile-account .password-button-group { + padding-top: 1rem; + display: flex; + justify-content: center; +} + +.account-profile-account .password-button-group .btn { + margin: 0; +} + +.account-profile-account .password-change-notifications { + display: flex; + justify-content: center; +} + /*! jQuery UI - v1.10.0 - 2013-01-17 * https://jqueryui.com @@ -7165,4 +7184,4 @@ aside .catalog-session-pinned .pinned-items { .aimeos .card .btn { margin: 0.5rem 1rem; -} \ No newline at end of file +} From 3f60921113aa8bb614a58457cd57e30647aa4352 Mon Sep 17 00:00:00 2001 From: Eike Ahmels Date: Fri, 17 Sep 2021 14:50:12 +0200 Subject: [PATCH 4/4] fixed styling of password change --- client/html/themes/default/aimeos.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/html/themes/default/aimeos.css b/client/html/themes/default/aimeos.css index 0168fc761..ab45cd71c 100644 --- a/client/html/themes/default/aimeos.css +++ b/client/html/themes/default/aimeos.css @@ -4148,6 +4148,10 @@ html.no-js .catalog-filter-price:hover .price-lists { justify-content: center; } +.account-profile-account .password-change > div { + flex-wrap: nowrap!important; +} + /*! jQuery UI - v1.10.0 - 2013-01-17 * https://jqueryui.com