From 6866c8622f3df8b688bd38ef839309be81286f3c Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Wed, 1 Nov 2017 18:14:14 -0600 Subject: [PATCH 01/19] Feature/login events (#137) * Add option to have delayed authorization * Minor optimization * Add rolled back login event changes * Add deprecation docblocks * Remember entered username if login fails * Fix package name * Fix typehint from docblock * Improved rate limiter to work without sessions and against distributed attacks * Improve rate limiting * Minor fix * Udpated registration form * Added optional dynamic page visibility * Fire onLoginPage event --- CHANGELOG.md | 11 + README.md | 43 ++- blueprints.yaml | 14 +- classes/Controller.php | 182 +++++----- classes/Events/UserLoginEvent.php | 256 ++++++++++++++ classes/Login.php | 276 +++++++++++++-- classes/LoginCache.php | 103 ++++++ classes/RateLimiter.php | 108 ++++++ classes/RememberMe/RememberMe.php | 2 +- classes/RememberMe/TokenStorage.php | 9 +- cli/ChangePasswordCommand.php | 2 +- cli/ChangeUserStateCommand.php | 2 +- cli/NewUserCommand.php | 2 +- css/login.css | 5 + languages.yaml | 42 +-- login.php | 387 ++++++++++++++-------- login.yaml | 2 + pages/login.md | 1 - pages/register.md | 29 +- templates/login.json.twig | 2 +- templates/partials/login-form.html.twig | 13 +- templates/partials/login-status.html.twig | 2 +- templates/partials/messages.html.twig | 19 +- templates/partials/reset-form.html.twig | 2 +- 24 files changed, 1180 insertions(+), 334 deletions(-) create mode 100644 classes/Events/UserLoginEvent.php create mode 100644 classes/LoginCache.php create mode 100644 classes/RateLimiter.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c13bb3..2ddc32a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# v2.5.0 +## mm/dd/2017 + +1. [](#new) + * Added `$grav['login']->login()` and `$grav['login']->logout()` functions with event hooks + * Added `$grav['login']->getRateLimiter($context)` function + * Added events `onUserLoginAuthenticate`, `onUserLoginAuthorize`, `onUserLoginFailure`, `onUserLogin`, `onUserLogout` +1. [](#improved) + * Remember entered username if login fails + * Improved rate limiter to work without sessions and against distributed attacks + # v2.4.3 ## 10/11/2017 diff --git a/README.md b/README.md index cbaf135..f8183a7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,18 @@ These are available via GPM, and because the plugin has dependencies you just ne $ bin/gpm install login ``` +# Changes in version 2.4 + +Added new `$grav['login']->login()` and `$grav['login']->logout()` functions for you to use. + +They use following events which can be hooked by plugins: + +* `onUserLoginAuthenticate` Allows plugins to include their own authentication methods. +* `onUserLoginAuthorize` Allows plugins to block user from being logged in. +* `onUserLoginFailure` Allows plugins to include their own logic when user authentication failed. +* `onUserLogin` Allows plugins to include their own logic when user logs in. +* `onUserLogout` Allows plugins to include their own logic when user logs out. + # Changes in version 2.0 The Login Plugin 2.0 has the following changes compared to 1.0: @@ -200,25 +212,27 @@ Add the following content to your registration form page: ``` --- form: - + fields: - - - name: username + fullname: + type: text + validate: + required: true + + username: type: text validate: required: true message: PLUGIN_LOGIN.USERNAME_NOT_VALID config-pattern@: system.username_regex - - - name: email - type: text + email: + type: email validate: required: true message: PLUGIN_LOGIN.EMAIL_VALIDATION_MESSAGE - - - name: password1 + password1: type: password label: Enter a password validate: @@ -226,10 +240,9 @@ form: message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE config-pattern@: system.pwd_regex - - - name: password2 + password2: type: password - label: Repeat the password + label: Enter the password again validate: required: true message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE @@ -245,11 +258,13 @@ form: process: register_user: true - display: '/welcome' - message: "Welcome to my site!" + message: "Thanks for registering..." + reset: true --- -# Registration +# Register + +Create a new user account by entering all the required fields below: ``` This is a normal form. The only thing different from a contact form or another form that you might write on your site is the process field `register_user`, which takes care of processing the user registration. diff --git a/blueprints.yaml b/blueprints.yaml index 9edb435..b65304e 100644 --- a/blueprints.yaml +++ b/blueprints.yaml @@ -13,7 +13,7 @@ bugs: https://github.com/getgrav/grav-plugin-login/issues license: MIT dependencies: - - { name: grav, version: '>=1.3.5' } + - { name: grav, version: '>=1.3.9' } - { name: form, version: '>=2.4.0' } - { name: email, version: '~2.0' } @@ -99,6 +99,18 @@ form: validate: type: bool + dynamic_page_visibility: + type: toggle + label: PLUGIN_LOGIN.DYNAMIC_VISIBILITY + highlight: 0 + default: 0 + help: PLUGIN_LOGIN.DYNAMIC_VISIBILITY_HELP + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + protect_protected_page_media: type: toggle label: PLUGIN_LOGIN.PROTECT_PROTECTED_PAGE_MEDIA_LABEL diff --git a/classes/Controller.php b/classes/Controller.php index 03c52e6..cd5d8fc 100644 --- a/classes/Controller.php +++ b/classes/Controller.php @@ -1,20 +1,18 @@ rememberMe() instead */ protected $rememberMe; @@ -71,7 +70,7 @@ public function __construct(Grav $grav, $action, $post = null) { $this->grav = $grav; $this->action = $action; - $this->login = isset($this->grav['login']) ? $this->grav['login'] : ''; + $this->login = $this->grav['login']; $this->post = $post ? $this->getPost($post) : []; $this->rememberMe(); @@ -79,6 +78,7 @@ public function __construct(Grav $grav, $action, $post = null) /** * Performs an action. + * @throws \RuntimeException */ public function execute() { @@ -118,24 +118,28 @@ public function taskLogin() /** @var Language $t */ $t = $this->grav['language']; - /** @var User $user */ - $user = $this->grav['user']; + $userKey = isset($this->post['username']) ? (string)$this->post['username'] : ''; + $ipKey = Uri::ip(); - $count = $this->grav['config']->get('plugins.login.max_login_count', 5); - $interval =$this->grav['config']->get('plugins.login.max_login_interval', 10); + $rateLimiter = $this->login->getRateLimiter('login_attempts'); - if ($this->login->isUserRateLimited($user, 'login_attempts', $count, $interval)) { - $this->login->setMessage($t->translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $interval]), 'error'); + // Check if the current IP has been used in failed login attempts. + $attempts = count($rateLimiter->getAttempts($ipKey, 'ip')); + + $rateLimiter->registerRateLimitedAction($ipKey, 'ip')->registerRateLimitedAction($userKey); + + // Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited. + if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($userKey))) { + $this->login->setMessage($t->translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $rateLimiter->getInterval()]), 'error'); $this->setRedirect($this->grav['config']->get('plugins.login.route', '/')); return true; } - if ($this->authenticate($this->post)) { - $this->login->setMessage($t->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL')); + $rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($userKey); - $this->login->resetRateLimit($user, 'login_attempts'); + $this->login->setMessage($t->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL'), 'info'); $redirect = $this->grav['config']->get('plugins.login.redirect_after_login'); if (!$redirect) { @@ -143,6 +147,8 @@ public function taskLogin() } $this->setRedirect($redirect); } else { + /** @var User $user */ + $user = $this->grav['user']; if ($user->username) { $this->login->setMessage($t->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error'); $this->setRedirect($this->grav['config']->get('plugins.login.route_unauthorized', '/')); @@ -161,16 +167,8 @@ public function taskLogin() */ public function taskLogout() { - /** @var User $user */ - $user = $this->grav['user']; - - if (!$this->rememberMe->login()) { - $credentials = $user->get('username'); - $this->rememberMe->getStorage()->cleanAllTriplets($credentials); - } - $this->rememberMe->clearCookie(); + $this->login->logout(['remember_me' => true]); - $this->grav['session']->invalidate()->start(); $this->setRedirect('/'); return true; @@ -189,7 +187,7 @@ protected function taskForgot() $email = isset($data['email']) ? $data['email'] : ''; $user = !empty($email) ? User::find($email, ['email']) : null; - /** @var Language $l */ + /** @var Language $language */ $language = $this->grav['language']; $messages = $this->grav['messages']; @@ -224,11 +222,12 @@ protected function taskForgot() return true; } - $count = $this->grav['config']->get('plugins.login.max_pw_resets_count', 0); - $interval =$this->grav['config']->get('plugins.login.max_pw_resets_interval', 2); + $userKey = $user->username; + $rateLimiter = $this->login->getRateLimiter('pw_resets'); + $rateLimiter->registerRateLimitedAction($userKey); - if ($this->login->isUserRateLimited($user, 'pw_resets', $count, $interval)) { - $messages->add($language->translate(['PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $email, $interval]), 'error'); + if ($rateLimiter->isRateLimited($userKey)) { + $messages->add($language->translate(['PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $email, $rateLimiter->getInterval()]), 'error'); $this->setRedirect($this->grav['config']->get('plugins.login.route', '/')); return true; @@ -269,6 +268,7 @@ protected function taskForgot() * Handle the reset password action. * * @return bool True if the action was performed. + * @throws \Exception */ public function taskReset() { @@ -293,8 +293,7 @@ public function taskReset() return true; } - unset($user->hashed_password); - unset($user->reset); + unset($user->hashed_password, $user->reset); $user->password = $password; $user->validate(); @@ -337,46 +336,10 @@ public function taskReset() */ protected function authenticate($form) { - /** @var User $user */ - $user = $this->grav['user']; - - if (!$user->authenticated) { - $username = isset($form['username']) ? $form['username'] : $this->rememberMe->login(); - - // Normal login process - $user = User::find($username); - if ($user->exists() && !empty($form['username']) && !empty($form['password'])) { - // Authenticate user - $user->authenticated = $user->authenticate($form['password']); - - if ($user->authenticated) { + // Remove login nonce. + $form = array_diff_key($form, ['login-form-nonce' => true]); - // Authorize against user ACL - $user_authorized = $user->authorize('site.login'); - - if ($user_authorized) { - $this->grav['session']->user = $user; - - unset($this->grav['user']); - $this->grav['user'] = $user; - - // If the user wants to be remembered, create Rememberme cookie - if (!empty($form['rememberme'])) { - $this->rememberMe->createCookie($form['username']); - } else { - $this->rememberMe->clearCookie(); - $this->rememberMe->getStorage()->cleanAllTriplets($user->get('username')); - } - } - } - } - } - - // Authorize against user ACL - $user_authorized = $user->authorize('site.login'); - $user->authenticated = ($user->authenticated && $user_authorized); - - return $user->authenticated; + return $this->login->login($form, ['remember_me' => true])->authenticated; } /** @@ -401,44 +364,6 @@ public function setRedirect($path, $code = 303) $this->redirectCode = $code; } - /** - * Gets and sets the RememberMe class - * - * @param mixed $var A rememberMe instance to set - * - * @return RememberMe\RememberMe Returns the current rememberMe instance - */ - public function rememberMe($var = null) - { - if ($var !== null) { - $this->rememberMe = $var; - } - - if (!$this->rememberMe) { - /** @var Config $config */ - $config = $this->grav['config']; - - // Setup storage for RememberMe cookies - $storage = new RememberMe\TokenStorage(); - $this->rememberMe = new RememberMe\RememberMe($storage); - $this->rememberMe->setCookieName($config->get('plugins.login.rememberme.name')); - $this->rememberMe->setExpireTime($config->get('plugins.login.rememberme.timeout')); - - // Hardening cookies with user-agent and random salt or - // fallback to use system based cache key - $server_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown'; - $data = $server_agent . $config->get('security.salt', $this->grav['cache']->getKey()); - $this->rememberMe->setSalt(hash('sha512', $data)); - - // Set cookie with correct base path of Grav install - $cookie = new Cookie(); - $cookie->setPath($this->grav['base_url_relative'] ?: '/'); - $this->rememberMe->setCookie($cookie); - } - - return $this->rememberMe; - } - /** * Prepare and return POST data. * @@ -479,4 +404,45 @@ protected function jsonDecode(array $data) return $data; } + /** + * Gets and sets the RememberMe class + * + * @param mixed $var A rememberMe instance to set + * + * @return RememberMe\RememberMe Returns the current rememberMe instance + * @deprecated 3.0 Use $grav['login']->rememberMe() instead + */ + public function rememberMe($var = null) + { + $this->rememberMe = $this->login->rememberMe($var); + + return $this->rememberMe; + } + + /** + * Check if user may use password reset functionality. + * + * @param User $user + * @param $field + * @param $count + * @param $interval + * @return bool + * @deprecated 3.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class. + */ + protected function isUserRateLimited(User $user, $field, $count, $interval) + { + return $this->login->isUserRateLimited($user, $field, $count, $interval); + } + + /** + * Reset the rate limit counter + * + * @param User $user + * @param $field + * @deprecated 3.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class. + */ + protected function resetRateLimit(User $user, $field) + { + $this->login->resetRateLimit($user, $field); + } } diff --git a/classes/Events/UserLoginEvent.php b/classes/Events/UserLoginEvent.php new file mode 100644 index 0000000..5c25a81 --- /dev/null +++ b/classes/Events/UserLoginEvent.php @@ -0,0 +1,256 @@ + + (isset($items['credentials']) ? (array)$items['credentials'] : []) + ['username' => '', 'password' => ''], + 'options' => [], + 'authorize' => 'site.login', + 'status' => static::AUTHENTICATION_UNDEFINED, + 'user' => null, + 'message' => '' + ]; + + parent::__construct($items); + + if (!$this->offsetExists('user')) { + $this->offsetSet('user', User::load($this['credentials']['username'], false)); + } + } + + public function isSuccess() + { + $status = $this->offsetGet('status'); + $failure = static::AUTHENTICATION_FAILURE | static::AUTHENTICATION_CANCELLED | static::AUTHORIZATION_EXPIRED + | static::AUTHORIZATION_DENIED; + + return ($status & static::AUTHENTICATION_SUCCESS) && !($status & $failure); + } + + public function isDelayed() + { + return $this->isSuccess() && ($this->offsetGet('status') & static::AUTHORIZATION_DELAYED); + } + + /** + * @return int + */ + public function getStatus() + { + return (int)$this->offsetGet('status'); + } + + /** + * @param int $status + * @return $this + */ + public function setStatus($status) + { + $this->offsetSet('status', $this->offsetGet('status') | (int)$status); + + return $this; + } + + /** + * @return array + */ + public function getCredentials() + { + return $this->offsetGet('credentials') + ['username' => '', 'password' => '']; + } + + /** + * @param string $name + * @return mixed + */ + public function getCredential($name) + { + return isset($this->items['credentials'][$name]) ? $this->items['credentials'][$name] : null; + } + + /** + * @param string $name + * @param mixed $value + * @return $this + */ + public function setCredential($name, $value) + { + $this->items['credentials'][$name] = $value; + + return $this; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->offsetGet('options'); + } + + /** + * @param string $name + * @return mixed + */ + public function getOption($name) + { + return isset($this->items['options'][$name]) ? $this->items['options'][$name] : null; + } + + /** + * @param string $name + * @param mixed $value + * @return $this + */ + public function setOption($name, $value) + { + $this->items['options'][$name] = $value; + + return $this; + } + + /** + * @return User + */ + public function getUser() + { + return $this->offsetGet('user'); + } + + /** + * @param User $user + * @return $this + */ + public function setUser(User $user) + { + $this->offsetSet('user', $user); + + return $this; + } + + /** + * @return array + */ + public function getAuthorize() + { + return (array)$this->offsetGet('authorize'); + } + + /** + * @param string $message + * @return $this + */ + public function setMessage($message) + { + $this->items['message'] = (string)$message; + + return $this; + } + + /** + * Magic setter method + * + * @param mixed $offset Asset name value + * @param mixed $value Asset value + */ + public function __set($offset, $value) + { + $this->offsetSet($offset, $value); + } + + /** + * Magic getter method + * + * @param mixed $offset Asset name value + * @return mixed Asset value + */ + public function __get($offset) + { + return $this->offsetGet($offset); + } + + /** + * Magic method to determine if the attribute is set + * + * @param mixed $offset Asset name value + * @return boolean True if the value is set + */ + public function __isset($offset) + { + return $this->offsetExists($offset); + } + + /** + * Magic method to unset the attribute + * + * @param mixed $offset The name value to unset + */ + public function __unset($offset) + { + $this->offsetUnset($offset); + } +} diff --git a/classes/Login.php b/classes/Login.php index ac2cf81..f31a1ac 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -1,12 +1,13 @@ config = $this->grav['config']; $this->language = $this->grav['language']; $this->session = $this->grav['session']; - $this->user = $this->grav['user']; $this->uri = $this->grav['uri']; } + /** + * Login user. + * + * @param array $credentials + * @param array $options + * @return User + */ + public function login(array $credentials, array $options = []) + { + $grav = Grav::instance(); + + $eventOptions = [ + 'credentials' => $credentials, + 'options' => $options + ]; + + // Attempt to authenticate the user. + $event = new UserLoginEvent($eventOptions); + $grav->fireEvent('onUserLoginAuthenticate', $event); + + if ($event->isSuccess()) { + + // Make sure that event didn't mess up with the user authorization. + $user = $event->getUser(); + $user->authenticated = true; + $user->authorized = false; + + // Allow plugins to prevent login after successful authentication. + $event = new UserLoginEvent($event->toArray()); + $grav->fireEvent('onUserLoginAuthorize', $event); + } + + if ($event->isSuccess()) { + // User has been logged in, let plugins know. + $event = new UserLoginEvent($event->toArray()); + $grav->fireEvent('onUserLogin', $event); + + // Make sure that event didn't mess up with the user authorization. + $user = $event->getUser(); + $user->authenticated = true; + $user->authorized = $event->isDelayed(); + + } else { + // Allow plugins to log errors or do other tasks on failure. + $event = new UserLoginEvent($event->toArray()); + $grav->fireEvent('onUserLoginFailure', $event); + + // Make sure that event didn't mess up with the user authorization. + $user = $event->getUser(); + $user->authenticated = false; + $user->authorized = false; + } + + $user = $event->getUser(); + $user->def('language', 'en'); + + return $user; + } + + /** + * Logout user. + * + * @param array $options + * @param User $user + * @return User + */ + public function logout(array $options = [], User $user = null) + { + $grav = Grav::instance(); + + $eventOptions = [ + 'user' => $user ?: $grav['user'], + 'options' => $options + ]; + + $event = new UserLoginEvent($eventOptions); + + // Logout the user. + $grav->fireEvent('onUserLogout', $event); + + $user = $event->getUser(); + $user->authenticated = false; + + return $user; + } + + /** + * Get Current logged in user + * + * @return User + */ + public function getUser() + { + /** @var User $user */ + $user = $this->grav['user']; + if (!($user->get('access') || $user->get('groups'))) { + $user = User::load($user->get('username')); + } + return $user; + } + /** * Add message into the session queue. * @@ -85,6 +192,29 @@ public function messages($type = null) return $messages->fetch($type); } + /** + * Authenticate user. + * + * @param array $credentials Form fields. + * @param array $options + * + * @return bool + */ + public function authenticate($credentials, $options = ['remember_me' => true]) + { + $user = $this->login($credentials, $options); + + if ($user->authenticated) { + $this->setMessage($this->language->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL', + [$user->language]), 'info'); + + $redirect_route = $this->uri->route(); + $this->grav->redirect($redirect_route); + } + + return $user->authenticated; + } + /** * Create a new user file * @@ -126,15 +256,15 @@ public function register($data) return $user; } - /** * Handle the email to notify the user account creation to the site admin. * - * @param $user + * @param User $user * * @return bool True if the action was performed. + * @throws \RuntimeException */ - public function sendNotificationEmail($user) + public function sendNotificationEmail(User $user) { if (empty($user->email)) { throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD')); @@ -167,11 +297,12 @@ public function sendNotificationEmail($user) /** * Handle the email to welcome the new user * - * @param $user + * @param User $user * * @return bool True if the action was performed. + * @throws \RuntimeException */ - public function sendWelcomeEmail($user) + public function sendWelcomeEmail(User $user) { if (empty($user->email)) { throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD')); @@ -198,8 +329,9 @@ public function sendWelcomeEmail($user) * @param User $user * * @return bool True if the action was performed. + * @throws \RuntimeException */ - public function sendActivationEmail($user) + public function sendActivationEmail(User $user) { if (empty($user->email)) { throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD')); @@ -233,14 +365,79 @@ public function sendActivationEmail($user) return true; } + /** + * Gets and sets the RememberMe class + * + * @param mixed $var A rememberMe instance to set + * + * @return RememberMe Returns the current rememberMe instance + * @throws \InvalidArgumentException + */ + public function rememberMe($var = null) + { + if ($var !== null) { + $this->rememberMe = $var; + } + + if (!$this->rememberMe) { + /** @var Config $config */ + $config = $this->grav['config']; + + // Setup storage for RememberMe cookies + $storage = new TokenStorage; + $this->rememberMe = new RememberMe($storage); + $this->rememberMe->setCookieName($config->get('plugins.login.rememberme.name')); + $this->rememberMe->setExpireTime($config->get('plugins.login.rememberme.timeout')); + + // Hardening cookies with user-agent and random salt or + // fallback to use system based cache key + $server_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown'; + $data = $server_agent . $config->get('security.salt', $this->grav['cache']->getKey()); + $this->rememberMe->setSalt(hash('sha512', $data)); + + // Set cookie with correct base path of Grav install + $cookie = new Cookie; + $cookie->setPath($this->grav['base_url_relative'] ?: '/'); + $this->rememberMe->setCookie($cookie); + } + + return $this->rememberMe; + } + + /** + * @param string $context + * @param int $maxCount + * @param int $interval + * @return RateLimiter + */ + public function getRateLimiter($context, $maxCount = null, $interval = null) + { + if (!isset($this->rateLimiters[$context])) { + switch ($context) { + case 'login_attempts': + $maxCount = $this->grav['config']->get('plugins.login.max_login_count', 5); + $interval = $this->grav['config']->get('plugins.login.max_login_interval', 10); + break; + case 'pw_resets': + $maxCount = $this->grav['config']->get('plugins.login.max_pw_resets_count', 0); + $interval = $this->grav['config']->get('plugins.login.max_pw_resets_interval', 2); + break; + } + $this->rateLimiters[$context] = new RateLimiter($context, $maxCount, $interval); + } + + return $this->rateLimiters[$context]; + } + /** * Check if user may use password reset functionality. * - * @param User $user - * @param $field - * @param $count - * @param $interval + * @param User $user + * @param string $field + * @param int $count + * @param int $interval * @return bool + * @deprecated 3.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class. */ public function isUserRateLimited(User $user, $field, $count, $interval) { @@ -266,15 +463,56 @@ public function isUserRateLimited(User $user, $field, $count, $interval) return false; } + + public function isUserAuthorizedForPage($user, $page, $config = null) + { + $header = $page->header(); + $rules = isset($header->access) ? (array)$header->access : []; + + if ($config && $config->get('parent_acl')) { + // If page has no ACL rules, use its parent's rules + if (!$rules) { + $parent = $page->parent(); + while (!$rules and $parent) { + $header = $parent->header(); + $rules = isset($header->access) ? (array)$header->access : []; + $parent = $parent->parent(); + } + } + } + + // Continue to the page if it has no ACL rules. + if (!$rules) { + return true; + } + + // Continue to the page if user is authorized to access the page. + foreach ($rules as $rule => $value) { + if (is_array($value)) { + foreach ($value as $nested_rule => $nested_value) { + if ($user->authorize($rule . '.' . $nested_rule) == $nested_value) { + return true; + } + } + } else { + if ($user->authorize($rule) == $value) { + return true; + } + } + } + + return false; + } + /** - * Reset the rate limit counter + * Reset the rate limit counter. * - * @param User $user - * @param $field + * @param User $user + * @param string $field + * @deprecated 3.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class. */ public function resetRateLimit(User $user, $field) { $user->{$field} = []; } - } diff --git a/classes/LoginCache.php b/classes/LoginCache.php new file mode 100644 index 0000000..51d6309 --- /dev/null +++ b/classes/LoginCache.php @@ -0,0 +1,103 @@ +lifetime = (int)$defaultLifetime; + $this->driver = new FilesystemCache(Grav::instance()['locator']->findResource('cache://login/' . $namespace, true, true)); + } + + /** + * Fetches a value from the cache. + * + * @param string $key The unique key of this item in the cache. + * @param mixed $default Default value to return if the key does not exist. + * + * @return mixed The value of the item from the cache, or $default in case of cache miss. + */ + public function get($key, $default = null) + { + $value = $this->driver->fetch($key); + + // Doctrine cache does not differentiate between no result and cached 'false'. Make sure that we do. + return $value !== false || $this->driver->contains($key) ? $value : $default; + } + + /** + * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. + * + * @param string $key The key of the item to store. + * @param mixed $value The value of the item to store, must be serializable. + * @param null|int $ttl Optional. The TTL value of this item. + * + * @return bool True on success and false on failure. + */ + public function set($key, $value, $ttl = null) + { + $ttl = $ttl !== null ? (int)$ttl : $this->lifetime; + + return $this->driver->save($key, $value, $ttl); + } + + /** + * Determines whether an item is present in the cache. + * + * @param string $key The cache item key. + * + * @return bool + */ + public function has($key) + { + return $this->driver->contains($key); + } + + /** + * Delete an item from the cache by its unique key. + * + * @param string $key The unique cache key of the item to delete. + * + * @return bool True if the item was successfully removed. False if there was an error. + */ + public function delete($key) + { + return $this->driver->delete($key); + } + + /** + * Wipes clean the entire cache's keys. + * + * @return bool True on success and false on failure. + */ + public function clear() + { + return $this->driver->flushAll(); + } +} diff --git a/classes/RateLimiter.php b/classes/RateLimiter.php new file mode 100644 index 0000000..aafeb68 --- /dev/null +++ b/classes/RateLimiter.php @@ -0,0 +1,108 @@ +cache = new LoginCache($context, (int)$interval * 60); + $this->maxCount = (int) $maxCount; + $this->interval = (int) $interval; + } + + /** + * @return int + */ + public function getInterval() + { + return $this->interval; + } + + /** + * Check if user has hit rate limiter. Remember to use registerRateLimitedAction() before doing the check. + * + * @param string $key + * @param string $type + * @return bool + */ + public function isRateLimited($key, $type = 'username') + { + if (!$key || !$this->interval) { + return false; + } + + return $this->maxCount && count($this->getAttempts($key, $type)) > $this->maxCount; + } + + /** + * + * + * @param string $key + * @param string $type + * @return array + */ + public function getAttempts($key, $type = 'username') + { + return (array) $this->cache->get($type . $key, []); + } + + /** + * Register rate limited action. + * + * @param string $key + * @param string $type + * @return $this + */ + public function registerRateLimitedAction($key, $type = 'username') + { + if ($key && $this->interval) { + $tries = (array)$this->cache->get($type . $key, []); + $tries[] = time(); + + $this->cache->set($type . $key, $tries); + } + + return $this; + } + + /** + * Reset the user rate limit counter. + * + * @param string $key + * @param string $type + * @return $this + */ + public function resetRateLimit($key, $type = 'username') + { + if ($key) { + $this->cache->delete($type . $key); + } + + return $this; + } +} diff --git a/classes/RememberMe/RememberMe.php b/classes/RememberMe/RememberMe.php index 4690f55..27710b0 100644 --- a/classes/RememberMe/RememberMe.php +++ b/classes/RememberMe/RememberMe.php @@ -1,6 +1,6 @@ driver->fetch($id); - if (isset($tokens[$persistentToken]) && $tokens[$persistentToken] == $token) { + if (isset($tokens[$persistentToken]) && $tokens[$persistentToken] === $token) { return self::TRIPLET_FOUND; } diff --git a/cli/ChangePasswordCommand.php b/cli/ChangePasswordCommand.php index 24be533..fc850b5 100644 --- a/cli/ChangePasswordCommand.php +++ b/cli/ChangePasswordCommand.php @@ -1,6 +1,6 @@ Password Reset

Dear %1$s,

A request was made on %4$s to reset your password.


Click this to reset your password

Alternatively, copy the following URL into your browser's address bar:

%2$s


Kind regards,

%3$s

" SESSION: "“Remember Me”-Session" - REMEMBER_ME: Remember Me + REMEMBER_ME: "Remember Me" REMEMBER_ME_HELP: "Sets a persistent cookie on your browser to allow persistent-login authentication between sessions." REMEMBER_ME_STOLEN_COOKIE: "Someone else has used your login information to access this page! All sessions were logged out. Please log in with your credentials and check your data." - BUILTIN_CSS: Use built in CSS - BUILTIN_CSS_HELP: Include the CSS provided by the admin plugin - ROUTE: Login path - ROUTE_HELP: Custom route to a custom login page that your theme provides - ROUTE_REGISTER: Registration path - ROUTE_REGISTER_HELP: Route to the registration page. Set this if you want to use the built-in registration page. Leave it empty if you have your own registration form + BUILTIN_CSS: "Use built in CSS" + BUILTIN_CSS_HELP: "Include the CSS provided by the admin plugin" + ROUTE: "Login path" + ROUTE_HELP: "Custom route to a custom login page that your theme provides" + ROUTE_REGISTER: "Registration path" + ROUTE_REGISTER_HELP: "Route to the registration page. Set this if you want to use the built-in registration page. Leave it empty if you have your own registration form" USERNAME_NOT_VALID: "Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed" USERNAME_NOT_AVAILABLE: "Username %s already exists, please pick another username" PASSWORD_NOT_VALID: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" @@ -74,7 +74,7 @@ en: REDIRECT_AFTER_LOGIN_HELP: "Custom route to redirect after login" REDIRECT_AFTER_REGISTRATION: "Redirect after registration" REDIRECT_AFTER_REGISTRATION_HELP: "Custom route to redirect after the registration" - OPTIONS: Options + OPTIONS: "Options" EMAIL_VALIDATION_MESSAGE: "Must be a valid email address" PASSWORD_VALIDATION_MESSAGE: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" TIMEOUT_HELP: "Sets the session timeout in seconds when Remember Me is enabled and checked by the user. Minimum is 604800 which means 1 week" @@ -107,6 +107,10 @@ en: ROUTE_RESET: "Reset password route" ROUTE_PROFILE: "User profile route" ROUTE_ACTIVATE: "User activation route" + LOGGED_OUT: "You have been successfully logged out..." + PAGE_RESTRICTED: "Access is restricted, please login..." + DYNAMIC_VISIBILITY: "Dynamic Page Visibility" + DYNAMIC_VISIBILITY_HELP: "Allows dynamic processing of page visibility base on access rules if login.visibility_requires_access is set to true on a page" de: PLUGIN_LOGIN: diff --git a/login.php b/login.php index 4a790e1..6e241b4 100755 --- a/login.php +++ b/login.php @@ -1,13 +1,12 @@ [['initializeSession', 10000], ['initializeLogin', 1000]], - 'onTask.login.login' => ['loginController', 0], - 'onTask.login.forgot' => ['loginController', 0], - 'onTask.login.logout' => ['loginController', 0], - 'onTask.login.reset' => ['loginController', 0], - 'onPagesInitialized' => ['storeReferrerPage', 0], - 'onPageInitialized' => ['authorizePage', 0], - 'onPageFallBackUrl' => ['authorizeFallBackUrl', 0], - 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], - 'onTwigSiteVariables' => ['onTwigSiteVariables', -100000], - 'onFormProcessed' => ['onFormProcessed', 0] + 'onPluginsInitialized' => [['initializeSession', 10000], ['initializeLogin', 1000]], + 'onTask.login.login' => ['loginController', 0], + 'onTask.login.forgot' => ['loginController', 0], + 'onTask.login.logout' => ['loginController', 0], + 'onTask.login.reset' => ['loginController', 0], + 'onPagesInitialized' => [['storeReferrerPage', 0], ['pageVisibility', 0]], + 'onPageInitialized' => ['authorizePage', 0], + 'onPageFallBackUrl' => ['authorizeFallBackUrl', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], + 'onTwigSiteVariables' => ['onTwigSiteVariables', -100000], + 'onFormProcessed' => ['onFormProcessed', 0], + 'onUserLoginAuthenticate' => [['userLoginAuthenticateByRememberMe', 10001], ['userLoginAuthenticateByEmail', 10000], ['userLoginAuthenticate', 0]], + 'onUserLoginAuthorize' => ['userLoginAuthorize', 0], + 'onUserLoginFailure' => ['userLoginFailure', 0], + 'onUserLogin' => ['userLogin', 0], + 'onUserLogout' => ['userLogout', 0], ]; } /** - * Initialize login plugin if path matches. + * [onPluginsInitialized] Initialize login plugin if path matches. + * @throws \RuntimeException */ public function initializeSession() { - /** @var Config $config */ - $config = $this->grav['config']; - // Check to ensure sessions are enabled. - if (!$config->get('system.session.enabled')) { + if (!$this->config->get('system.session.enabled')) { throw new \RuntimeException('The Login plugin requires "system.session" to be enabled'); } // Autoload classes $autoload = __DIR__ . '/vendor/autoload.php'; if (!is_file($autoload)) { - throw new \Exception('Login Plugin failed to load. Composer dependencies not met.'); + throw new \RuntimeException('Login Plugin failed to load. Composer dependencies not met.'); } require_once $autoload; - // Define current user service. - $this->grav['user'] = function ($c) { - /** @var Grav $c */ + // Define login service. + $this->grav['login'] = function (Grav $c) { + return new Login($c); + }; + // Define current user service. + $this->grav['user'] = function (Grav $c) { $session = $c['session']; - if (!isset($session->user)) { - $session->user = new User; - - if ($c['config']->get('plugins.login.rememberme.enabled')) { - $controller = new Controller($c, ''); - $rememberMe = $controller->rememberMe(); - - // If we can present the correct tokens from the cookie, we are logged in - $username = $rememberMe->login(); - if ($username) { - // Normal login process - $user = User::load($username); - if ($user->exists()) { - // There is a chance that an attacker has stolen - // the login token, so we store the fact that - // the user was logged in via RememberMe - // (instead of login form) - $session->remember_me = $rememberMe; - $session->user = $user; - } - } - - // Check if the token was invalid - if ($rememberMe->loginTokenWasInvalid()) { - $controller->setMessage($c['language']->translate('PLUGIN_LOGIN.REMEMBER_ME_STOLEN_COOKIE')); - } - } + if (empty($session->user)) { + $session->user = $c['login']->login(['username' => ''], ['remember_me' => true, 'remember_me_login' => true]); } return $session->user; @@ -128,19 +110,16 @@ public function initializeSession() } /** - * Initialize login plugin if path matches. + * [onPluginsInitialized] Initialize login plugin if path matches. + * @throws \RuntimeException */ public function initializeLogin() { + $this->login = $this->grav['login']; + /** @var Uri $uri */ $uri = $this->grav['uri']; - //Initialize Login Object - $this->login = new Login($this->grav); - - //Store Login Object in Grav - $this->grav['login'] = $this->login; - // Admin has its own login; make sure we're not in admin. if (!isset($this->grav['admin'])) { $this->route = $this->config->get('plugins.login.route'); @@ -197,6 +176,34 @@ public function initializeLogin() } } + /** + * Optional ability to dynamically set visibility based on page access and page header + * that states `login.visibility_requires_access: true` + * + * @param Event $e + */ + public function pageVisibility(Event $e) + { + if ($this->config->get('plugins.login.dynamic_page_visibility')) { + $pages = $e['pages']; + $user = $this->grav['login']->getUser(); + + foreach ($pages->instances() as $page) { + $header = $page->header(); + if (isset($header->login['visibility_requires_access'])) { + $config = $this->mergeConfig($page); + $access = $this->grav['login']->isUserAuthorizedForPage($user, $page, $config); + if ($access == false) { + $page->visible(false); + } + } + } + } + } + + /** + * [onPagesInitialized] + */ public function storeReferrerPage() { $invalid_redirect_routes = [ @@ -206,11 +213,13 @@ public function storeReferrerPage() $this->config->get('plugins.login.route_forgot') ?: '/forgot_password', $this->config->get('plugins.login.route_reset') ?: '/reset_password', ]; - $current_route = $this->grav['uri']->route(); + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $current_route = $uri->route(); - if (!in_array($current_route, $invalid_redirect_routes)) { + if (!in_array($current_route, $invalid_redirect_routes, true)) { $allowed = true; /** @var Page $page */ @@ -223,15 +232,15 @@ public function storeReferrerPage() } if ($allowed && $page->routable()) { - $this->grav['session']->redirect_after_login = $page->route() . $this->grav['uri']->params() ?: ''; + $this->grav['session']->redirect_after_login = $page->route() . ($uri->params() ?: ''); } } - } } /** * Add Login page + * @throws \Exception */ public function addLoginPage() { @@ -242,7 +251,7 @@ public function addLoginPage() if (!$page) { // Only add login page if it hasn't already been defined. $page = new Page; - $page->init(new \SplFileInfo(__DIR__ . "/pages/login.md")); + $page->init(new \SplFileInfo(__DIR__ . '/pages/login.md')); $page->slug(basename($this->route)); $pages->addPage($page, $this->route); @@ -251,6 +260,7 @@ public function addLoginPage() /** * Add Login page + * @throws \Exception */ public function addForgotPage() { @@ -262,7 +272,7 @@ public function addForgotPage() if (!$page) { // Only add forgot page if it hasn't already been defined. $page = new Page; - $page->init(new \SplFileInfo(__DIR__ . "/pages/forgot.md")); + $page->init(new \SplFileInfo(__DIR__ . '/pages/forgot.md')); $page->slug(basename($route)); $pages->addPage($page, $route); @@ -271,6 +281,7 @@ public function addForgotPage() /** * Add Reset page + * @throws \Exception */ public function addResetPage() { @@ -291,7 +302,7 @@ public function addResetPage() if (!$page) { // Only add login page if it hasn't already been defined. $page = new Page; - $page->init(new \SplFileInfo(__DIR__ . "/pages/reset.md")); + $page->init(new \SplFileInfo(__DIR__ . '/pages/reset.md')); $page->slug(basename($route)); $pages->addPage($page, $route); @@ -300,6 +311,7 @@ public function addResetPage() /** * Add Register page + * @throws \Exception */ public function addRegisterPage() { @@ -311,7 +323,7 @@ public function addRegisterPage() if (!$page) { $page = new Page; - $page->init(new \SplFileInfo(__DIR__ . "/pages/register.md")); + $page->init(new \SplFileInfo(__DIR__ . '/pages/register.md')); $page->template('form'); $page->slug(basename($route)); @@ -321,6 +333,7 @@ public function addRegisterPage() /** * Handle user activation + * @throws \RuntimeException */ public function handleUserActivation() { @@ -344,7 +357,7 @@ public function handleUserActivation() $token = $uri->param('token'); $user = User::load($username); - if (!$user->activation_token) { + if (empty($user->activation_token)) { $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST'); $messages->add($message, 'error'); } else { @@ -485,61 +498,24 @@ public function authorizeFallBackUrl() } /** - * Authorize Page + * [onPageInitialized] Authorize Page */ public function authorizePage() { - /** @var User $user */ - $user = $this->grav['user']; - if (!($user->get('access') || $user->get('groups'))) { - $user = User::load($user->get('username')); + if (!$this->authenticated) { + return true; } + /** @var User $user */ + $user = $this->grav['login']->getUser(); + /** @var Page $page */ $page = $this->grav['page']; - if (!$page) { - return; - } - - $header = $page->header(); - $rules = isset($header->access) ? (array)$header->access : []; - - $config = $this->mergeConfig($page); - - if ($config->get('parent_acl')) { - // If page has no ACL rules, use its parent's rules - if (!$rules) { - $parent = $page->parent(); - while (!$rules and $parent) { - $header = $parent->header(); - $rules = isset($header->access) ? (array)$header->access : []; - $parent = $parent->parent(); - } - } - } - - // Continue to the page if it has no ACL rules. - if (!$rules) { - return; - } - - // Continue to the page if user is authorized to access the page. - foreach ($rules as $rule => $value) { - if (is_array($value)) { - foreach ($value as $nested_rule => $nested_value) { - if ($user->authorize($rule . '.' . $nested_rule) == $nested_value) { - return; - } - } - } else { - if ($user->authorize($rule) == $value) { - return; - } - } + if (!$page || $this->grav['login']->isUserAuthorizedForPage($user, $page, $this->mergeConfig($page))) { + return true; } - // If this is not an HTML page request, simply throw a 403 error $uri_extension = $this->grav['uri']->extension('html'); $supported_types = $this->config->get('media.types'); @@ -553,14 +529,11 @@ public function authorizePage() $this->grav->redirect($this->route, 302); } - /** @var Language $l */ - $l = $this->grav['language']; - /** @var Twig $twig */ $twig = $this->grav['twig']; // Reset page with login page. - if (!$user->authenticated) { + if (empty($user->authenticated)) { if ($this->route) { $page = $this->grav['pages']->dispatch($this->route); @@ -571,10 +544,10 @@ public function authorizePage() // Get the admin Login page is needed, else teh default if ($this->isAdmin()) { - $login_file = $this->grav['locator']->findResource("plugins://admin/pages/admin/login.md"); + $login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md'); $page->init(new \SplFileInfo($login_file)); } else { - $page->init(new \SplFileInfo(__DIR__ . "/pages/login.md")); + $page->init(new \SplFileInfo(__DIR__ . '/pages/login.md')); } $page->slug(basename($this->route)); @@ -586,6 +559,8 @@ public function authorizePage() $twig->twig_vars['form'] = new Form($page); } else { + /** @var Language $l */ + $l = $this->grav['language']; $this->grav['messages']->add($l->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error'); $this->authorized = false; $twig->twig_vars['notAuthorized'] = true; @@ -594,9 +569,8 @@ public function authorizePage() } } - /** - * Add twig paths to plugin templates. + * [onTwigTemplatePaths] Add twig paths to plugin templates. */ public function onTwigTemplatePaths() { @@ -605,7 +579,7 @@ public function onTwigTemplatePaths() } /** - * Set all twig variables for generating output. + * [onTwigSiteVariables] Set all twig variables for generating output. */ public function onTwigSiteVariables() { @@ -618,7 +592,7 @@ public function onTwigSiteVariables() $extension = $extension ?: 'html'; if (!$this->authenticated) { - $twig->template = "login." . $extension . ".twig"; + $twig->template = "login.{$extension}.twig"; } // add CSS for frontend if required @@ -626,7 +600,7 @@ public function onTwigSiteVariables() $this->grav['assets']->add('plugin://login/css/login.css'); } - $task = $this->grav['uri']->param('task'); + $task = $this->grav['uri']->param('task') ?: (isset($_POST['task']) ? $_POST['task'] : ''); $task = substr($task, strlen('login.')); if ($task === 'reset') { $username = $this->grav['uri']->param('user'); @@ -636,7 +610,8 @@ public function onTwigSiteVariables() $twig->twig_vars['username'] = $username; $twig->twig_vars['token'] = $token; } - + } elseif ($task === 'login') { + $twig->twig_vars['username'] = isset($_POST['username']) ? $_POST['username'] : ''; } } @@ -644,15 +619,18 @@ public function onTwigSiteVariables() * Process the user registration, triggered by a registration form * * @param Form $form + * @throws \RuntimeException */ private function processUserRegistration($form, Event $event) { + $language = $this->grav['language']; + if (!$this->config->get('plugins.login.enabled')) { - throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED')); + throw new \RuntimeException($language->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED')); } if (!$this->config->get('plugins.login.user_registration.enabled')) { - throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED')); + throw new \RuntimeException($language->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED')); } $data = []; @@ -662,7 +640,7 @@ private function processUserRegistration($form, Event $event) if (file_exists($this->grav['locator']->findResource('account://' . $username . YAML_EXT))) { $this->grav->fireEvent('onFormValidationError', new Event([ 'form' => $form, - 'message' => $this->grav['language']->translate([ + 'message' => $language->translate([ 'PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE', $username ]) @@ -678,7 +656,7 @@ private function processUserRegistration($form, Event $event) if ($form->value('password1') !== $form->value('password2')) { $this->grav->fireEvent('onFormValidationError', new Event([ 'form' => $form, - 'message' => $this->grav['language']->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH') + 'message' => $language->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH') ])); $event->stopPropagation(); @@ -687,16 +665,16 @@ private function processUserRegistration($form, Event $event) $data['password'] = $form->value('password1'); } - $fields = $this->config->get('plugins.login.user_registration.fields', []); + $fields = (array)$this->config->get('plugins.login.user_registration.fields', []); foreach ($fields as $field) { // Process value of field if set in the page process.register_user - $default_values = $this->config->get('plugins.login.user_registration.default_values'); + $default_values = (array)$this->config->get('plugins.login.user_registration.default_values'); if ($default_values) { foreach ($default_values as $key => $param) { $values = explode(',', $param); - if ($key == $field) { + if ($key === $field) { $data[$field] = $values; } } @@ -710,8 +688,7 @@ private function processUserRegistration($form, Event $event) if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2', false) ) { - unset($data['password1']); - unset($data['password2']); + unset($data['password1'], $data['password2']); } if ($this->config->get('plugins.login.user_registration.options.set_user_disabled', false)) { @@ -743,10 +720,9 @@ private function processUserRegistration($form, Event $event) * Save user profile information * * @param Form $form - * @param Event $event * @return bool */ - private function processUserProfile($form, Event $event) + private function processUserProfile($form) { $user = $this->grav['user']; $user->merge($form->getData()->toArray()); @@ -762,11 +738,13 @@ private function processUserProfile($form, Event $event) } /** - * Process a registration form. Handles the following actions: + * [onFormProcessed] Process a registration form. Handles the following actions: * * - register_user: registers a user + * - update_user: updates user profile * * @param Event $event + * @throws \RuntimeException */ public function onFormProcessed(Event $event) { @@ -778,8 +756,145 @@ public function onFormProcessed(Event $event) $this->processUserRegistration($form, $event); break; case 'update_user': - $this->processUserProfile($form, $event); + $this->processUserProfile($form); break; } } + + /** + * @param UserLoginEvent $event + * @throws \RuntimeException + */ + public function userLoginAuthenticateByRememberMe(UserLoginEvent $event) + { + // Check that we're logging in with remember me. + if (!$event->getOption('remember_me_login') || !$event->getOption('remember_me') || $this->isAdmin()) { + return; + } + + // Only use remember me if user isn't set and feature is enabled. + if ($this->grav['config']->get('plugins.login.rememberme.enabled') && !$event->getUser()->exists()) { + /** @var RememberMe $rememberMe */ + $rememberMe = $this->grav['login']->rememberMe(); + $username = $rememberMe->login(); + + if ($rememberMe->loginTokenWasInvalid()) { + // Token was invalid. We will display error page as this was likely an attack. + throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REMEMBER_ME_STOLEN_COOKIE'), 403); + } + + if ($username === false) { + // User has not been remembered, there is no point of continuing. + $event->setStatus($event::AUTHENTICATION_FAILURE); + $event->stopPropagation(); + + return; + } + + // Allow remember me to work with different login methods. + $event->setCredential('username', $username); + $event->setUser(User::load($username, false)); + } + } + + public function userLoginAuthenticateByEmail(UserLoginEvent $event) + { + if (($username = $event->getCredential('username')) && !$event->getUser()->exists()) { + $event->setUser(User::find($username)); + } + } + + public function userLoginAuthenticate(UserLoginEvent $event) + { + $user = $event->getUser(); + $credentials = $event->getCredentials(); + + if (!$user->exists()) { + // Never let non-existing users to pass the authentication. + // Higher level plugins may override this behavior by stopping propagation. + $event->setStatus($event::AUTHENTICATION_FAILURE); + $event->stopPropagation(); + + return; + } + + // Never let empty password to pass the authentication. + // Higher level plugins may override this behavior by stopping propagation. + if (empty($credentials['password'])) { + $event->setStatus($event::AUTHENTICATION_FAILURE); + $event->stopPropagation(); + + return; + } + + // Try default user authentication. Stop propagation if authentication succeeds. + if ($user->authenticate($credentials['password'])) { + $event->setStatus($event::AUTHENTICATION_SUCCESS); + $event->stopPropagation(); + + return; + } + + // If authentication status is undefined, lower level event handlers may still be able to authenticate user. + } + + public function userLoginAuthorize(UserLoginEvent $event) + { + // Always block access if authorize defaulting to site.login fails. + $user = $event->getUser(); + foreach ($event->getAuthorize() as $authorize) { + if (!$user->authorize($authorize)) { + $event->setStatus($event::AUTHORIZATION_DENIED); + $event->stopPropagation(); + + return; + } + } + } + + public function userLoginFailure(UserLoginEvent $event) + { + $this->grav['session']->user = User::load('', false); + } + + public function userLogin(UserLoginEvent $event) + { + $session = $this->grav['session']; + $session->user = $event->getUser(); + + if ($event->getOption('remember_me')) { + /** @var Login $login */ + $login = $this->grav['login']; + + $session->remember_me = (bool)$event->getOption('remember_me_login'); + + // If the user wants to be remembered, create Rememberme cookie. + $username = $event->getUser()->get('username'); + if ($event->getCredential('rememberme')) { + $login->rememberMe()->createCookie($username); + } else { + $login->rememberMe()->getStorage()->cleanAllTriplets($username); + $login->rememberMe()->clearCookie(); + } + } + } + + public function userLogout(UserLoginEvent $event) + { + if ($event->getOption('remember_me')) { + /** @var Login $login */ + $login = $this->grav['login']; + + if (!$login->rememberMe()->login()) { + $login->rememberMe()->getStorage()->cleanAllTriplets($event->getUser()->get('username')); + } + $login->rememberMe()->clearCookie(); + } + + $this->grav['session']->invalidate()->start(); + $this->grav['session']->user = User::load('', false); + + $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.LOGGED_OUT'), + 'info'); + } } diff --git a/login.yaml b/login.yaml index 98593d3..b27555f 100644 --- a/login.yaml +++ b/login.yaml @@ -9,6 +9,8 @@ route_reset: '/reset_password' route_profile: '/user_profile' route_register: '/user_register' route_unauthorized: '/user_unauthorized' + +dynamic_page_visibility: false parent_acl: false protect_protected_page_media: false diff --git a/pages/login.md b/pages/login.md index 648dc03..3e20ac3 100644 --- a/pages/login.md +++ b/pages/login.md @@ -25,4 +25,3 @@ form: # User Login -This page is restricted... diff --git a/pages/register.md b/pages/register.md index 5988d3c..36bdaff 100644 --- a/pages/register.md +++ b/pages/register.md @@ -4,39 +4,35 @@ login_redirect_here: false form: fields: - - - name: username + fullname: + type: text + validate: + required: true + + + username: type: text - id: username - placeholder: "Choose a username" validate: required: true message: PLUGIN_LOGIN.USERNAME_NOT_VALID config-pattern@: system.username_regex - - - name: email + email: type: email - id: email - placeholder: "Enter your email" validate: required: true message: PLUGIN_LOGIN.EMAIL_VALIDATION_MESSAGE - - - name: password1 + password1: type: password - id: password1 label: Enter a password validate: required: true message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE config-pattern@: system.pwd_regex - - - name: password2 + password2: type: password - id: password2 label: Enter the password again validate: required: true @@ -53,7 +49,10 @@ form: process: register_user: true - message: "You are logged in" + message: "Thanks for registering..." + reset: true --- # Register + +Create a new user account by entering all the required fields below: diff --git a/templates/login.json.twig b/templates/login.json.twig index 05cb2e4..14dbf95 100644 --- a/templates/login.json.twig +++ b/templates/login.json.twig @@ -1,5 +1,5 @@ {%- if not grav.user.authenticated -%} -{"code":401,"status":"unauthenticated","message":"Authentication required","login":{{ include('partials/login-form.html.twig')|trim|json_encode }}} +{"code":401,"status":"unauthenticated","error":{"message":"Authentication required","login":{{ include('partials/login-form.html.twig')|trim|json_encode }}}} {%- else -%} {"code":200,"status":"authenticated","message":"You have been authenticated"} {%- endif -%} \ No newline at end of file diff --git a/templates/partials/login-form.html.twig b/templates/partials/login-form.html.twig index cd17ec8..b556bed 100755 --- a/templates/partials/login-form.html.twig +++ b/templates/partials/login-form.html.twig @@ -11,7 +11,7 @@ {% else %} {{ content|raw }} -
+ {% if grav.twig.plugins_hooked_loginPage %} {% for label in grav.twig.plugins_hooked_loginPage %} {% include label %} @@ -19,14 +19,13 @@ {% endif %} {% for field in form.fields %} + {% set value = field.name == 'username' ? username : '' %} {% if field.type %} -
- {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} -
+ {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} {% endif %} {% endfor %} -
+
{% if config.plugins.login.rememberme.enabled and page.header.form.login.rememberme ?? true %}
@@ -37,10 +36,10 @@ {% endif %} {% if page.header.form.login.forgot_button ?? true %} - {{ 'PLUGIN_LOGIN.BTN_FORGOT'|t }} + {{ 'PLUGIN_LOGIN.BTN_FORGOT'|t }} {% endif %} - +
{{ nonce_field('login-form', 'login-form-nonce')|raw }} diff --git a/templates/partials/login-status.html.twig b/templates/partials/login-status.html.twig index 5e7e73b..7c06016 100755 --- a/templates/partials/login-status.html.twig +++ b/templates/partials/login-status.html.twig @@ -1,5 +1,5 @@ diff --git a/templates/partials/messages.html.twig b/templates/partials/messages.html.twig index 7727bf7..61fbf67 100644 --- a/templates/partials/messages.html.twig +++ b/templates/partials/messages.html.twig @@ -1,3 +1,16 @@ -{% for message in grav.messages.fetch %} -
{{ message.message|raw }}
-{% endfor %} \ No newline at end of file +{% if grav.messages.all %} +
+ {% for message in grav.messages.fetch %} + + {% set scope = message.scope|e %} + + {% set type_mapping = {'info':'green', 'error': 'red', 'warning': 'yellow'} %} + {% set type = type_mapping[scope] %} + + {% set icon_mapping = {'info':'check', 'error': 'ban', 'warning': 'exclamation-triangle'} %} + {% set icon = icon_mapping[scope] %} + +

{{ message.message|raw }}

+ {% endfor %} +
+{% endif %} diff --git a/templates/partials/reset-form.html.twig b/templates/partials/reset-form.html.twig index c39c0f5..39d16b3 100644 --- a/templates/partials/reset-form.html.twig +++ b/templates/partials/reset-form.html.twig @@ -4,7 +4,7 @@ {% include 'partials/messages.html.twig' %} - + {% for field in form.fields %} {% set value = attribute(grav.twig.twig_vars, field.name) is defined ? attribute(grav.twig.twig_vars, field.name) : null %} From 350651d844e12702063093f15c5f5bd6ff6eda0c Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Wed, 8 Nov 2017 12:22:56 -0700 Subject: [PATCH 02/19] Improvements in registration --- README.md | 42 +++++++++++++++++++++++++++++++++++++----- languages.yaml | 1 + login.php | 43 +++++++++++++++++++++++++++++++------------ login.yaml | 4 ++++ 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f8183a7..df58420 100644 --- a/README.md +++ b/README.md @@ -262,10 +262,9 @@ form: reset: true --- -# Register +# Registration of Users Create a new user account by entering all the required fields below: -``` This is a normal form. The only thing different from a contact form or another form that you might write on your site is the process field `register_user`, which takes care of processing the user registration. @@ -284,6 +283,22 @@ You can avoid having 2 fields for the password, which by the way is a recommende Last important thing before the registration is correctly setup: make sure in the Login plugin settings you have the user registration enabled, otherwise the registration will trigger an error, as by default user registration is DISABLED. + +# Registration Options + +There are several options that can be configured when registering users via `user/plugins/login.yaml`, they are pretty self-explanatory: + +``` +user_registration: + options: + validate_password1_and_password2: true + set_user_disabled: false + login_after_registration: true + send_activation_email: false + send_notification_email: false + send_welcome_email: false +``` + ## Sending an activation email By default the registration process adds a new user, and sets it as enabled. @@ -307,6 +322,17 @@ The content of the notification email is defined in the language file, strings ` Note: if the activation email is enabled, the notification email to be sent upon the account activation action (when the user clicks the link to activate the account) +## Default Access + +To control what access your users have upon registering you can edit the `user_registration.access:` attribute in the `user/plugins/login.yaml`. The default is simply `site.login: true`: + +``` +user_registration: + access: + site: + login: 'true' +``` + ## Adding your own fields If you want to add your own custom fields to the registration form, just add fields to the form like you would with any other form. @@ -316,13 +342,13 @@ Then, to let the Login plugin add those fields to the user yaml file, you also n By default we have ``` +user_registration: + fields: - 'username' - 'password' - 'email' - 'fullname' - 'title' - - 'access' - - 'state' ``` Add your own as you prefer, to build any custom registration form you can think of. @@ -331,7 +357,13 @@ Add your own as you prefer, to build any custom registration form you can think If you want to pre-fill a field, without showing it to the user in the form, you could set it as an hidden field. But the user could see it - and modify it via the browser dev tools. -To add a field and make sure the user cannot modify it, add it to "Default values" list. +To add a field and make sure the user cannot modify it, add it to "default_values" list: + +``` +user_registration: + default_values: + title: "Newbie User" +``` ## Login users directly after the registration diff --git a/languages.yaml b/languages.yaml index 02b9eb7..763458b 100755 --- a/languages.yaml +++ b/languages.yaml @@ -35,6 +35,7 @@ en: ROUTE_REGISTER_HELP: "Route to the registration page. Set this if you want to use the built-in registration page. Leave it empty if you have your own registration form" USERNAME_NOT_VALID: "Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed" USERNAME_NOT_AVAILABLE: "Username %s already exists, please pick another username" + EMAIL_NOT_AVAILABLE: "Email address %s already exists, please pick another email address" PASSWORD_NOT_VALID: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" PASSWORDS_DO_NOT_MATCH: "Passwords do not match. Double-check you entered the same password twice" USER_NEEDS_EMAIL_FIELD: "The user needs an email field" diff --git a/login.php b/login.php index 6e241b4..d053527 100755 --- a/login.php +++ b/login.php @@ -626,18 +626,17 @@ private function processUserRegistration($form, Event $event) $language = $this->grav['language']; if (!$this->config->get('plugins.login.enabled')) { - throw new \RuntimeException($language->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED')); + throw new \RuntimeException($language->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED')); } if (!$this->config->get('plugins.login.user_registration.enabled')) { throw new \RuntimeException($language->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED')); } - $data = []; + // Check for existing username $username = $form->value('username'); - $data['username'] = $username; - - if (file_exists($this->grav['locator']->findResource('account://' . $username . YAML_EXT))) { + $existing_username = User::find($username,['username']); + if ($existing_username->exists()) { $this->grav->fireEvent('onFormValidationError', new Event([ 'form' => $form, 'message' => $language->translate([ @@ -646,10 +645,29 @@ private function processUserRegistration($form, Event $event) ]) ])); $event->stopPropagation(); + return; + } + // Check for existing email + $email = $form->value('email'); + $existing_email = User::find($email,['email']); + if ($existing_email->exists()) { + $this->grav->fireEvent('onFormValidationError', new Event([ + 'form' => $form, + 'message' => $language->translate([ + 'PLUGIN_LOGIN.EMAIL_NOT_AVAILABLE', + $email + ]) + ])); + $event->stopPropagation(); return; } + $data = []; + $data['username'] = $username; + + + // if multiple password fields, check they match and set password field from it if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2', false) ) { @@ -672,9 +690,13 @@ private function processUserRegistration($form, Event $event) $default_values = (array)$this->config->get('plugins.login.user_registration.default_values'); if ($default_values) { foreach ($default_values as $key => $param) { - $values = explode(',', $param); if ($key === $field) { + if (is_array($param)) { + $values = explode(',', $param); + } else { + $values = $param; + } $data[$field] = $values; } } @@ -685,18 +707,13 @@ private function processUserRegistration($form, Event $event) } } - if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2', - false) - ) { - unset($data['password1'], $data['password2']); - } - if ($this->config->get('plugins.login.user_registration.options.set_user_disabled', false)) { $data['state'] = 'disabled'; } else { $data['state'] = 'enabled'; } + $this->grav->fireEvent('onUserLoginRegisterData', new Event(['data' => &$data])); $user = $this->login->register($data); if ($this->config->get('plugins.login.user_registration.options.send_activation_email', false)) { @@ -710,6 +727,8 @@ private function processUserRegistration($form, Event $event) } } + $this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user])); + $redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration', false); if ($redirect) { $this->grav->redirect($redirect); diff --git a/login.yaml b/login.yaml index b27555f..902cf30 100644 --- a/login.yaml +++ b/login.yaml @@ -23,6 +23,10 @@ user_registration: - 'email' - 'fullname' - 'title' + - 'level' + + default_values: + level: Newbie access: site: From aa2abf84863c990ed9f4b1deb18bbfb0fb2f3816 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Wed, 8 Nov 2017 15:08:49 -0700 Subject: [PATCH 03/19] placeholder for redirect_after_registraiton --- login.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/login.yaml b/login.yaml index 902cf30..3b381e4 100644 --- a/login.yaml +++ b/login.yaml @@ -32,6 +32,8 @@ user_registration: site: login: 'true' + redirect_after_registration: '' + options: validate_password1_and_password2: true set_user_disabled: false From 89cd8672209de0d6dd48688904689a1fe73e1bde Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 10 Nov 2017 11:11:38 +0200 Subject: [PATCH 04/19] Add new events to README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index df58420..e8ad0f6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ These are available via GPM, and because the plugin has dependencies you just ne $ bin/gpm install login ``` -# Changes in version 2.4 +# Changes in version 2.5 Added new `$grav['login']->login()` and `$grav['login']->logout()` functions for you to use. @@ -23,6 +23,8 @@ They use following events which can be hooked by plugins: * `onUserLoginFailure` Allows plugins to include their own logic when user authentication failed. * `onUserLogin` Allows plugins to include their own logic when user logs in. * `onUserLogout` Allows plugins to include their own logic when user logs out. +* `onUserLoginRegisterData` Allows plugins to include their own data to be added to the user object during registration. +* `onUserLoginRegistered` Allows plugins to hook into user registration just before the redirect. # Changes in version 2.0 From 10864c784239402b336d1fc164ac36db681962f6 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 10 Nov 2017 12:00:55 +0200 Subject: [PATCH 05/19] Do not send nonce with activation link, email app can open the link in another browser Use Login::login() function when login after activation option is turned on --- CHANGELOG.md | 2 ++ classes/Login.php | 10 +--------- login.php | 41 ++++++++++++++++++++++++----------------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ddc32a..a8110ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ 1. [](#improved) * Remember entered username if login fails * Improved rate limiter to work without sessions and against distributed attacks +1. [](#bugfix) + * Do not send nonce with activation link, email app can open the link in another browser # v2.4.3 ## 10/11/2017 diff --git a/classes/Login.php b/classes/Login.php index f31a1ac..1cf120f 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -245,14 +245,6 @@ public function register($data) $user->file($file); $user->save(); - if (isset($data['state']) && $data['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) { - //Login user - $this->session->user = $user; - unset($this->grav['user']); - $this->grav['user'] = $user; - $user->authorized = $user->authorize('site.login'); - } - return $user; } @@ -343,7 +335,7 @@ public function sendActivationEmail(User $user) $user->save(); $param_sep = $this->config->get('system.param_sep', ':'); - $activation_link = $this->grav['base_url_absolute'] . $this->config->get('plugins.login.route_activate') . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username . '/nonce' . $param_sep . Utils::getNonce('user-activation'); + $activation_link = $this->grav['base_url_absolute'] . $this->config->get('plugins.login.route_activate') . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username; $site_name = $this->config->get('site.title', 'Website'); diff --git a/login.php b/login.php index d053527..ed85d98 100755 --- a/login.php +++ b/login.php @@ -66,7 +66,7 @@ public static function getSubscribedEvents() 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], 'onTwigSiteVariables' => ['onTwigSiteVariables', -100000], 'onFormProcessed' => ['onFormProcessed', 0], - 'onUserLoginAuthenticate' => [['userLoginAuthenticateByRememberMe', 10001], ['userLoginAuthenticateByEmail', 10000], ['userLoginAuthenticate', 0]], + 'onUserLoginAuthenticate' => [['userLoginAuthenticateByRegistration', 10002], ['userLoginAuthenticateByRememberMe', 10001], ['userLoginAuthenticateByEmail', 10000], ['userLoginAuthenticate', 0]], 'onUserLoginAuthorize' => ['userLoginAuthorize', 0], 'onUserLoginFailure' => ['userLoginFailure', 0], 'onUserLogin' => ['userLogin', 0], @@ -345,15 +345,6 @@ public function handleUserActivation() $username = $uri->param('username'); - $nonce = $uri->param('nonce'); - if ($nonce === null || !Utils::verifyNonce($nonce, 'user-activation')) { - $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST'); - $messages->add($message, 'error'); - $this->grav->redirect('/'); - - return; - } - $token = $uri->param('token'); $user = User::load($username); @@ -361,7 +352,7 @@ public function handleUserActivation() $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST'); $messages->add($message, 'error'); } else { - list($good_token, $expire) = explode('::', $user->activation_token); + list($good_token, $expire) = explode('::', $user->activation_token, 2); if ($good_token === $token) { if (time() > $expire) { @@ -369,7 +360,9 @@ public function handleUserActivation() $messages->add($message, 'error'); } else { $user['state'] = 'enabled'; + unset($user['activation_token']); $user->save(); + $message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY'); $messages->add($message, 'info'); @@ -381,12 +374,7 @@ public function handleUserActivation() } if ($this->config->get('plugins.login.user_registration.options.login_after_registration', false)) { - //Login user - $this->grav['session']->user = $user; - unset($this->grav['user']); - $this->grav['user'] = $user; - $user->authenticated = true; - $user->authorized = $user->authorize('site.login'); + $this->login->login(['username' => $username], ['after_registration' => true]); } } } else { @@ -729,6 +717,10 @@ private function processUserRegistration($form, Event $event) $this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user])); + if (isset($data['state']) && $data['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) { + $this->login->login(['username' => $username], ['after_registration' => true]); + } + $redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration', false); if ($redirect) { $this->grav->redirect($redirect); @@ -780,6 +772,21 @@ public function onFormProcessed(Event $event) } } + /** + * @param UserLoginEvent $event + * @throws \RuntimeException + */ + public function userLoginAuthenticateByRegistration(UserLoginEvent $event) + { + // Check that we're logging in after registration. + if (!$event->getOption('after_registration') || $this->isAdmin()) { + return; + } + + $event->setStatus($event::AUTHENTICATION_SUCCESS); + $event->stopPropagation(); + } + /** * @param UserLoginEvent $event * @throws \RuntimeException From 84966d870f44c6e703092dd67b32aa1686936cfe Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 10 Nov 2017 13:43:48 +0200 Subject: [PATCH 06/19] Deprecated $grav['login']->getUser(), use $grav['user'] instead --- classes/Login.php | 27 ++++++++++++--------------- login.php | 4 ++-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/classes/Login.php b/classes/Login.php index 1cf120f..0ec7b2b 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -149,21 +149,6 @@ public function logout(array $options = [], User $user = null) return $user; } - /** - * Get Current logged in user - * - * @return User - */ - public function getUser() - { - /** @var User $user */ - $user = $this->grav['user']; - if (!($user->get('access') || $user->get('groups'))) { - $user = User::load($user->get('username')); - } - return $user; - } - /** * Add message into the session queue. * @@ -507,4 +492,16 @@ public function resetRateLimit(User $user, $field) { $user->{$field} = []; } + + /** + * Get Current logged in user + * + * @return User + * @deprecated 3.0 Use $grav['user'] instead. + */ + public function getUser() + { + /** @var User $user */ + return $this->grav['user']; + } } diff --git a/login.php b/login.php index ed85d98..f9e5bb5 100755 --- a/login.php +++ b/login.php @@ -186,7 +186,7 @@ public function pageVisibility(Event $e) { if ($this->config->get('plugins.login.dynamic_page_visibility')) { $pages = $e['pages']; - $user = $this->grav['login']->getUser(); + $user = $this->grav['user']; foreach ($pages->instances() as $page) { $header = $page->header(); @@ -495,7 +495,7 @@ public function authorizePage() } /** @var User $user */ - $user = $this->grav['login']->getUser(); + $user = $this->grav['user']; /** @var Page $page */ $page = $this->grav['page']; From 1b691bb2752c68598203f4b2f3659f116ca5ff5e Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 10 Nov 2017 14:08:41 +0200 Subject: [PATCH 07/19] Code cleanup --- classes/Login.php | 74 +++++++++++++++++++++++++---------------------- login.php | 17 +++++------ 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/classes/Login.php b/classes/Login.php index 0ec7b2b..03cada1 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -12,6 +12,7 @@ use Grav\Common\Grav; use Grav\Common\File\CompiledYamlFile; use Grav\Common\Language\Language; +use Grav\Common\Page\Page; use Grav\Common\Session; use Grav\Common\User\User; use Grav\Common\Uri; @@ -407,46 +408,17 @@ public function getRateLimiter($context, $maxCount = null, $interval = null) } /** - * Check if user may use password reset functionality. - * - * @param User $user - * @param string $field - * @param int $count - * @param int $interval + * @param User $user + * @param Page $page + * @param Config|null $config * @return bool - * @deprecated 3.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class. */ - public function isUserRateLimited(User $user, $field, $count, $interval) - { - if ($count > 0) { - if (!isset($user->{$field})) { - $user->{$field} = array(); - } - //remove older than 1 hour attempts - $actual_resets = array(); - foreach ($user->{$field} as $reset) { - if ($reset > (time() - $interval * 60)) { - $actual_resets[] = $reset; - } - } - - if (count($actual_resets) >= $count) { - return true; - } - $actual_resets[] = time(); // current reset - $user->{$field} = $actual_resets; - - } - return false; - } - - - public function isUserAuthorizedForPage($user, $page, $config = null) + public function isUserAuthorizedForPage(User $user, Page $page, Config $config = null) { $header = $page->header(); $rules = isset($header->access) ? (array)$header->access : []; - if ($config && $config->get('parent_acl')) { + if ($config !== null && $config->get('parent_acl')) { // If page has no ACL rules, use its parent's rules if (!$rules) { $parent = $page->parent(); @@ -481,6 +453,40 @@ public function isUserAuthorizedForPage($user, $page, $config = null) return false; } + /** + * Check if user may use password reset functionality. + * + * @param User $user + * @param string $field + * @param int $count + * @param int $interval + * @return bool + * @deprecated 3.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class. + */ + public function isUserRateLimited(User $user, $field, $count, $interval) + { + if ($count > 0) { + if (!isset($user->{$field})) { + $user->{$field} = array(); + } + //remove older than 1 hour attempts + $actual_resets = array(); + foreach ($user->{$field} as $reset) { + if ($reset > (time() - $interval * 60)) { + $actual_resets[] = $reset; + } + } + + if (count($actual_resets) >= $count) { + return true; + } + $actual_resets[] = time(); // current reset + $user->{$field} = $actual_resets; + + } + return false; + } + /** * Reset the rate limit counter. * diff --git a/login.php b/login.php index f9e5bb5..1afb8bc 100755 --- a/login.php +++ b/login.php @@ -41,9 +41,6 @@ class LoginPlugin extends Plugin /** @var bool */ protected $authenticated = true; - /** @var bool */ - protected $authorized = true; - /** @var Login */ protected $login; @@ -180,11 +177,14 @@ public function initializeLogin() * Optional ability to dynamically set visibility based on page access and page header * that states `login.visibility_requires_access: true` * + * Note that this setting may be slow on large sites as it loads all pages into memory for each page load! + * * @param Event $e */ public function pageVisibility(Event $e) { if ($this->config->get('plugins.login.dynamic_page_visibility')) { + /** @var Pages $pages */ $pages = $e['pages']; $user = $this->grav['user']; @@ -192,8 +192,8 @@ public function pageVisibility(Event $e) $header = $page->header(); if (isset($header->login['visibility_requires_access'])) { $config = $this->mergeConfig($page); - $access = $this->grav['login']->isUserAuthorizedForPage($user, $page, $config); - if ($access == false) { + $access = $this->login->isUserAuthorizedForPage($user, $page, $config); + if ($access === false) { $page->visible(false); } } @@ -218,7 +218,6 @@ public function storeReferrerPage() $uri = $this->grav['uri']; $current_route = $uri->route(); - if (!in_array($current_route, $invalid_redirect_routes, true)) { $allowed = true; @@ -450,7 +449,6 @@ public function loginController() if (!isset($post['login-form-nonce']) || !Utils::verifyNonce($post['login-form-nonce'], 'login-form')) { $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'info'); - $this->authorized = false; $twig = $this->grav['twig']; $twig->twig_vars['notAuthorized'] = true; @@ -491,7 +489,7 @@ public function authorizeFallBackUrl() public function authorizePage() { if (!$this->authenticated) { - return true; + return; } /** @var User $user */ @@ -501,7 +499,7 @@ public function authorizePage() $page = $this->grav['page']; if (!$page || $this->grav['login']->isUserAuthorizedForPage($user, $page, $this->mergeConfig($page))) { - return true; + return; } // If this is not an HTML page request, simply throw a 403 error @@ -550,7 +548,6 @@ public function authorizePage() /** @var Language $l */ $l = $this->grav['language']; $this->grav['messages']->add($l->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error'); - $this->authorized = false; $twig->twig_vars['notAuthorized'] = true; $this->setUnauthorizedPage(); From 8e4072fe262209e3571301766dda6b7ad2a842d6 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 10 Nov 2017 20:21:26 +0200 Subject: [PATCH 08/19] Fix bug, sorry :) --- classes/Login.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/Login.php b/classes/Login.php index 03cada1..cb47c8d 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -9,6 +9,7 @@ use Birke\Rememberme\Cookie; use Grav\Common\Config\Config; +use Grav\Common\Data\Data; use Grav\Common\Grav; use Grav\Common\File\CompiledYamlFile; use Grav\Common\Language\Language; @@ -16,7 +17,6 @@ use Grav\Common\Session; use Grav\Common\User\User; use Grav\Common\Uri; -use Grav\Common\Utils; use Grav\Plugin\Email\Utils as EmailUtils; use Grav\Plugin\Login\Events\UserLoginEvent; use Grav\Plugin\Login\RememberMe\RememberMe; @@ -410,10 +410,10 @@ public function getRateLimiter($context, $maxCount = null, $interval = null) /** * @param User $user * @param Page $page - * @param Config|null $config + * @param Data|null $config * @return bool */ - public function isUserAuthorizedForPage(User $user, Page $page, Config $config = null) + public function isUserAuthorizedForPage(User $user, Page $page, $config = null) { $header = $page->header(); $rules = isset($header->access) ? (array)$header->access : []; From 11f63256c894bc39e68fd8acb5cbd6a24bd8c9c7 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 10 Nov 2017 21:45:09 +0200 Subject: [PATCH 09/19] Fix auto-login on registration if no validation is needed --- classes/Events/UserLoginEvent.php | 2 +- classes/Login.php | 5 +++-- login.php | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/classes/Events/UserLoginEvent.php b/classes/Events/UserLoginEvent.php index 5c25a81..c4bf522 100644 --- a/classes/Events/UserLoginEvent.php +++ b/classes/Events/UserLoginEvent.php @@ -78,7 +78,7 @@ public function __construct(array $items = []) parent::__construct($items); if (!$this->offsetExists('user')) { - $this->offsetSet('user', User::load($this['credentials']['username'], false)); + $this->offsetSet('user', User::load($this['credentials']['username'])); } } diff --git a/classes/Login.php b/classes/Login.php index cb47c8d..7b6b9b7 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -69,16 +69,17 @@ public function __construct(Grav $grav) * * @param array $credentials * @param array $options + * @param array $extra Example: ['authorize' => 'site.login', 'user' => null], undefined variables gets set. * @return User */ - public function login(array $credentials, array $options = []) + public function login(array $credentials, array $options = [], array $extra = []) { $grav = Grav::instance(); $eventOptions = [ 'credentials' => $credentials, 'options' => $options - ]; + ] + $extra; // Attempt to authenticate the user. $event = new UserLoginEvent($eventOptions); diff --git a/login.php b/login.php index 1afb8bc..f302746 100755 --- a/login.php +++ b/login.php @@ -715,7 +715,7 @@ private function processUserRegistration($form, Event $event) $this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user])); if (isset($data['state']) && $data['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) { - $this->login->login(['username' => $username], ['after_registration' => true]); + $this->login->login(['username' => $username], ['after_registration' => true], ['user' => $user]); } $redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration', false); From a07083e99978cc397cf59bf3b8a490aabf5abae8 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 30 Nov 2017 18:33:32 -0700 Subject: [PATCH 10/19] Removed `messsages.html.twig` use new Core version --- CHANGELOG.md | 1 + templates/forgot.html.twig | 1 + templates/login.html.twig | 1 + templates/partials/forgot-form.html.twig | 2 -- templates/partials/login-form.html.twig | 1 - templates/partials/messages.html.twig | 16 ---------------- templates/partials/reset-form.html.twig | 2 -- templates/profile.html.twig | 1 + templates/reset.html.twig | 1 + 9 files changed, 5 insertions(+), 21 deletions(-) delete mode 100644 templates/partials/messages.html.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index a8110ad..38babc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ 1. [](#improved) * Remember entered username if login fails * Improved rate limiter to work without sessions and against distributed attacks + * Removed `partials/messages.html.twig` and rely on new core version 1. [](#bugfix) * Do not send nonce with activation link, email app can open the link in another browser diff --git a/templates/forgot.html.twig b/templates/forgot.html.twig index b5b6e19..9ca0001 100644 --- a/templates/forgot.html.twig +++ b/templates/forgot.html.twig @@ -1,5 +1,6 @@ {% extends 'partials/base.html.twig' %} {% block content %} + {% include 'partials/messages.html.twig' %} {% include 'partials/forgot-form.html.twig' %} {% endblock %} diff --git a/templates/login.html.twig b/templates/login.html.twig index 9c69bcf..7ac282f 100644 --- a/templates/login.html.twig +++ b/templates/login.html.twig @@ -1,5 +1,6 @@ {% extends 'partials/base.html.twig' %} {% block content %} + {% include 'partials/messages.html.twig' %} {% include 'partials/login-form.html.twig' %} {% endblock %} diff --git a/templates/partials/forgot-form.html.twig b/templates/partials/forgot-form.html.twig index e88e8f8..8435540 100644 --- a/templates/partials/forgot-form.html.twig +++ b/templates/partials/forgot-form.html.twig @@ -1,8 +1,6 @@
{{ content|raw }} - {% include 'partials/messages.html.twig' %} - {% for field in form.fields %} {% if field.type %} diff --git a/templates/partials/login-form.html.twig b/templates/partials/login-form.html.twig index b556bed..63b8c38 100755 --- a/templates/partials/login-form.html.twig +++ b/templates/partials/login-form.html.twig @@ -1,5 +1,4 @@
- {% include 'partials/messages.html.twig' %} {% if page.template == 'login' or show_login_form %} diff --git a/templates/partials/messages.html.twig b/templates/partials/messages.html.twig deleted file mode 100644 index 61fbf67..0000000 --- a/templates/partials/messages.html.twig +++ /dev/null @@ -1,16 +0,0 @@ -{% if grav.messages.all %} -
- {% for message in grav.messages.fetch %} - - {% set scope = message.scope|e %} - - {% set type_mapping = {'info':'green', 'error': 'red', 'warning': 'yellow'} %} - {% set type = type_mapping[scope] %} - - {% set icon_mapping = {'info':'check', 'error': 'ban', 'warning': 'exclamation-triangle'} %} - {% set icon = icon_mapping[scope] %} - -

{{ message.message|raw }}

- {% endfor %} -
-{% endif %} diff --git a/templates/partials/reset-form.html.twig b/templates/partials/reset-form.html.twig index 39d16b3..079a607 100644 --- a/templates/partials/reset-form.html.twig +++ b/templates/partials/reset-form.html.twig @@ -2,8 +2,6 @@
{{ content|raw }} - {% include 'partials/messages.html.twig' %} - {% for field in form.fields %} {% set value = attribute(grav.twig.twig_vars, field.name) is defined ? attribute(grav.twig.twig_vars, field.name) : null %} diff --git a/templates/profile.html.twig b/templates/profile.html.twig index dbfa868..e35b743 100644 --- a/templates/profile.html.twig +++ b/templates/profile.html.twig @@ -3,6 +3,7 @@ {% do form.setAllData(grav.user.toArray) %} {% block content %} + {% include 'partials/messages.html.twig' %} {{ page.content }} {% include 'forms/form.html.twig' %} {% endblock %} \ No newline at end of file diff --git a/templates/reset.html.twig b/templates/reset.html.twig index e162356..f459784 100644 --- a/templates/reset.html.twig +++ b/templates/reset.html.twig @@ -1,6 +1,7 @@ {% extends 'partials/base.html.twig' %} {% block content %} + {% include 'partials/messages.html.twig' %} {% include 'partials/reset-form.html.twig' %} {% endblock %} From 14b1f838ff50eeca4691ca1ab2a3d19c681c90c3 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 30 Nov 2017 18:36:37 -0700 Subject: [PATCH 11/19] Use new FlashCookieObject calls to store logout message --- login.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/login.php b/login.php index f302746..7d69b3a 100755 --- a/login.php +++ b/login.php @@ -29,6 +29,8 @@ */ class LoginPlugin extends Plugin { + const TMP_COOKIE_NAME = 'tmp-message'; + /** @var string */ protected $route; @@ -598,6 +600,12 @@ public function onTwigSiteVariables() } elseif ($task === 'login') { $twig->twig_vars['username'] = isset($_POST['username']) ? $_POST['username'] : ''; } + + $flashData = $this->grav['session']->getFlashCookieObject(self::TMP_COOKIE_NAME); + + if (isset($flashData->message)) { + $this->grav['messages']->add($flashData->message, $flashData->status); + } } /** @@ -915,9 +923,7 @@ public function userLogout(UserLoginEvent $event) } $this->grav['session']->invalidate()->start(); - $this->grav['session']->user = User::load('', false); - - $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.LOGGED_OUT'), - 'info'); + $this->grav['session']->setFlashCookieObject(self::TMP_COOKIE_NAME, ['message' => $this->grav['language']->translate('PLUGIN_LOGIN.LOGGED_OUT'), + 'status' => 'info']); } } From 8e0e0a884ede916589ab8ec0caf3a44019ec8d1e Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 30 Nov 2017 18:39:39 -0700 Subject: [PATCH 12/19] Fix logout message --- CHANGELOG.md | 1 + classes/Controller.php | 14 +++++++++----- classes/Login.php | 30 +----------------------------- login.php | 7 +++++++ 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38babc3..88efc15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Added `$grav['login']->login()` and `$grav['login']->logout()` functions with event hooks * Added `$grav['login']->getRateLimiter($context)` function * Added events `onUserLoginAuthenticate`, `onUserLoginAuthorize`, `onUserLoginFailure`, `onUserLogin`, `onUserLogout` + * Use new `Session:get/setFlashCookieObject()` methods to maintain logout message 1. [](#improved) * Remember entered username if login fails * Improved rate limiter to work without sessions and against distributed attacks diff --git a/classes/Controller.php b/classes/Controller.php index cd5d8fc..320dd4a 100644 --- a/classes/Controller.php +++ b/classes/Controller.php @@ -82,6 +82,8 @@ public function __construct(Grav $grav, $action, $post = null) */ public function execute() { + $messages = $this->grav['messages']; + // Set redirect if available. if (isset($this->post['_redirect'])) { $redirect = $this->post['_redirect']; @@ -98,7 +100,7 @@ public function execute() try { $success = call_user_func([$this, $method]); } catch (\RuntimeException $e) { - $this->login->setMessage($e->getMessage(), 'error'); + $messages->add($e->getMessage(), 'error'); } if (!$this->redirect && isset($redirect)) { @@ -118,6 +120,8 @@ public function taskLogin() /** @var Language $t */ $t = $this->grav['language']; + $messages = $this->grav['messages']; + $userKey = isset($this->post['username']) ? (string)$this->post['username'] : ''; $ipKey = Uri::ip(); @@ -130,7 +134,7 @@ public function taskLogin() // Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited. if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($userKey))) { - $this->login->setMessage($t->translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $rateLimiter->getInterval()]), 'error'); + $messages->add($t->translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $rateLimiter->getInterval()]), 'error'); $this->setRedirect($this->grav['config']->get('plugins.login.route', '/')); return true; @@ -139,7 +143,7 @@ public function taskLogin() if ($this->authenticate($this->post)) { $rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($userKey); - $this->login->setMessage($t->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL'), 'info'); + $messages->add($t->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL'), 'info'); $redirect = $this->grav['config']->get('plugins.login.redirect_after_login'); if (!$redirect) { @@ -150,10 +154,10 @@ public function taskLogin() /** @var User $user */ $user = $this->grav['user']; if ($user->username) { - $this->login->setMessage($t->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error'); + $messages->add($t->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error'); $this->setRedirect($this->grav['config']->get('plugins.login.route_unauthorized', '/')); } else { - $this->login->setMessage($t->translate('PLUGIN_LOGIN.LOGIN_FAILED'), 'error'); + $messages->add($t->translate('PLUGIN_LOGIN.LOGIN_FAILED'), 'error'); } } diff --git a/classes/Login.php b/classes/Login.php index 7b6b9b7..629dc5f 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -151,34 +151,6 @@ public function logout(array $options = [], User $user = null) return $user; } - /** - * Add message into the session queue. - * - * @param string $msg - * @param string $type - */ - public function setMessage($msg, $type = 'info') - { - /** @var Message $messages */ - $messages = $this->grav['messages']; - $messages->add($msg, $type); - } - - /** - * Fetch and delete messages from the session queue. - * - * @param string $type - * - * @return array - */ - public function messages($type = null) - { - /** @var Message $messages */ - $messages = $this->grav['messages']; - - return $messages->fetch($type); - } - /** * Authenticate user. * @@ -192,7 +164,7 @@ public function authenticate($credentials, $options = ['remember_me' => true]) $user = $this->login($credentials, $options); if ($user->authenticated) { - $this->setMessage($this->language->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL', + $this->grav['messages']->add($this->language->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL', [$user->language]), 'info'); $redirect_route = $this->uri->route(); diff --git a/login.php b/login.php index 7d69b3a..b7040e6 100755 --- a/login.php +++ b/login.php @@ -617,6 +617,7 @@ public function onTwigSiteVariables() private function processUserRegistration($form, Event $event) { $language = $this->grav['language']; + $messages = $this->grav['messages']; if (!$this->config->get('plugins.login.enabled')) { throw new \RuntimeException($language->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED')); @@ -709,8 +710,12 @@ private function processUserRegistration($form, Event $event) $this->grav->fireEvent('onUserLoginRegisterData', new Event(['data' => &$data])); $user = $this->login->register($data); + $fullname = $user->fullname ?: $user->username; + if ($this->config->get('plugins.login.user_registration.options.send_activation_email', false)) { $this->login->sendActivationEmail($user); + $message = $language->translate(['PLUGIN_LOGIN.ACTIVATION_NOTICE_MSG', $fullname]); + $messages->add($message, 'info'); } else { if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) { $this->login->sendWelcomeEmail($user); @@ -718,6 +723,8 @@ private function processUserRegistration($form, Event $event) if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) { $this->login->sendNotificationEmail($user); } + $message = $language->translate(['PLUGIN_LOGIN.WELCOME_NOTICE_MSG', $fullname]); + $messages->add($message, 'info'); } $this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user])); From 5d590f6b643fed56ae25df15cf687b85b22b5123 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 30 Nov 2017 18:40:44 -0700 Subject: [PATCH 13/19] Languages now in dedicated files --- CHANGELOG.md | 3 +- languages.yaml | 517 -------------------------------------------- languages/de.yaml | 47 ++++ languages/en.yaml | 115 ++++++++++ languages/fr.yaml | 89 ++++++++ languages/hr.yaml | 41 ++++ languages/hu.yaml | 21 ++ languages/ro.yaml | 86 ++++++++ languages/ru.yaml | 108 +++++++++ languages/test.html | 19 ++ 10 files changed, 528 insertions(+), 518 deletions(-) delete mode 100755 languages.yaml create mode 100644 languages/de.yaml create mode 100644 languages/en.yaml create mode 100644 languages/fr.yaml create mode 100644 languages/hr.yaml create mode 100644 languages/hu.yaml create mode 100644 languages/ro.yaml create mode 100644 languages/ru.yaml create mode 100644 languages/test.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 88efc15..32c70c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ * Added `$grav['login']->login()` and `$grav['login']->logout()` functions with event hooks * Added `$grav['login']->getRateLimiter($context)` function * Added events `onUserLoginAuthenticate`, `onUserLoginAuthorize`, `onUserLoginFailure`, `onUserLogin`, `onUserLogout` - * Use new `Session:get/setFlashCookieObject()` methods to maintain logout message + * Logout message is now maintained during session destruction 1. [](#improved) * Remember entered username if login fails * Improved rate limiter to work without sessions and against distributed attacks * Removed `partials/messages.html.twig` and rely on new core version + * Moved languages from unified file into dedicated language file structure 1. [](#bugfix) * Do not send nonce with activation link, email app can open the link in another browser diff --git a/languages.yaml b/languages.yaml deleted file mode 100755 index 763458b..0000000 --- a/languages.yaml +++ /dev/null @@ -1,517 +0,0 @@ -en: - PLUGIN_LOGIN: - USERNAME: "Username" - EMAIL: "Email" - USERNAME_EMAIL: "Username/Email" - PASSWORD: "Password" - ACCESS_DENIED: "Access denied..." - LOGIN_FAILED: "Login failed..." - LOGIN_SUCCESSFUL: "You have been successfully logged in." - BTN_LOGIN: "Login" - BTN_LOGOUT: "Logout" - BTN_FORGOT: "Forgot" - BTN_REGISTER: "Register" - BTN_RESET: "Reset Password" - BTN_SEND_INSTRUCTIONS: "Send Reset Instructions" - RESET_LINK_EXPIRED: "Reset link has expired, please try again" - RESET_PASSWORD_RESET: "Password has been reset" - RESET_INVALID_LINK: "Invalid reset link used, please try again" - FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instructions to reset your password have been sent via email" - FORGOT_FAILED_TO_EMAIL: "Failed to email instructions, please try again later" - FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Cannot reset password for %s, no email address is set" - FORGOT_USERNAME_DOES_NOT_EXIST: "User with username %s does not exist" - FORGOT_EMAIL_NOT_CONFIGURED: "Cannot reset password. This site is not configured to send emails" - FORGOT_EMAIL_SUBJECT: "%s Password Reset Request" - FORGOT_EMAIL_BODY: "

Password Reset

Dear %1$s,

A request was made on %4$s to reset your password.


Click this to reset your password

Alternatively, copy the following URL into your browser's address bar:

%2$s


Kind regards,

%3$s

" - SESSION: "“Remember Me”-Session" - REMEMBER_ME: "Remember Me" - REMEMBER_ME_HELP: "Sets a persistent cookie on your browser to allow persistent-login authentication between sessions." - REMEMBER_ME_STOLEN_COOKIE: "Someone else has used your login information to access this page! All sessions were logged out. Please log in with your credentials and check your data." - BUILTIN_CSS: "Use built in CSS" - BUILTIN_CSS_HELP: "Include the CSS provided by the admin plugin" - ROUTE: "Login path" - ROUTE_HELP: "Custom route to a custom login page that your theme provides" - ROUTE_REGISTER: "Registration path" - ROUTE_REGISTER_HELP: "Route to the registration page. Set this if you want to use the built-in registration page. Leave it empty if you have your own registration form" - USERNAME_NOT_VALID: "Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed" - USERNAME_NOT_AVAILABLE: "Username %s already exists, please pick another username" - EMAIL_NOT_AVAILABLE: "Email address %s already exists, please pick another email address" - PASSWORD_NOT_VALID: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" - PASSWORDS_DO_NOT_MATCH: "Passwords do not match. Double-check you entered the same password twice" - USER_NEEDS_EMAIL_FIELD: "The user needs an email field" - EMAIL_SENDING_FAILURE: "An error occurred while sending the email" - ACTIVATION_EMAIL_SUBJECT: "Activate your account on %s" - ACTIVATION_EMAIL_BODY: "Hi %s, click here to activate your account on %s" - WELCOME_EMAIL_SUBJECT: "Welcome to %s" - WELCOME_EMAIL_BODY: "Hi %s, welcome to %s!" - NOTIFICATION_EMAIL_SUBJECT: "New user on %s" - NOTIFICATION_EMAIL_BODY: "Hi, a new user registered on %s. Username: %s, email: %s" - EMAIL_FOOTER: "GetGrav.org" - ACTIVATION_LINK_EXPIRED: "Activation link expired" - USER_ACTIVATED_SUCCESSFULLY: "User activated successfully" - INVALID_REQUEST: "Invalid request" - USER_REGISTRATION: "User Registration" - USER_REGISTRATION_ENABLED_HELP: "Enable the user registration" - VALIDATE_PASSWORD1_AND_PASSWORD2: "Validate double entered password" - VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validate and compare two different fields for the passwords, named `password1` and `password2`. Enable this if you have two password fields in the registration form" - SET_USER_DISABLED: "Set the user as disabled" - SET_USER_DISABLED_HELP: "Best used along with the `Send activation email` email. Adds the user to Grav, but sets it as disabled" - LOGIN_AFTER_REGISTRATION: "Login the user after registration" - LOGIN_AFTER_REGISTRATION_HELP: "Immediately login the user after the registration. If email activation is required, the user will be logged in immediately after activating the account" - SEND_ACTIVATION_EMAIL: "Send activation email" - SEND_ACTIVATION_EMAIL_HELP: "Sends an email to the user to activate his account. Enable the `Set the user as disabled` option when using this feature, so the user will be set as disabled and an email will be sent to activate the account" - SEND_NOTIFICATION_EMAIL: "Send notification email" - SEND_NOTIFICATION_EMAIL_HELP: "Notifies the site admin that a new user has registered. The email will be sent to the `To` field in the Email Plugin configuration" - SEND_WELCOME_EMAIL: "Send welcome email" - SEND_WELCOME_EMAIL_HELP: "Sends an email to the newly registered user" - DEFAULT_VALUES: "Default values" - DEFAULT_VALUES_HELP: "List of field names and values associated, that will be added to the user profile (yaml file) by default, without being configurable by the user. Separate multiple values with a comma, with no spaces between the values" - ADDITIONAL_PARAM_KEY: "Parameter" - ADDITIONAL_PARAM_VALUE: "Value" - REGISTRATION_FIELDS: "Registration fields" - REGISTRATION_FIELDS_HELP: "Add the fields that will be added to the user yaml file. Fields not listed here will not be added even if present in the registration form" - REGISTRATION_FIELD_KEY: "Field name" - REDIRECT_AFTER_LOGIN: "Redirect after login" - REDIRECT_AFTER_LOGIN_HELP: "Custom route to redirect after login" - REDIRECT_AFTER_REGISTRATION: "Redirect after registration" - REDIRECT_AFTER_REGISTRATION_HELP: "Custom route to redirect after the registration" - OPTIONS: "Options" - EMAIL_VALIDATION_MESSAGE: "Must be a valid email address" - PASSWORD_VALIDATION_MESSAGE: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" - TIMEOUT_HELP: "Sets the session timeout in seconds when Remember Me is enabled and checked by the user. Minimum is 604800 which means 1 week" - GROUPS_HELP: "List of groups the new registered user will be part of, if any" - SITE_ACCESS_HELP: "List of site access levels the new registered user will have. Example: `login` -> `true` " - WELCOME: "Welcome" - REDIRECT_AFTER_ACTIVATION: "Redirect after the user activation" - REDIRECT_AFTER_ACTIVATION_HELP: "Used if the user is required to activate the account via email. Once activated, this route will be shown" - REGISTRATION_DISABLED: "Registration disabled" - USE_PARENT_ACL_LABEL: "Use parent access rules" - USE_PARENT_ACL_HELP: "Check for parent access rules if no rules are defined" - PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Protect a login-protected page media" - PROTECT_PROTECTED_PAGE_MEDIA_HELP: "If enabled, media of a login protected page is login protected as well and cannot be seen unless logged in" - SECURITY_TAB: "Security" - MAX_RESETS_COUNT: "Max password resets count" - MAX_RESETS_COUNT_HELP: "Password reset flood protection setting (0 - not limited)" - MAX_RESETS_INTERVAL: "Max password resets interval" - MAX_RESETS_INTERVAL_HELP: "The time interval for the max password resets count value" - FORGOT_CANNOT_RESET_IT_IS_BLOCKED: "Cannot reset password for %s, password reset functionality temporarily blocked, please try later (maximum %s minutes)" - MAX_LOGINS_COUNT: "Max logins count" - MAX_LOGINS_COUNT_HELP: "Flood protection setting (0 - not limited)" - MAX_LOGINS_INTERVAL: "Max logins interval" - MAX_LOGINS_INTERVAL_HELP: "The time interval for the login count value" - TOO_MANY_LOGIN_ATTEMPTS: "Too many failed login attempted in the configured time (%s minutes)" - SECONDS: "seconds" - RESETS: "resets" - ATTEMPTS: "attempts" - ROUTES: "Routes" - ROUTE_FORGOT: "Forgot password route" - ROUTE_RESET: "Reset password route" - ROUTE_PROFILE: "User profile route" - ROUTE_ACTIVATE: "User activation route" - LOGGED_OUT: "You have been successfully logged out..." - PAGE_RESTRICTED: "Access is restricted, please login..." - DYNAMIC_VISIBILITY: "Dynamic Page Visibility" - DYNAMIC_VISIBILITY_HELP: "Allows dynamic processing of page visibility base on access rules if login.visibility_requires_access is set to true on a page" - -de: - PLUGIN_LOGIN: - USERNAME: Benutzername - EMAIL: Email - USERNAME_EMAIL: Benutzername/Email - PASSWORD: Passwort - ACCESS_DENIED: Zugang verweigert - LOGIN_FAILED: Login fehlgeschlagen... - LOGIN_SUCCESSFUL: Du wurdest erfolgreich eingeloggt. - BTN_LOGIN: Anmelden - BTN_LOGOUT: Abmelden - BTN_FORGOT: Vergessen - BTN_REGISTER: Registrieren - REMEMBER_ME: Angemeldet bleiben - REMEMBER_ME_HELP: "Speichert einen Cookie im Browser, welcher eine fortwährende Anmeldung sicherstellt." - BUILTIN_CSS: "Nutze das integrierte CSS" - BUILTIN_CSS_HELP: "Nutze das CSS, welches vom Admin Plugin bereitgestellt werden" - ROUTE: "Anmeldepfad" - ROUTE_REGISTER: "Registrierungspfad" - USERNAME_NOT_AVAILABLE: "Der Nutzername %s existiert bereits, bitte wähle einen Anderen" - USER_NEEDS_EMAIL_FIELD: "Der Nutzer benötigt ein E-Mail Feld" - EMAIL_SENDING_FAILURE: "Ein Fehler ist beim senden der E-Mail aufgetreten" - ACTIVATION_EMAIL_SUBJECT: "Aktiviere dein Account auf %s" - ACTIVATION_EMAIL_BODY: "Hi %s, click %s to activate your account on %s" - WELCOME_EMAIL_SUBJECT: "Willkommen zu %s" - WELCOME_EMAIL_BODY: "Hi %s, willkommen zu %s!" - NOTIFICATION_EMAIL_SUBJECT: "Neuer Nutzer auf %s" - NOTIFICATION_EMAIL_BODY: "Hi, ein neuer Nutzer hat sich auf %s registriert. Nutzername: %s, E-Mail: %s" - EMAIL_FOOTER: "GetGrav.org" - ACTIVATION_LINK_EXPIRED: "Aktivierungslink ist abgelaufen" - USER_ACTIVATED_SUCCESSFULLY: "Nutzer erfolgreich aktiviert" - INVALID_REQUEST: "Ungültige Anfrage" - USER_REGISTRATION: "Nutzer Registrierung" - USER_REGISTRATION_ENABLED_HELP: "Aktiviere die Nutzer Registrierung" - VALIDATE_PASSWORD1_AND_PASSWORD2: "Überprüfe das doppelt eingegebene Passwort" - SEND_ACTIVATION_EMAIL: "Aktivierungs E-Mail senden" - SEND_NOTIFICATION_EMAIL: "Benachtichtigungs E-Mail senden" - SEND_WELCOME_EMAIL: "Sende eine Willkommens E-Mail" - DEFAULT_VALUES: "Standard Werte" - ADDITIONAL_PARAM_KEY: "Parameter" - ADDITIONAL_PARAM_VALUE: "Wert" - REGISTRATION_FIELDS: "Registrierungsfelder" - REGISTRATION_FIELD_KEY: "Feldname" - REDIRECT_AFTER_LOGIN: "Umleitung nach Login" - REDIRECT_AFTER_REGISTRATION: "Umleitung nach Registrierung" - OPTIONS: Optionen - EMAIL_VALIDATION_MESSAGE: "Muss eine gültige E-Mail Adresse sein" - WELCOME: "Willkommen" - -fr: - PLUGIN_LOGIN: - USERNAME: "Nom d’utilisateur" - EMAIL: "E-mail" - USERNAME_EMAIL: "Nom d’utilisateur/E-mail" - PASSWORD: "Mot de passe" - ACCESS_DENIED: "Accès refusé..." - LOGIN_FAILED: "Échec de la connexion..." - LOGIN_SUCCESSFUL: "Vous vous êtes connecté avec succès." - BTN_LOGIN: "Connexion" - BTN_LOGOUT: "Déconnexion" - BTN_FORGOT: "Mot de passe oublié" - BTN_REGISTER: "S’enregister" - BTN_RESET: "Réinitialiser le mot de passe" - BTN_SEND_INSTRUCTIONS: "Envoyer les instructions de Réinitialisation" - RESET_LINK_EXPIRED: "Le lien de réinitialisation a expiré, veuillez réessayer" - RESET_PASSWORD_RESET: "Le mot de passe a été réinitialisé" - RESET_INVALID_LINK: "Le lien de réinitialisation utilisé n’est pas valide, veuillez réessayer" - FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Les instructions pour la réinitialisation de votre mot de passe ont été envoyées par e-mail" - FORGOT_FAILED_TO_EMAIL: "Impossible d’envoyer les instructions, veuillez réessayer ultérieurement" - FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Impossible de réinitialiser le mot de passe pour %s, aucune adresse e-mail n’a été paramétrée" - FORGOT_USERNAME_DOES_NOT_EXIST: "L’utilisateur avec le nom d’utilisateur %s n’existe pas" - FORGOT_EMAIL_NOT_CONFIGURED: "Impossible de réinitialiser le mot de passe. Ce site n’est pas configuré pour envoyer des e-mails" - FORGOT_EMAIL_SUBJECT: "Demande de réinitialisation de mot de passe %s" - FORGOT_EMAIL_BODY: "

Réinitialisation de mot de passe

%1$s,

Une demande a été faite sur %4$s pour la réinitialisation de votre mot de passe.


Cliquez ici pour réinitialiser votre mot de passe

Vous pouvez également copier l’URL suivante dans la barre d’adresse de votre navigateur :

%2$s


Cordialement,

%3$s

" - SESSION: "Session - “Se souvenir de moi”" - REMEMBER_ME: "Se souvenir de moi" - REMEMBER_ME_HELP: "Définit un cookie persistant sur votre navigateur autorisant l’authentification par connexion persistante entre les sessions." - REMEMBER_ME_STOLEN_COOKIE: "Quelqu’un d’autre a utilisé vos informations de connexion pour accéder à cette page ! Toutes les sessions ont été déconnectées. Veuillez vous connecter avec vos identifiants et vérifier vos données." - BUILTIN_CSS: "Utiliser les CSS intégrés" - BUILTIN_CSS_HELP: "Utiliser les CSS fournis dans le plugin d’administration" - ROUTE: "Chemin de connexion" - ROUTE_HELP: "Chemin personnalisé vers une page de connexion personnalisée proposée par votre thème" - ROUTE_REGISTER: "Chemin vers l’inscription" - ROUTE_REGISTER_HELP: "Chemin vers la page d’inscription. A définir si vous souhaitez utiliser la page d’inscription intégrée. Laisser vide si vous disposez de votre propre formulaire d’inscription." - USERNAME_NOT_VALID: "Le nom d’utilisateur doit comporter entre 3 et 16 caractères et peut être composé de lettres minuscules, de chiffres et de tirets de soulignement (underscores) et des traits d’union. Les lettres majuscules, les espaces et les caractères spéciaux ne sont pas autorisés." - USERNAME_NOT_AVAILABLE: "Le nom d’utilisateur %s existe déjà, veuillez en choisir un autre." - PASSWORD_NOT_VALID: "Le mot de passe doit contenir au moins un chiffre, une majuscule et une minuscule et être composé d’au moins 8 caractères" - PASSWORDS_DO_NOT_MATCH: "Les mots de passe sont différents. Réessayez de saisir le même mot de passe deux fois." - USER_NEEDS_EMAIL_FIELD: "L’utilisateur a besoin d’un champ pour e-mail" - EMAIL_SENDING_FAILURE: "Une erreur est survenue lors de l’envoi de l’e-mail." - ACTIVATION_EMAIL_SUBJECT: "Activer votre compte sur %s" - ACTIVATION_EMAIL_BODY: "Bonjour %s, cliquez pour activer votre compte sur %s" - WELCOME_EMAIL_SUBJECT: "Bienvenue sur %s" - WELCOME_EMAIL_BODY: "Bonjour %s, bienvenue sur %s!" - NOTIFICATION_EMAIL_SUBJECT: "Nouvel utilisateur sur %s" - EMAIL_FOOTER: "GetGrav.org" - NOTIFICATION_EMAIL_BODY: "Bonjour, un nouvel utilisateur s’est inscrit sur %s. Nom d’utilisateur : %s, e-mail : %s" - ACTIVATION_LINK_EXPIRED: "Le lien d’activation a expiré" - USER_ACTIVATED_SUCCESSFULLY: "Utilisateur activé avec succès" - INVALID_REQUEST: "Requête non valide" - USER_REGISTRATION: "Inscription de l’utilisateur" - USER_REGISTRATION_ENABLED_HELP: "Activer l’inscription des utilisateurs" - VALIDATE_PASSWORD1_AND_PASSWORD2: "Valider la double saisie du mot de passe" - VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Comparer et valider deux champs pour les mots de passe `Mot de passe 1` et `Mot de passe 2`. Activez cette option si vous avez deux champs de mots de passe dans le formulaire d’inscription." - SET_USER_DISABLED: "Définir l’utilisateur comme désactivé" - SET_USER_DISABLED_HELP: "La meilleure pratique si vous utilisez l’option `Envoyer un e-mail d’activation`. Ajoute l’utilisateur à Grav, mais le défini comme étant désactivé." - LOGIN_AFTER_REGISTRATION: "Connecte l’utilisateur après son inscription" - LOGIN_AFTER_REGISTRATION_HELP: "Identifier immédiatement l’utilisateur après l’inscription. Si l’e-mail d’activation est demandé, l’utilisateur sera connecté immédiatement après l’activation du compte." - SEND_ACTIVATION_EMAIL: "Envoyer un e-mail d’activation" - SEND_ACTIVATION_EMAIL_HELP: "Envoyer un e-mail à l’utilisateur pour l’activation son compte. Lorsque vous utilisez cette fonction, activez l’option `Définir l’utilisateur comme désactivé` afin que l’utilisateur soit désactivé et qu’un e-mail lui soit envoyé pour activer son compte." - SEND_NOTIFICATION_EMAIL: "Envoyer un e-mail de notification" - SEND_NOTIFICATION_EMAIL_HELP: "Informe l’administrateur du site qu’un nouvel utilisateur s’est enregistré. L’e-mail sera envoyé à la personne renseignée dans le champ `À` dans la configuration du plugin e-mail." - SEND_WELCOME_EMAIL: "Envoyer un e-mail de bienvenue" - SEND_WELCOME_EMAIL_HELP: "Envoyer un e-mail à un nouvel utilisateur enregistré." - DEFAULT_VALUES: "Valeurs par défaut" - DEFAULT_VALUES_HELP: "Liste des noms et valeurs associés pour les champs. Ils seront ajoutés au profil utilisateur par défaut (fichier yaml), sans pouvoir être configurables par l’utilisateur. Séparez les différentes valeurs par une virgule, sans espaces entre les valeurs." - ADDITIONAL_PARAM_KEY: "Paramètre" - ADDITIONAL_PARAM_VALUE: "Valeur" - REGISTRATION_FIELDS: "Champs d’inscription" - REGISTRATION_FIELDS_HELP: "Ajouter les champs qui seront ajoutés au fichier yaml de l’utilisateur. Les champs non listés ne seront pas ajoutés même s’ils sont présent sur le formulaire d’inscription" - REGISTRATION_FIELD_KEY: "Nom du champ" - REDIRECT_AFTER_LOGIN: "Redirection après connexion" - REDIRECT_AFTER_LOGIN_HELP: "Chemin personnalisé de redirection après la connexion" - REDIRECT_AFTER_REGISTRATION: "Redirection après inscription" - REDIRECT_AFTER_REGISTRATION_HELP: "Chemin personnalisé de redirection après l’inscription" - OPTIONS: "Options" - EMAIL_VALIDATION_MESSAGE: "Doit-être une adresse e-mail valide" - PASSWORD_VALIDATION_MESSAGE: "Le mot de passe doit-être composé d’au moins un chiffre, une majuscule et une minuscule, et au moins 8 caractères" - TIMEOUT_HELP: "Définit le délai d’expiration de la session en secondes lorsque `Se souvenir de moi` est activé et coché par l’utilisateur. Minimum de 604800 ce qui correspond à 1 semaine." - GROUPS_HELP: "Liste des groupes auxquels le nouvel utilisateur enregistré fera partie, le cas échéant." - SITE_ACCESS_HELP: "Liste des niveaux d’accès au site attribués au nouvel utilisateur enregistré. Exemple : `login` -> `true` " - WELCOME: "Bienvenue" - REDIRECT_AFTER_ACTIVATION: "Redirection après l’activation de l’utilisateur" - REDIRECT_AFTER_ACTIVATION_HELP: "Utilisé s’il est nécessaire pour l’utilisateur d’activer le compte par e-mail. Une fois activé, ce chemin s’affichera" - REGISTRATION_DISABLED: "Inscription désactivée" - USE_PARENT_ACL_LABEL: "Appliquer les règles d’accès parentes" - USE_PARENT_ACL_HELP: "Utiliser les règles d’accès parentes si aucune règle n’a été définie" - PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Protéger le média d'une page par une protection par connexion" - PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Si activé, les médias d'une page protégée par connexion sera également protégé par un système de connexion et ne pourra pas être visible à moins d'être connecté." - -ru: - PLUGIN_LOGIN: - USERNAME: Логин - EMAIL: Email - USERNAME_EMAIL: Логин/Email - PASSWORD: Пароль - ACCESS_DENIED: Доступ закрыт... - LOGIN_FAILED: Ошибка входа... - LOGIN_SUCCESSFUL: Вы успешно вошли в систему. - BTN_LOGIN: Войти - BTN_LOGOUT: Выйти - BTN_FORGOT: Забыл - BTN_REGISTER: Регистрация - BTN_RESET: "Сброс пароля" - BTN_SEND_INSTRUCTIONS: "Отправить инструкции по сбросу" - RESET_LINK_EXPIRED: "Время ссылки для сброса истекло, повторите попытку" - RESET_PASSWORD_RESET: "Пароль был сброшен" - RESET_INVALID_LINK: "Неверная ссылка сброса, повторите попытку" - FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Инструкции по сбросу пароля были отправлены по электронной почте" - FORGOT_FAILED_TO_EMAIL: "Не удалось отправить инструкции по электронной почте, повторите попытку позже" - FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Не удается сбросить пароль для %s, адресс электронной почты не установлен" - FORGOT_USERNAME_DOES_NOT_EXIST: "Пользователь с именем %s не существует" - FORGOT_EMAIL_NOT_CONFIGURED: "Невозможно сбросить пароль. Этот сайт не настроен для отправки писем" - FORGOT_EMAIL_SUBJECT: "%s Запрос на сброс пароля" - FORGOT_EMAIL_BODY: "

Восстановление пароля

Уважаемый %1$s,

Был сделан запрос для сброса пароля от %4$s.


Нажмите, чтобы сбросить пароль

Или скопируйте следующий URL-адрес в адресную строку браузера:

%2$s


С уважением,

%3$s

" - SESSION: "“Запомнить меня”-Сессия" - REMEMBER_ME: Запомнить меня - REMEMBER_ME_HELP: "Устанавливает постоянный файл cookie в вашем браузере, чтобы разрешить постоянную аутентификацию входа между сеансами." - REMEMBER_ME_STOLEN_COOKIE: "Кто-то еще использовал вашу регистрационную информацию для доступа к этой странице! Все сеансы были отключены. Войдите в свою учетную запись и проверьте свои данные." - BUILTIN_CSS: Использовать встроенный CSS - BUILTIN_CSS_HELP: Использовать CSS, предоставленный плагином администратора. - ROUTE: Путь страницы входа - ROUTE_HELP: Путь к пользовательской странице входа, которую предоставляет ваша тема - ROUTE_REGISTER: Путь регистрации - ROUTE_REGISTER_HELP: Путь к пользовательской странице регистрации. Заполните, если вы хотите использовать встроенную страницу регистрации. Оставьте его пустым, если у вас есть собственная регистрационная форма - USERNAME_NOT_VALID: "Имя пользователя должно быть от 3 до 16 символов, включая строчные буквы, цифры, символы подчеркивания и дефисы. Прописные буквы, пробелы и специальные символы не допускаются" - USERNAME_NOT_AVAILABLE: "Имя пользователя %s уже существует, выберите другое имя пользователя" - PASSWORD_NOT_VALID: "Пароль должен содержать как минимум одно число, одну прописную и строчную букву, и быть не менее 8 символов" - PASSWORDS_DO_NOT_MATCH: "Пароли не совпадают. Дважды проверьте, что вы дважды ввели тот же пароль" - USER_NEEDS_EMAIL_FIELD: "Пользователю требуется поле электронной почты" - EMAIL_SENDING_FAILURE: "Произошла ошибка при отправке письма" - ACTIVATION_EMAIL_SUBJECT: "Активируйте свою учетную запись %s" - ACTIVATION_EMAIL_BODY: "Привет %s, перейдите сюда для активации вашей учетной записи %s" - WELCOME_EMAIL_SUBJECT: "Добро пожаловать в %s" - WELCOME_EMAIL_BODY: "Привет %s, добро пожаловать в %s!" - NOTIFICATION_EMAIL_SUBJECT: "Новый пользователь %s" - NOTIFICATION_EMAIL_BODY: "Привет, новый пользователь, зарегистрированный на %s. Имя пользователя: %s, email: %s" - EMAIL_FOOTER: "GetGrav.org" - ACTIVATION_LINK_EXPIRED: "Время ссылки для активации истекло" - USER_ACTIVATED_SUCCESSFULLY: "Пользователь успешно активирован" - INVALID_REQUEST: "Неверный запрос" - USER_REGISTRATION: "Регистрация пользователя" - USER_REGISTRATION_ENABLED_HELP: "Включить регистрацию пользователя" - VALIDATE_PASSWORD1_AND_PASSWORD2: "Двойная проверка введенного пароля" - VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Подтвердить и сравнить два разных поля для паролей с именами `password1` и` password2`. Включите это, если у вас есть два поля пароля в регистрационной форме" - SET_USER_DISABLED: "Установить пользователя как отключенный" - SET_USER_DISABLED_HELP: "Лучше всего использовать электронную почту «Отправить электронную почту активации». Добавляет пользователя в Grav, но устанавливает его как отключенный" - LOGIN_AFTER_REGISTRATION: "Воход в систему после регистрации" - LOGIN_AFTER_REGISTRATION_HELP: "Автоматический воход в систему после регистрации. Если требуется активация электронной почты, пользователь будет входить в систему сразу после активации учетной записи" - SEND_ACTIVATION_EMAIL: "Отправить письмо активации" - SEND_ACTIVATION_EMAIL_HELP: "Отправляет электронное письмо пользователю для активации своей учетной записи. Включите параметр «Установить пользователя как отключенный» при использовании этой функции, чтобы пользователь был отключен, и для активации учетной записи будет отправлено электронное письмо" - SEND_NOTIFICATION_EMAIL: "Отправить уведомление по электронной почте" - SEND_NOTIFICATION_EMAIL_HELP: "Сообщает администратору сайта о регистрации нового пользователя. Электронная почта будет отправлена в поле «Кому» в конфигурации плагина электронной почты" - SEND_WELCOME_EMAIL: "Отправить приветственное письмо" - SEND_WELCOME_EMAIL_HELP: "Отправляет электронное письмо вновь зарегистрированному пользователю" - DEFAULT_VALUES: "Значения по умолчанию" - DEFAULT_VALUES_HELP: "Список названий полей и связанных значений, которые будут добавлены в профиль пользователя (файл yaml) по умолчанию, без настройки пользователем. Разделите несколько значений запятой, без пробелов между значениями" - ADDITIONAL_PARAM_KEY: "Параметр" - ADDITIONAL_PARAM_VALUE: "Значение" - REGISTRATION_FIELDS: "Регистрационные поля" - REGISTRATION_FIELDS_HELP: "Добавьте поля, которые будут добавлены в файл yaml пользователя. Поля, не перечисленные здесь, не будут добавлены, даже если они присутствуют в регистрационной форме" - REGISTRATION_FIELD_KEY: "Имя поля" - REDIRECT_AFTER_LOGIN: "Перенаправление после входа в систему" - REDIRECT_AFTER_LOGIN_HELP: "Пользовательский маршрут для перенаправления после входа в систему" - REDIRECT_AFTER_REGISTRATION: "Перенаправление после регистрации" - REDIRECT_AFTER_REGISTRATION_HELP: "Пользовательский маршрут для перенаправления после регистрации" - OPTIONS: Опции - EMAIL_VALIDATION_MESSAGE: "Адрес эл. почты должен быть действительным" - PASSWORD_VALIDATION_MESSAGE: "Пароль должен содержать как минимум одно число, одну прописную и строчную букву и быть не менее 8 символов" - TIMEOUT_HELP: "Устанавливает тайм-аут сеанса в секундах, когда функция «Запомнить меня» включена и установлена пользователем. Минимум 604800, что означает 1 неделю" - GROUPS_HELP: "Список групп, в которые войдет новый зарегистрированный пользователь" - SITE_ACCESS_HELP: "Список уровней доступа к сайту, зарегистрированных пользователей. Пример: `login` ->` true`" - WELCOME: "Добро пожаловать" - REDIRECT_AFTER_ACTIVATION: "Перенаправление после активации пользователя" - REDIRECT_AFTER_ACTIVATION_HELP: "Используется, если пользователю требуется активировать учетную запись по электронной почте. После активации этот маршрут будет показан" - REGISTRATION_DISABLED: "Регистрация отключена" - USE_PARENT_ACL_LABEL: "Использовать родительские правила доступа" - USE_PARENT_ACL_HELP: "Проверьте правила доступа к родителям, если правила не определены" - PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Защита защищенных страниц." - PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Если этот параметр включен, то доступ к защищенной странице для входа в систему также защищен паролем, и его нельзя увидеть, если он не зарегистрирован" - SECURITY_TAB: "Безопасность" - MAX_RESETS_COUNT: "Максимальное количество сброса пароля" - MAX_RESETS_COUNT_HELP: "Настройка защиты пароля от флуда (0 - не ограничено)" - MAX_RESETS_INTERVAL: "Максимальный интервал сброса пароля" - MAX_RESETS_INTERVAL_HELP: "Интервал времени для максимального количества сбросов пароля" - FORGOT_CANNOT_RESET_IT_IS_BLOCKED: "Невозможно сбросить пароль для %s, функция сброса пароля временно отключена, попробуйте позже (максимум %s минут)" - MAX_LOGINS_COUNT: "Максимальное количество входов" - MAX_LOGINS_COUNT_HELP: "Настройка защиты от флуда (0 - не ограничено)" - MAX_LOGINS_INTERVAL: "Максимальный интервал входа" - MAX_LOGINS_INTERVAL_HELP: "Временной интервал для значения счетчика входа" - TOO_MANY_LOGIN_ATTEMPTS: "Слишком много неудачных попыток входа в настроенное время (%s минут)" - SECONDS: "секунд" - RESETS: "сбросов" - ATTEMPTS: "попыток" - ROUTES: "Маршруты" - ROUTE_FORGOT: "Забыли пароль" - ROUTE_RESET: "Сброса пароля" - ROUTE_PROFILE: "Профиля пользователя" - ROUTE_ACTIVATE: "Активации пользователя" - -hr: - PLUGIN_LOGIN: - ACCESS_DENIED: Pristup odbijen... - LOGIN_FAILED: Prijava nije uspjela... - BTN_LOGIN: Prijava - BTN_LOGOUT: Odjava - BTN_FORGOT: Zaboravih - BTN_REGISTER: Registriraj - REMEMBER_ME: Zapamti me - BUILTIN_CSS: Koristi ugrađeni CSS - BUILTIN_CSS_HELP: Uključi CSS koji dolazi sa admin pluginom - ROUTE: Putanja prijave - ROUTE_REGISTER: Putanja registracije - USERNAME_NOT_VALID: "Korisničko ime bi trebalo biti između 3 i 16 znakova, uključujući mala slova, brojeve, _, i crtice. VELIKA SLOVA, razmaci, i posebni znakovi nisu dopušteni" - USERNAME_NOT_AVAILABLE: "Korisničko ime %s već postoji, odaberi neko drugo" - PASSWORD_NOT_VALID: "Lozinka mora sadržavati bar jedan broj i jedno veliko i malo slovo, i bar još 8 ili više znakova" - PASSWORDS_DO_NOT_MATCH: "Lozinke se ne slažu. Poonovo provjeri da li si unio istu lozinku dva puta" - USER_NEEDS_EMAIL_FIELD: "Korisnik treba email polje" - EMAIL_SENDING_FAILURE: "Došlo je do greške pri slanju emaila" - ACTIVATION_LINK_EXPIRED: "Aktivacijski link je istekao" - USER_ACTIVATED_SUCCESSFULLY: "Korisnik je uspješno aktiviran" - INVALID_REQUEST: "Nevaljani zahtjev" - USER_REGISTRATION: "Registracija korisnika" - USER_REGISTRATION_ENABLED_HELP: "Omogući registraciju korisnika" - VALIDATE_PASSWORD1_AND_PASSWORD2: "Validiraj duplo unesenu lozinku" - VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validiraj i usporedi dva različčita polja za lozinke, imenovana `lozinka1` i `lozinka2`. Omogući ovo ako imaš dva polja za lozinke u formularu registracije" - LOGIN_AFTER_REGISTRATION: "Ulogiraj korisnika nakon reegistracije" - LOGIN_AFTER_REGISTRATION_HELP: "Ulogiraj korisnika odmah nakon registracije. Ako je potrebna email aktivacija, korisnik će biti ulogiran odmah nakon aktiviranja računa" - SEND_ACTIVATION_EMAIL: "Pošalji aktivacijski email" - SEND_ACTIVATION_EMAIL_HELP: "Šalje email korisniku da aktivira svoja račun." - SEND_NOTIFICATION_EMAIL: "Pošalji email obavijest" - SEND_NOTIFICATION_EMAIL_HELP: "Obavještava administratora da se novi korisnik registrirao. Email će biti poslan na `To` polje u Email Plugin konfiguraciji" - SEND_WELCOME_EMAIL: "Pošalji email dobrodošlice" - SEND_WELCOME_EMAIL_HELP: "Šalje email novoregistriranom korisniku" - DEFAULT_VALUES: "Određene vrijednosti" - DEFAULT_VALUES_HELP: "List of field names and values associated, that will be added to the user profile (yaml file) by default, without being configurable by the user. Separate multiple values with a comma, with no spaces between the values" - ADDITIONAL_PARAM_KEY: "Parametar" - ADDITIONAL_PARAM_VALUE: "Vrijednost" - REGISTRATION_FIELDS: "Registracijska polja" - REGISTRATION_FIELDS_HELP: "Add the fields that will be added to the user yaml file. Fields not listed here will not be added even if present in the registration form" - REGISTRATION_FIELD_KEY: "Ime polja" - OPTIONS: Opcije - -hu: - PLUGIN_LOGIN: - ACCESS_DENIED: Hozzáférés megtagadva... - LOGIN_FAILED: Sikertelen belépés... - LOGIN_SUCCESSFUL: Sikeresen beléptél. - BTN_LOGIN: Belépés - BTN_LOGOUT: Kilépés - BTN_FORGOT: Elfelejtettem - BTN_REGISTER: Regisztráció - REMEMBER_ME: Jegyezz Meg - REMEMBER_ME_HELP: "Elhelyezünk egy hosszú lejáratú sütit a böngésződben, hogy belépve maradhass két munkamenet között." - REMEMBER_ME_STOLEN_COOKIE: "Valaki a belépési adataid felhasználásával látogatta meg ezt az oldalt! Minden munkamenetet kiléptettünk. Kérlek, jelentkezz be ismét és ellenőrizd az adataidat." - BUILTIN_CSS: Beépített CSS használata - BUILTIN_CSS_HELP: Az admin plugin által biztosított CSS beillesztése - ROUTE: Belépés útvonala - ROUTE_HELP: Egyedi útvonal egy egyedi belépő oldalhoz, melyet az aktuális téma biztosít - ROUTE_REGISTER: Regisztráció útvonala - ROUTE_REGISTER_HELP: A regisztrációs oldal elérési útja. Akkor állítsd be, ha a beépített regisztrációs oldalt szeretnéd használni - USERNAME_NOT_VALID: "A felhasználónév 3-16 karakter hosszú legyen, tartalmazhat kisbetűket, számokat, aláhúzást és kötőjelet. Nagybetűk, szóköz és speciális karakterek használata nem megengedett" - USERNAME_NOT_AVAILABLE: "%s nevű felhasználó már létezik, kérlek válassz más felhasználónevet" - PASSWORD_NOT_VALID: "A jelszónak tartalmaznia kell legalább egy számot, egy kisbetűt és egy nagybetűt, valamint legalább 8 karakter hosszú kell, hogy legyen" - PASSWORDS_DO_NOT_MATCH: "A két jelszó nem egyezik meg. Győzödj meg róla, hogy azonos legyen a kettő" -ro: - PLUGIN_LOGIN: - USERNAME: "Nume utilizator" - PASSWORD: "Parolă" - ACCESS_DENIED: "Acces refuzat..." - LOGIN_FAILED: "Logare eșuată..." - LOGIN_SUCCESSFUL: "Ați fost logat cu succes." - BTN_LOGIN: "Logare" - BTN_LOGOUT: "Ieșire din cont " - BTN_FORGOT: "Am uitat" - BTN_REGISTER: "Înregistrare" - BTN_RESET: "Resetează parola" - BTN_SEND_INSTRUCTIONS: "Trimite instrucțiuni pentru resetare" - RESET_LINK_EXPIRED: "Link-ul pentru resetarea parolei a expirat, vă rugăm încercați din nou " - RESET_PASSWORD_RESET: "Parola a fost modificată" - RESET_INVALID_LINK: "Link-ul pentru resetare este invalid, Invalid reset link used, vă rugăm încercați din nou " - FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instrucțiunile pentru resetarea parolei au fst trimise pe email" - FORGOT_FAILED_TO_EMAIL: "Instrucțiunile nu au putut fi trimise pe email, vă rugăm încercați mai târziu " - FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Parola nu poate fi resetată pentru %s, nu este setată nici o adresă de email" - FORGOT_USERNAME_DOES_NOT_EXIST: "Utilizatorul cu numele %s nu există" - FORGOT_EMAIL_NOT_CONFIGURED: "Parola nu poate fi resetată. Acest site nu este configurat pentru a trimite email-uri." - FORGOT_EMAIL_SUBJECT: "%s Cerere de resetare a parolei" - FORGOT_EMAIL_BODY: "

Resetare parolă

Dragă %1$s,

O cerere de resetare a parolei a fost făcută în data de %4$s.


Apasă aici pentru a reseta parola

Alternativ, copiază URL de mai jos în bara de adrese a browser-ului favorit:

%2$s


Cu respect,

%3$s

" - SESSION: "“Ține-mă minte”-Sesiune" - REMEMBER_ME: "Ține-mă minte" - REMEMBER_ME_HELP: "Setează o cookie în browserul Dvs. ce permite menținerea datelor de logare între sesiuni." - REMEMBER_ME_STOLEN_COOKIE: "Altcineva a folosit darele Dvs de logare pentru a accesa această pagină! Toate sesiunile au fost deconectate. Vă rugăm să vă logați cu datele Dvs. și să verificați toate detaliile. " - BUILTIN_CSS: "Folosește CSS-ul din modul" - BUILTIN_CSS_HELP: "Include CSS-ul furnizat de către modulul Admin" - ROUTE: "Calea pentru logare" - ROUTE_HELP: "O rută personalizată către pagina de logare pe care o furnizează tema activă " - ROUTE_REGISTER: "Calea pentru înregistrare " - ROUTE_REGISTER_HELP: "Ruta către pagina de înregistrare. Setați această rută dacă doriți folosirea paginei implicite pentru înregistrare. Lăsați gol dacă folosiți o pagină personalizată pentru înregistrare." - USERNAME_NOT_VALID: "Numele de utilizator trebuie să fie între 3-16 caractere, incluzând litere mici, numere, linii de subliniere și cratime. Literele de tipar, spațiile și caracterele speciale nu sunt permise. " - USERNAME_NOT_AVAILABLE: "Utilizatorul %s există deja, vă rugăm alegeți un alt nume de utilizator " - PASSWORD_NOT_VALID: " Parola trebuie să conțină cel puțin 1 număr și o literă de tipar și o literă mică; și să aibă minim 8 caractere" - PASSWORDS_DO_NOT_MATCH: " Parolele nu sunt identice. Vă rugăm verificați că ași scris aceeiași parolă de două ori." - USER_NEEDS_EMAIL_FIELD: "Utilizatorul trebuie să aibă adresa de email completată" - EMAIL_SENDING_FAILURE: "A apărut o eroare la trimirea email-ului" - ACTIVATION_EMAIL_SUBJECT: "Activați-vă contrul pentru %s" - ACTIVATION_EMAIL_BODY: "Salut %s, apasă aici pentru a-ți activa contul de pe %s" - WELCOME_EMAIL_SUBJECT: "Bine ați venit pe %s" - WELCOME_EMAIL_BODY: "Salut %s, bine ai venit la %s!" - NOTIFICATION_EMAIL_SUBJECT: "Utilizator nou pe %s" - NOTIFICATION_EMAIL_BODY: "Salut, un nou utilizator s-a înregistrat pe %s. Nume de utilizator: %s, adresă de email: %s" - ACTIVATION_LINK_EXPIRED: "Link-ul pentru activare este expirat" - USER_ACTIVATED_SUCCESSFULLY: "Utilizator activat cu succes" - INVALID_REQUEST: "Cerere invalidă " - USER_REGISTRATION: "Înregistrare utilizator " - USER_REGISTRATION_ENABLED_HELP: "Activați înregistrarea utilizatorilor" - VALIDATE_PASSWORD1_AND_PASSWORD2: "Validați parola introdusă de două ori" - VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validați și comparați cele două câmpuri pentru parolă cu numele `password1` și `password2`. Activați această opțiune dacă există două câmpuri pentru parolă în formularul de înregistrare." - SET_USER_DISABLED: "Setați utilizatorul dezactivat" - SET_USER_DISABLED_HELP: "Cel mai bine să fie folosit împreună cu email-ul pentru activare. Adaugă utilizatorul în Grav dar îl setează ca dezactivat" - LOGIN_AFTER_REGISTRATION: "Loghează utilizatorul după înregistrare" - LOGIN_AFTER_REGISTRATION_HELP: "Imediat după înregistrare loghează utilizatorul în cont. Dacă este necesară activarea prin email, utilizatorul va fi logat imediat după activarea contului." - SEND_ACTIVATION_EMAIL: "Trimite email-ul pentru activare" - SEND_ACTIVATION_EMAIL_HELP: "Trimite un email către utilizatorul nou înregistrat pentru activarea contului. Activați opțiunea `Setați utilizatorul dezactivat` când folosiți această opțiune pentru a seta utilizatorul dezactivat și pentru a trimite un email automat pentru activarea contului. " - SEND_NOTIFICATION_EMAIL: "Trimite email cu notificare" - SEND_NOTIFICATION_EMAIL_HELP: "Notifică adminstratorul site-ului când un utilizator nou s-a înregistrat. Email-ul va di trimis către adresa `Către` din configurarea modului de Email" - SEND_WELCOME_EMAIL: "Trimite email de bun venit" - SEND_WELCOME_EMAIL_HELP: "Trimite un email către utilizatorul nou înregistrat." - DEFAULT_VALUES: " Valori implicite" - DEFAULT_VALUES_HELP: "Listă de câmpuri și valorile asociate acestora ce vor fi adăugate profilului utilizatorului (în fișierul yaml) în mod implicit fără a putea fi configurabile de către utilizator. Separați valorile multiple cu virgulă, fără spații între valori." - ADDITIONAL_PARAM_KEY: "Parametru" - ADDITIONAL_PARAM_VALUE: "Valoare" - REGISTRATION_FIELDS: "Câmpuri pentru înregistrare" - REGISTRATION_FIELDS_HELP: "Adaugă câmpurile ce vor fi adăugate fișierului yaml al utilizatorului. Câmpurile care nu sunt listate aici nu vor fi adăugate chiar dacă sunt prezente în formularul de înregistrare." - REGISTRATION_FIELD_KEY: " Numele câmpului" - REDIRECT_AFTER_LOGIN: "Redirecționează după logare" - REDIRECT_AFTER_LOGIN_HELP: "Ruta personalizată pentru redirecționare după logare" - REDIRECT_AFTER_REGISTRATION: "Redirecționează după înregistrare" - REDIRECT_AFTER_REGISTRATION_HELP: "Ruta personalizată pentru redirecționare după înregistrare" - OPTIONS: 'Opțiuni' - EMAIL_VALIDATION_MESSAGE: "Trebuie să fie o adresă de email validă" - PASSWORD_VALIDATION_MESSAGE: "Parola trebuie să conțină cel puțin un număr și o literă de tipar și să aibă cel puțin 8 caractere" - TIMEOUT_HELP: "Setează pauza pentru sesiune când este activat 'Ține-mă minte' de către utilizator. Minimul este de 604800 care înseamnă 1 săptămână." - GROUPS_HELP: "Lista grupurilor din care utilizatorii nou înregistrați vor face parte, dacă este necesar" - SITE_ACCESS_HELP: "Lista cu niveluri de acces la care utilizatorul nou înregistrat are acces. De eg: `login` -> `true` " - WELCOME: "Bine ați venit" - REDIRECT_AFTER_ACTIVATION: "Redirecționează după activarea utilizatorului" - REDIRECT_AFTER_ACTIVATION_HELP: "Folosită dacă utilizatorul trebuie să-și activeze contul prin email. Odată activat contul va fi folosită această rută." - REGISTRATION_DISABLED: " Dezactivează înregistrarea " - USE_PARENT_ACL_LABEL: "Folosește regulile de acces ale părintelui" - USE_PARENT_ACL_HELP: "Verifică regulie de acces ale părintelui dacă nu sunt specificate alte reguli de acces" - PROTECT_PROTECTED_PAGE_MEDIA_LABEL: " Protejează media ce aparține paginii de logare " - PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Dacă este activată, media ce aparține unei pagini de logare este protejată și nu poate fi accesată decât după logare." diff --git a/languages/de.yaml b/languages/de.yaml new file mode 100644 index 0000000..cb65837 --- /dev/null +++ b/languages/de.yaml @@ -0,0 +1,47 @@ +PLUGIN_LOGIN: + USERNAME: "Benutzername" + EMAIL: "Email" + USERNAME_EMAIL: "Benutzername/Email" + PASSWORD: "Passwort" + ACCESS_DENIED: "Zugang verweigert" + LOGIN_FAILED: "Login fehlgeschlagen..." + LOGIN_SUCCESSFUL: "Du wurdest erfolgreich eingeloggt." + BTN_LOGIN: "Anmelden" + BTN_LOGOUT: "Abmelden" + BTN_FORGOT: "Vergessen" + BTN_REGISTER: "Registrieren" + REMEMBER_ME: "Angemeldet bleiben" + REMEMBER_ME_HELP: "Speichert einen Cookie im Browser, welcher eine fortwährende Anmeldung sicherstellt." + BUILTIN_CSS: "Nutze das integrierte CSS" + BUILTIN_CSS_HELP: "Nutze das CSS, welches vom Admin Plugin bereitgestellt werden" + ROUTE: "Anmeldepfad" + ROUTE_REGISTER: "Registrierungspfad" + USERNAME_NOT_AVAILABLE: "Der Nutzername %s existiert bereits, bitte wähle einen Anderen" + USER_NEEDS_EMAIL_FIELD: "Der Nutzer benötigt ein E-Mail Feld" + EMAIL_SENDING_FAILURE: "Ein Fehler ist beim senden der E-Mail aufgetreten" + ACTIVATION_EMAIL_SUBJECT: "Aktiviere dein Account auf %s" + ACTIVATION_EMAIL_BODY: "Hi %s, click %s to activate your account on %s" + WELCOME_EMAIL_SUBJECT: "Willkommen zu %s" + WELCOME_EMAIL_BODY: "Hi %s, willkommen zu %s!" + NOTIFICATION_EMAIL_SUBJECT: "Neuer Nutzer auf %s" + NOTIFICATION_EMAIL_BODY: "Hi, ein neuer Nutzer hat sich auf %s registriert. Nutzername: %s, E-Mail: %s" + EMAIL_FOOTER: "GetGrav.org" + ACTIVATION_LINK_EXPIRED: "Aktivierungslink ist abgelaufen" + USER_ACTIVATED_SUCCESSFULLY: "Nutzer erfolgreich aktiviert" + INVALID_REQUEST: "Ungültige Anfrage" + USER_REGISTRATION: "Nutzer Registrierung" + USER_REGISTRATION_ENABLED_HELP: "Aktiviere die Nutzer Registrierung" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Überprüfe das doppelt eingegebene Passwort" + SEND_ACTIVATION_EMAIL: "Aktivierungs E-Mail senden" + SEND_NOTIFICATION_EMAIL: "Benachtichtigungs E-Mail senden" + SEND_WELCOME_EMAIL: "Sende eine Willkommens E-Mail" + DEFAULT_VALUES: "Standard Werte" + ADDITIONAL_PARAM_KEY: "Parameter" + ADDITIONAL_PARAM_VALUE: "Wert" + REGISTRATION_FIELDS: "Registrierungsfelder" + REGISTRATION_FIELD_KEY: "Feldname" + REDIRECT_AFTER_LOGIN: "Umleitung nach Login" + REDIRECT_AFTER_REGISTRATION: "Umleitung nach Registrierung" + OPTIONS: "Optionen" + EMAIL_VALIDATION_MESSAGE: "Muss eine gültige E-Mail Adresse sein" + WELCOME: "Willkommen" \ No newline at end of file diff --git a/languages/en.yaml b/languages/en.yaml new file mode 100644 index 0000000..14edd62 --- /dev/null +++ b/languages/en.yaml @@ -0,0 +1,115 @@ +PLUGIN_LOGIN: + USERNAME: "Username" + EMAIL: "Email" + USERNAME_EMAIL: "Username/Email" + PASSWORD: "Password" + ACCESS_DENIED: "Access denied..." + LOGIN_FAILED: "Login failed..." + LOGIN_SUCCESSFUL: "You have been successfully logged in." + BTN_LOGIN: "Login" + BTN_LOGOUT: "Logout" + BTN_FORGOT: "Forgot" + BTN_REGISTER: "Register" + BTN_RESET: "Reset Password" + BTN_SEND_INSTRUCTIONS: "Send Reset Instructions" + RESET_LINK_EXPIRED: "Reset link has expired, please try again" + RESET_PASSWORD_RESET: "Password has been reset" + RESET_INVALID_LINK: "Invalid reset link used, please try again" + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instructions to reset your password have been sent via email" + FORGOT_FAILED_TO_EMAIL: "Failed to email instructions, please try again later" + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Cannot reset password for %s, no email address is set" + FORGOT_USERNAME_DOES_NOT_EXIST: "User with username %s does not exist" + FORGOT_EMAIL_NOT_CONFIGURED: "Cannot reset password. This site is not configured to send emails" + FORGOT_EMAIL_SUBJECT: "%s Password Reset Request" + FORGOT_EMAIL_BODY: "

Password Reset

Dear %1$s,

A request was made on %4$s to reset your password.


Click this to reset your password

Alternatively, copy the following URL into your browser's address bar:

%2$s


Kind regards,

%3$s

" + SESSION: "“Remember Me”-Session" + REMEMBER_ME: "Remember Me" + REMEMBER_ME_HELP: "Sets a persistent cookie on your browser to allow persistent-login authentication between sessions." + REMEMBER_ME_STOLEN_COOKIE: "Someone else has used your login information to access this page! All sessions were logged out. Please log in with your credentials and check your data." + BUILTIN_CSS: "Use built in CSS" + BUILTIN_CSS_HELP: "Include the CSS provided by the admin plugin" + ROUTE: "Login path" + ROUTE_HELP: "Custom route to a custom login page that your theme provides" + ROUTE_REGISTER: "Registration path" + ROUTE_REGISTER_HELP: "Route to the registration page. Set this if you want to use the built-in registration page. Leave it empty if you have your own registration form" + USERNAME_NOT_VALID: "Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed" + USERNAME_NOT_AVAILABLE: "Username %s already exists, please pick another username" + EMAIL_NOT_AVAILABLE: "Email address %s already exists, please pick another email address" + PASSWORD_NOT_VALID: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" + PASSWORDS_DO_NOT_MATCH: "Passwords do not match. Double-check you entered the same password twice" + USER_NEEDS_EMAIL_FIELD: "The user needs an email field" + EMAIL_SENDING_FAILURE: "An error occurred while sending the email" + ACTIVATION_EMAIL_SUBJECT: "Activate your account on %s" + ACTIVATION_EMAIL_BODY: "

%Account Activation

Hi %1$s,

Your account has been successfully created on %3$s, but you cannot login until it is activated.


Activate Your Account Now

Alternatively, copy the following URL into your browser's address bar:

%2$s


Kind regards,

%4$s

" + ACTIVATION_NOTICE_MSG: "Hi %s, your account is created, please check your email to fully activate it" + WELCOME_EMAIL_SUBJECT: "Welcome to %s" + WELCOME_EMAIL_BODY: "

Account Created

Hi %1$s,

Your account has been successfully created on %3$s.


Login Now

Alternatively, copy the following URL into your browser's address bar:

%2$s


Kind regards,

%4$s

" + WELCOME_NOTICE_MSG: "Hi %s, your account has been successfully created" + NOTIFICATION_EMAIL_SUBJECT: "New user on %s" + NOTIFICATION_EMAIL_BODY: "

New User

Hi, a new user registered on %1$s.

  • Username: %2$s
  • Email: %3$s


Visit %1$s

" + EMAIL_FOOTER: "GetGrav.org" + ACTIVATION_LINK_EXPIRED: "Activation link expired" + USER_ACTIVATED_SUCCESSFULLY: "User activated successfully" + INVALID_REQUEST: "Invalid request" + USER_REGISTRATION: "User Registration" + USER_REGISTRATION_ENABLED_HELP: "Enable the user registration" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Validate double entered password" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validate and compare two different fields for the passwords, named `password1` and `password2`. Enable this if you have two password fields in the registration form" + SET_USER_DISABLED: "Set the user as disabled" + SET_USER_DISABLED_HELP: "Best used along with the `Send activation email` email. Adds the user to Grav, but sets it as disabled" + LOGIN_AFTER_REGISTRATION: "Login the user after registration" + LOGIN_AFTER_REGISTRATION_HELP: "Immediately login the user after the registration. If email activation is required, the user will be logged in immediately after activating the account" + SEND_ACTIVATION_EMAIL: "Send activation email" + SEND_ACTIVATION_EMAIL_HELP: "Sends an email to the user to activate his account. Enable the `Set the user as disabled` option when using this feature, so the user will be set as disabled and an email will be sent to activate the account" + SEND_NOTIFICATION_EMAIL: "Send notification email" + SEND_NOTIFICATION_EMAIL_HELP: "Notifies the site admin that a new user has registered. The email will be sent to the `To` field in the Email Plugin configuration" + SEND_WELCOME_EMAIL: "Send welcome email" + SEND_WELCOME_EMAIL_HELP: "Sends an email to the newly registered user" + DEFAULT_VALUES: "Default values" + DEFAULT_VALUES_HELP: "List of field names and values associated, that will be added to the user profile (yaml file) by default, without being configurable by the user. Separate multiple values with a comma, with no spaces between the values" + ADDITIONAL_PARAM_KEY: "Parameter" + ADDITIONAL_PARAM_VALUE: "Value" + REGISTRATION_FIELDS: "Registration fields" + REGISTRATION_FIELDS_HELP: "Add the fields that will be added to the user Yaml file. Fields not listed here will not be added even if present in the registration form" + REGISTRATION_FIELD_KEY: "Field name" + REDIRECT_AFTER_LOGIN: "Redirect after login" + REDIRECT_AFTER_LOGIN_HELP: "Custom route to redirect after login" + REDIRECT_AFTER_REGISTRATION: "Redirect after registration" + REDIRECT_AFTER_REGISTRATION_HELP: "Custom route to redirect after the registration" + OPTIONS: "Options" + EMAIL_VALIDATION_MESSAGE: "Must be a valid email address" + PASSWORD_VALIDATION_MESSAGE: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" + TIMEOUT_HELP: "Sets the session timeout in seconds when Remember Me is enabled and checked by the user. Minimum is 604800 which means 1 week" + GROUPS_HELP: "List of groups the new registered user will be part of, if any" + SITE_ACCESS_HELP: "List of site access levels the new registered user will have. Example: `login` -> `true` " + WELCOME: "Welcome" + REDIRECT_AFTER_ACTIVATION: "Redirect after the user activation" + REDIRECT_AFTER_ACTIVATION_HELP: "Used if the user is required to activate the account via email. Once activated, this route will be shown" + REGISTRATION_DISABLED: "Registration disabled" + USE_PARENT_ACL_LABEL: "Use parent access rules" + USE_PARENT_ACL_HELP: "Check for parent access rules if no rules are defined" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Protect a login-protected page media" + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "If enabled, media of a login protected page is login protected as well and cannot be seen unless logged in" + SECURITY_TAB: "Security" + MAX_RESETS_COUNT: "Max password resets count" + MAX_RESETS_COUNT_HELP: "Password reset flood protection setting (0 - not limited)" + MAX_RESETS_INTERVAL: "Max password resets interval" + MAX_RESETS_INTERVAL_HELP: "The time interval for the max password resets count value" + FORGOT_CANNOT_RESET_IT_IS_BLOCKED: "Cannot reset password for %s, password reset functionality temporarily blocked, please try later (maximum %s minutes)" + MAX_LOGINS_COUNT: "Max logins count" + MAX_LOGINS_COUNT_HELP: "Flood protection setting (0 - not limited)" + MAX_LOGINS_INTERVAL: "Max logins interval" + MAX_LOGINS_INTERVAL_HELP: "The time interval for the login count value" + TOO_MANY_LOGIN_ATTEMPTS: "Too many failed login attempted in the configured time (%s minutes)" + SECONDS: "seconds" + RESETS: "resets" + ATTEMPTS: "attempts" + ROUTES: "Routes" + ROUTE_FORGOT: "Forgot password route" + ROUTE_RESET: "Reset password route" + ROUTE_PROFILE: "User profile route" + ROUTE_ACTIVATE: "User activation route" + LOGGED_OUT: "You have been successfully logged out..." + PAGE_RESTRICTED: "Access is restricted, please login..." + DYNAMIC_VISIBILITY: "Dynamic Page Visibility" + DYNAMIC_VISIBILITY_HELP: "Allows dynamic processing of page visibility base on access rules if login.visibility_requires_access is set to true on a page" \ No newline at end of file diff --git a/languages/fr.yaml b/languages/fr.yaml new file mode 100644 index 0000000..028af85 --- /dev/null +++ b/languages/fr.yaml @@ -0,0 +1,89 @@ +PLUGIN_LOGIN: + USERNAME: "Nom d’utilisateur" + EMAIL: "E-mail" + USERNAME_EMAIL: "Nom d’utilisateur/E-mail" + PASSWORD: "Mot de passe" + ACCESS_DENIED: "Accès refusé..." + LOGIN_FAILED: "Échec de la connexion..." + LOGIN_SUCCESSFUL: "Vous vous êtes connecté avec succès." + BTN_LOGIN: "Connexion" + BTN_LOGOUT: "Déconnexion" + BTN_FORGOT: "Mot de passe oublié" + BTN_REGISTER: "S’enregister" + BTN_RESET: "Réinitialiser le mot de passe" + BTN_SEND_INSTRUCTIONS: "Envoyer les instructions de Réinitialisation" + RESET_LINK_EXPIRED: "Le lien de réinitialisation a expiré, veuillez réessayer" + RESET_PASSWORD_RESET: "Le mot de passe a été réinitialisé" + RESET_INVALID_LINK: "Le lien de réinitialisation utilisé n’est pas valide, veuillez réessayer" + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Les instructions pour la réinitialisation de votre mot de passe ont été envoyées par e-mail" + FORGOT_FAILED_TO_EMAIL: "Impossible d’envoyer les instructions, veuillez réessayer ultérieurement" + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Impossible de réinitialiser le mot de passe pour %s, aucune adresse e-mail n’a été paramétrée" + FORGOT_USERNAME_DOES_NOT_EXIST: "L’utilisateur avec le nom d’utilisateur %s n’existe pas" + FORGOT_EMAIL_NOT_CONFIGURED: "Impossible de réinitialiser le mot de passe. Ce site n’est pas configuré pour envoyer des e-mails" + FORGOT_EMAIL_SUBJECT: "Demande de réinitialisation de mot de passe %s" + FORGOT_EMAIL_BODY: "

Réinitialisation de mot de passe

%1$s,

Une demande a été faite sur %4$s pour la réinitialisation de votre mot de passe.


Cliquez ici pour réinitialiser votre mot de passe

Vous pouvez également copier l’URL suivante dans la barre d’adresse de votre navigateur :

%2$s


Cordialement,

%3$s

" + SESSION: "Session - “Se souvenir de moi”" + REMEMBER_ME: "Se souvenir de moi" + REMEMBER_ME_HELP: "Définit un cookie persistant sur votre navigateur autorisant l’authentification par connexion persistante entre les sessions." + REMEMBER_ME_STOLEN_COOKIE: "Quelqu’un d’autre a utilisé vos informations de connexion pour accéder à cette page ! Toutes les sessions ont été déconnectées. Veuillez vous connecter avec vos identifiants et vérifier vos données." + BUILTIN_CSS: "Utiliser les CSS intégrés" + BUILTIN_CSS_HELP: "Utiliser les CSS fournis dans le plugin d’administration" + ROUTE: "Chemin de connexion" + ROUTE_HELP: "Chemin personnalisé vers une page de connexion personnalisée proposée par votre thème" + ROUTE_REGISTER: "Chemin vers l’inscription" + ROUTE_REGISTER_HELP: "Chemin vers la page d’inscription. A définir si vous souhaitez utiliser la page d’inscription intégrée. Laisser vide si vous disposez de votre propre formulaire d’inscription." + USERNAME_NOT_VALID: "Le nom d’utilisateur doit comporter entre 3 et 16 caractères et peut être composé de lettres minuscules, de chiffres et de tirets de soulignement (underscores) et des traits d’union. Les lettres majuscules, les espaces et les caractères spéciaux ne sont pas autorisés." + USERNAME_NOT_AVAILABLE: "Le nom d’utilisateur %s existe déjà, veuillez en choisir un autre." + PASSWORD_NOT_VALID: "Le mot de passe doit contenir au moins un chiffre, une majuscule et une minuscule et être composé d’au moins 8 caractères" + PASSWORDS_DO_NOT_MATCH: "Les mots de passe sont différents. Réessayez de saisir le même mot de passe deux fois." + USER_NEEDS_EMAIL_FIELD: "L’utilisateur a besoin d’un champ pour e-mail" + EMAIL_SENDING_FAILURE: "Une erreur est survenue lors de l’envoi de l’e-mail." + ACTIVATION_EMAIL_SUBJECT: "Activer votre compte sur %s" + ACTIVATION_EMAIL_BODY: "Bonjour %s, cliquez pour activer votre compte sur %s" + WELCOME_EMAIL_SUBJECT: "Bienvenue sur %s" + WELCOME_EMAIL_BODY: "Bonjour %s, bienvenue sur %s!" + NOTIFICATION_EMAIL_SUBJECT: "Nouvel utilisateur sur %s" + EMAIL_FOOTER: "GetGrav.org" + NOTIFICATION_EMAIL_BODY: "Bonjour, un nouvel utilisateur s’est inscrit sur %s. Nom d’utilisateur : %s, e-mail : %s" + ACTIVATION_LINK_EXPIRED: "Le lien d’activation a expiré" + USER_ACTIVATED_SUCCESSFULLY: "Utilisateur activé avec succès" + INVALID_REQUEST: "Requête non valide" + USER_REGISTRATION: "Inscription de l’utilisateur" + USER_REGISTRATION_ENABLED_HELP: "Activer l’inscription des utilisateurs" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Valider la double saisie du mot de passe" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Comparer et valider deux champs pour les mots de passe `Mot de passe 1` et `Mot de passe 2`. Activez cette option si vous avez deux champs de mots de passe dans le formulaire d’inscription." + SET_USER_DISABLED: "Définir l’utilisateur comme désactivé" + SET_USER_DISABLED_HELP: "La meilleure pratique si vous utilisez l’option `Envoyer un e-mail d’activation`. Ajoute l’utilisateur à Grav, mais le défini comme étant désactivé." + LOGIN_AFTER_REGISTRATION: "Connecte l’utilisateur après son inscription" + LOGIN_AFTER_REGISTRATION_HELP: "Identifier immédiatement l’utilisateur après l’inscription. Si l’e-mail d’activation est demandé, l’utilisateur sera connecté immédiatement après l’activation du compte." + SEND_ACTIVATION_EMAIL: "Envoyer un e-mail d’activation" + SEND_ACTIVATION_EMAIL_HELP: "Envoyer un e-mail à l’utilisateur pour l’activation son compte. Lorsque vous utilisez cette fonction, activez l’option `Définir l’utilisateur comme désactivé` afin que l’utilisateur soit désactivé et qu’un e-mail lui soit envoyé pour activer son compte." + SEND_NOTIFICATION_EMAIL: "Envoyer un e-mail de notification" + SEND_NOTIFICATION_EMAIL_HELP: "Informe l’administrateur du site qu’un nouvel utilisateur s’est enregistré. L’e-mail sera envoyé à la personne renseignée dans le champ `À` dans la configuration du plugin e-mail." + SEND_WELCOME_EMAIL: "Envoyer un e-mail de bienvenue" + SEND_WELCOME_EMAIL_HELP: "Envoyer un e-mail à un nouvel utilisateur enregistré." + DEFAULT_VALUES: "Valeurs par défaut" + DEFAULT_VALUES_HELP: "Liste des noms et valeurs associés pour les champs. Ils seront ajoutés au profil utilisateur par défaut (fichier yaml), sans pouvoir être configurables par l’utilisateur. Séparez les différentes valeurs par une virgule, sans espaces entre les valeurs." + ADDITIONAL_PARAM_KEY: "Paramètre" + ADDITIONAL_PARAM_VALUE: "Valeur" + REGISTRATION_FIELDS: "Champs d’inscription" + REGISTRATION_FIELDS_HELP: "Ajouter les champs qui seront ajoutés au fichier yaml de l’utilisateur. Les champs non listés ne seront pas ajoutés même s’ils sont présent sur le formulaire d’inscription" + REGISTRATION_FIELD_KEY: "Nom du champ" + REDIRECT_AFTER_LOGIN: "Redirection après connexion" + REDIRECT_AFTER_LOGIN_HELP: "Chemin personnalisé de redirection après la connexion" + REDIRECT_AFTER_REGISTRATION: "Redirection après inscription" + REDIRECT_AFTER_REGISTRATION_HELP: "Chemin personnalisé de redirection après l’inscription" + OPTIONS: "Options" + EMAIL_VALIDATION_MESSAGE: "Doit-être une adresse e-mail valide" + PASSWORD_VALIDATION_MESSAGE: "Le mot de passe doit-être composé d’au moins un chiffre, une majuscule et une minuscule, et au moins 8 caractères" + TIMEOUT_HELP: "Définit le délai d’expiration de la session en secondes lorsque `Se souvenir de moi` est activé et coché par l’utilisateur. Minimum de 604800 ce qui correspond à 1 semaine." + GROUPS_HELP: "Liste des groupes auxquels le nouvel utilisateur enregistré fera partie, le cas échéant." + SITE_ACCESS_HELP: "Liste des niveaux d’accès au site attribués au nouvel utilisateur enregistré. Exemple : `login` -> `true` " + WELCOME: "Bienvenue" + REDIRECT_AFTER_ACTIVATION: "Redirection après l’activation de l’utilisateur" + REDIRECT_AFTER_ACTIVATION_HELP: "Utilisé s’il est nécessaire pour l’utilisateur d’activer le compte par e-mail. Une fois activé, ce chemin s’affichera" + REGISTRATION_DISABLED: "Inscription désactivée" + USE_PARENT_ACL_LABEL: "Appliquer les règles d’accès parentes" + USE_PARENT_ACL_HELP: "Utiliser les règles d’accès parentes si aucune règle n’a été définie" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Protéger le média d'une page par une protection par connexion" + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Si activé, les médias d'une page protégée par connexion sera également protégé par un système de connexion et ne pourra pas être visible à moins d'être connecté." \ No newline at end of file diff --git a/languages/hr.yaml b/languages/hr.yaml new file mode 100644 index 0000000..3090885 --- /dev/null +++ b/languages/hr.yaml @@ -0,0 +1,41 @@ +PLUGIN_LOGIN: + ACCESS_DENIED: "Pristup odbijen..." + LOGIN_FAILED: "Prijava nije uspjela..." + BTN_LOGIN: "Prijava" + BTN_LOGOUT: "Odjava" + BTN_FORGOT: "Zaboravih" + BTN_REGISTER: "Registriraj" + REMEMBER_ME: "Zapamti me" + BUILTIN_CSS: "Koristi ugrađeni CSS" + BUILTIN_CSS_HELP: "Uključi CSS koji dolazi sa admin pluginom" + ROUTE: "Putanja prijave" + ROUTE_REGISTER: "Putanja registracije" + USERNAME_NOT_VALID: "Korisničko ime bi trebalo biti između 3 i 16 znakova, uključujući mala slova, brojeve, _, i crtice. VELIKA SLOVA, razmaci, i posebni znakovi nisu dopušteni" + USERNAME_NOT_AVAILABLE: "Korisničko ime %s već postoji, odaberi neko drugo" + PASSWORD_NOT_VALID: "Lozinka mora sadržavati bar jedan broj i jedno veliko i malo slovo, i bar još 8 ili više znakova" + PASSWORDS_DO_NOT_MATCH: "Lozinke se ne slažu. Poonovo provjeri da li si unio istu lozinku dva puta" + USER_NEEDS_EMAIL_FIELD: "Korisnik treba email polje" + EMAIL_SENDING_FAILURE: "Došlo je do greške pri slanju emaila" + ACTIVATION_LINK_EXPIRED: "Aktivacijski link je istekao" + USER_ACTIVATED_SUCCESSFULLY: "Korisnik je uspješno aktiviran" + INVALID_REQUEST: "Nevaljani zahtjev" + USER_REGISTRATION: "Registracija korisnika" + USER_REGISTRATION_ENABLED_HELP: "Omogući registraciju korisnika" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Validiraj duplo unesenu lozinku" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validiraj i usporedi dva različčita polja za lozinke, imenovana `lozinka1` i `lozinka2`. Omogući ovo ako imaš dva polja za lozinke u formularu registracije" + LOGIN_AFTER_REGISTRATION: "Ulogiraj korisnika nakon reegistracije" + LOGIN_AFTER_REGISTRATION_HELP: "Ulogiraj korisnika odmah nakon registracije. Ako je potrebna email aktivacija, korisnik će biti ulogiran odmah nakon aktiviranja računa" + SEND_ACTIVATION_EMAIL: "Pošalji aktivacijski email" + SEND_ACTIVATION_EMAIL_HELP: "Šalje email korisniku da aktivira svoja račun." + SEND_NOTIFICATION_EMAIL: "Pošalji email obavijest" + SEND_NOTIFICATION_EMAIL_HELP: "Obavještava administratora da se novi korisnik registrirao. Email će biti poslan na `To` polje u Email Plugin konfiguraciji" + SEND_WELCOME_EMAIL: "Pošalji email dobrodošlice" + SEND_WELCOME_EMAIL_HELP: "Šalje email novoregistriranom korisniku" + DEFAULT_VALUES: "Određene vrijednosti" + DEFAULT_VALUES_HELP: "List of field names and values associated, that will be added to the user profile (yaml file) by default, without being configurable by the user. Separate multiple values with a comma, with no spaces between the values" + ADDITIONAL_PARAM_KEY: "Parametar" + ADDITIONAL_PARAM_VALUE: "Vrijednost" + REGISTRATION_FIELDS: "Registracijska polja" + REGISTRATION_FIELDS_HELP: "Add the fields that will be added to the user yaml file. Fields not listed here will not be added even if present in the registration form" + REGISTRATION_FIELD_KEY: "Ime polja" + OPTIONS: "Opcije" \ No newline at end of file diff --git a/languages/hu.yaml b/languages/hu.yaml new file mode 100644 index 0000000..ac9c7f8 --- /dev/null +++ b/languages/hu.yaml @@ -0,0 +1,21 @@ +PLUGIN_LOGIN: + ACCESS_DENIED: "Hozzáférés megtagadva..." + LOGIN_FAILED: "Sikertelen belépés..." + LOGIN_SUCCESSFUL: "Sikeresen beléptél." + BTN_LOGIN: "Belépés" + BTN_LOGOUT: "Kilépés" + BTN_FORGOT: "Elfelejtettem" + BTN_REGISTER: "Regisztráció" + REMEMBER_ME: "Jegyezz Meg" + REMEMBER_ME_HELP: "Elhelyezünk egy hosszú lejáratú sütit a böngésződben, hogy belépve maradhass két munkamenet között." + REMEMBER_ME_STOLEN_COOKIE: "Valaki a belépési adataid felhasználásával látogatta meg ezt az oldalt! Minden munkamenetet kiléptettünk. Kérlek, jelentkezz be ismét és ellenőrizd az adataidat." + BUILTIN_CSS: "Beépített CSS használata" + BUILTIN_CSS_HELP: "Az admin plugin által biztosított CSS beillesztése" + ROUTE: "Belépés útvonala" + ROUTE_HELP: "Egyedi útvonal egy egyedi belépő oldalhoz, melyet az aktuális téma biztosít" + ROUTE_REGISTER: "Regisztráció útvonala" + ROUTE_REGISTER_HELP: "A regisztrációs oldal elérési útja. Akkor állítsd be, ha a beépített regisztrációs oldalt szeretnéd használni" + USERNAME_NOT_VALID: "A felhasználónév 3-16 karakter hosszú legyen, tartalmazhat kisbetűket, számokat, aláhúzást és kötőjelet. Nagybetűk, szóköz és speciális karakterek használata nem megengedett" + USERNAME_NOT_AVAILABLE: "%s nevű felhasználó már létezik, kérlek válassz más felhasználónevet" + PASSWORD_NOT_VALID: "A jelszónak tartalmaznia kell legalább egy számot, egy kisbetűt és egy nagybetűt, valamint legalább 8 karakter hosszú kell, hogy legyen" + PASSWORDS_DO_NOT_MATCH: "A két jelszó nem egyezik meg. Győzödj meg róla, hogy azonos legyen a kettő" \ No newline at end of file diff --git a/languages/ro.yaml b/languages/ro.yaml new file mode 100644 index 0000000..307e590 --- /dev/null +++ b/languages/ro.yaml @@ -0,0 +1,86 @@ +PLUGIN_LOGIN: + USERNAME: "Nume utilizator" + PASSWORD: "Parolă" + ACCESS_DENIED: "Acces refuzat..." + LOGIN_FAILED: "Logare eșuată..." + LOGIN_SUCCESSFUL: "Ați fost logat cu succes." + BTN_LOGIN: "Logare" + BTN_LOGOUT: "Ieșire din cont " + BTN_FORGOT: "Am uitat" + BTN_REGISTER: "Înregistrare" + BTN_RESET: "Resetează parola" + BTN_SEND_INSTRUCTIONS: "Trimite instrucțiuni pentru resetare" + RESET_LINK_EXPIRED: "Link-ul pentru resetarea parolei a expirat, vă rugăm încercați din nou " + RESET_PASSWORD_RESET: "Parola a fost modificată" + RESET_INVALID_LINK: "Link-ul pentru resetare este invalid, Invalid reset link used, vă rugăm încercați din nou " + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instrucțiunile pentru resetarea parolei au fst trimise pe email" + FORGOT_FAILED_TO_EMAIL: "Instrucțiunile nu au putut fi trimise pe email, vă rugăm încercați mai târziu " + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Parola nu poate fi resetată pentru %s, nu este setată nici o adresă de email" + FORGOT_USERNAME_DOES_NOT_EXIST: "Utilizatorul cu numele %s nu există" + FORGOT_EMAIL_NOT_CONFIGURED: "Parola nu poate fi resetată. Acest site nu este configurat pentru a trimite email-uri." + FORGOT_EMAIL_SUBJECT: "%s Cerere de resetare a parolei" + FORGOT_EMAIL_BODY: "

Resetare parolă

Dragă %1$s,

O cerere de resetare a parolei a fost făcută în data de %4$s.


Apasă aici pentru a reseta parola

Alternativ, copiază URL de mai jos în bara de adrese a browser-ului favorit:

%2$s


Cu respect,

%3$s

" + SESSION: "“Ține-mă minte”-Sesiune" + REMEMBER_ME: "Ține-mă minte" + REMEMBER_ME_HELP: "Setează o cookie în browserul Dvs. ce permite menținerea datelor de logare între sesiuni." + REMEMBER_ME_STOLEN_COOKIE: "Altcineva a folosit darele Dvs de logare pentru a accesa această pagină! Toate sesiunile au fost deconectate. Vă rugăm să vă logați cu datele Dvs. și să verificați toate detaliile. " + BUILTIN_CSS: "Folosește CSS-ul din modul" + BUILTIN_CSS_HELP: "Include CSS-ul furnizat de către modulul Admin" + ROUTE: "Calea pentru logare" + ROUTE_HELP: "O rută personalizată către pagina de logare pe care o furnizează tema activă " + ROUTE_REGISTER: "Calea pentru înregistrare " + ROUTE_REGISTER_HELP: "Ruta către pagina de înregistrare. Setați această rută dacă doriți folosirea paginei implicite pentru înregistrare. Lăsați gol dacă folosiți o pagină personalizată pentru înregistrare." + USERNAME_NOT_VALID: "Numele de utilizator trebuie să fie între 3-16 caractere, incluzând litere mici, numere, linii de subliniere și cratime. Literele de tipar, spațiile și caracterele speciale nu sunt permise. " + USERNAME_NOT_AVAILABLE: "Utilizatorul %s există deja, vă rugăm alegeți un alt nume de utilizator " + PASSWORD_NOT_VALID: " Parola trebuie să conțină cel puțin 1 număr și o literă de tipar și o literă mică; și să aibă minim 8 caractere" + PASSWORDS_DO_NOT_MATCH: " Parolele nu sunt identice. Vă rugăm verificați că ași scris aceeiași parolă de două ori." + USER_NEEDS_EMAIL_FIELD: "Utilizatorul trebuie să aibă adresa de email completată" + EMAIL_SENDING_FAILURE: "A apărut o eroare la trimirea email-ului" + ACTIVATION_EMAIL_SUBJECT: "Activați-vă contrul pentru %s" + ACTIVATION_EMAIL_BODY: "Salut %s, apasă aici pentru a-ți activa contul de pe %s" + WELCOME_EMAIL_SUBJECT: "Bine ați venit pe %s" + WELCOME_EMAIL_BODY: "Salut %s, bine ai venit la %s!" + NOTIFICATION_EMAIL_SUBJECT: "Utilizator nou pe %s" + NOTIFICATION_EMAIL_BODY: "Salut, un nou utilizator s-a înregistrat pe %s. Nume de utilizator: %s, adresă de email: %s" + ACTIVATION_LINK_EXPIRED: "Link-ul pentru activare este expirat" + USER_ACTIVATED_SUCCESSFULLY: "Utilizator activat cu succes" + INVALID_REQUEST: "Cerere invalidă " + USER_REGISTRATION: "Înregistrare utilizator " + USER_REGISTRATION_ENABLED_HELP: "Activați înregistrarea utilizatorilor" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Validați parola introdusă de două ori" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validați și comparați cele două câmpuri pentru parolă cu numele `password1` și `password2`. Activați această opțiune dacă există două câmpuri pentru parolă în formularul de înregistrare." + SET_USER_DISABLED: "Setați utilizatorul dezactivat" + SET_USER_DISABLED_HELP: "Cel mai bine să fie folosit împreună cu email-ul pentru activare. Adaugă utilizatorul în Grav dar îl setează ca dezactivat" + LOGIN_AFTER_REGISTRATION: "Loghează utilizatorul după înregistrare" + LOGIN_AFTER_REGISTRATION_HELP: "Imediat după înregistrare loghează utilizatorul în cont. Dacă este necesară activarea prin email, utilizatorul va fi logat imediat după activarea contului." + SEND_ACTIVATION_EMAIL: "Trimite email-ul pentru activare" + SEND_ACTIVATION_EMAIL_HELP: "Trimite un email către utilizatorul nou înregistrat pentru activarea contului. Activați opțiunea `Setați utilizatorul dezactivat` când folosiți această opțiune pentru a seta utilizatorul dezactivat și pentru a trimite un email automat pentru activarea contului. " + SEND_NOTIFICATION_EMAIL: "Trimite email cu notificare" + SEND_NOTIFICATION_EMAIL_HELP: "Notifică adminstratorul site-ului când un utilizator nou s-a înregistrat. Email-ul va di trimis către adresa `Către` din configurarea modului de Email" + SEND_WELCOME_EMAIL: "Trimite email de bun venit" + SEND_WELCOME_EMAIL_HELP: "Trimite un email către utilizatorul nou înregistrat." + DEFAULT_VALUES: " Valori implicite" + DEFAULT_VALUES_HELP: "Listă de câmpuri și valorile asociate acestora ce vor fi adăugate profilului utilizatorului (în fișierul yaml) în mod implicit fără a putea fi configurabile de către utilizator. Separați valorile multiple cu virgulă, fără spații între valori." + ADDITIONAL_PARAM_KEY: "Parametru" + ADDITIONAL_PARAM_VALUE: "Valoare" + REGISTRATION_FIELDS: "Câmpuri pentru înregistrare" + REGISTRATION_FIELDS_HELP: "Adaugă câmpurile ce vor fi adăugate fișierului yaml al utilizatorului. Câmpurile care nu sunt listate aici nu vor fi adăugate chiar dacă sunt prezente în formularul de înregistrare." + REGISTRATION_FIELD_KEY: " Numele câmpului" + REDIRECT_AFTER_LOGIN: "Redirecționează după logare" + REDIRECT_AFTER_LOGIN_HELP: "Ruta personalizată pentru redirecționare după logare" + REDIRECT_AFTER_REGISTRATION: "Redirecționează după înregistrare" + REDIRECT_AFTER_REGISTRATION_HELP: "Ruta personalizată pentru redirecționare după înregistrare" + OPTIONS: "Opțiuni" + EMAIL_VALIDATION_MESSAGE: "Trebuie să fie o adresă de email validă" + PASSWORD_VALIDATION_MESSAGE: "Parola trebuie să conțină cel puțin un număr și o literă de tipar și să aibă cel puțin 8 caractere" + TIMEOUT_HELP: "Setează pauza pentru sesiune când este activat 'Ține-mă minte' de către utilizator. Minimul este de 604800 care înseamnă 1 săptămână." + GROUPS_HELP: "Lista grupurilor din care utilizatorii nou înregistrați vor face parte, dacă este necesar" + SITE_ACCESS_HELP: "Lista cu niveluri de acces la care utilizatorul nou înregistrat are acces. De eg: `login` -> `true` " + WELCOME: "Bine ați venit" + REDIRECT_AFTER_ACTIVATION: "Redirecționează după activarea utilizatorului" + REDIRECT_AFTER_ACTIVATION_HELP: "Folosită dacă utilizatorul trebuie să-și activeze contul prin email. Odată activat contul va fi folosită această rută." + REGISTRATION_DISABLED: " Dezactivează înregistrarea " + USE_PARENT_ACL_LABEL: "Folosește regulile de acces ale părintelui" + USE_PARENT_ACL_HELP: "Verifică regulie de acces ale părintelui dacă nu sunt specificate alte reguli de acces" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: " Protejează media ce aparține paginii de logare " + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Dacă este activată, media ce aparține unei pagini de logare este protejată și nu poate fi accesată decât după logare." \ No newline at end of file diff --git a/languages/ru.yaml b/languages/ru.yaml new file mode 100644 index 0000000..06781c5 --- /dev/null +++ b/languages/ru.yaml @@ -0,0 +1,108 @@ +PLUGIN_LOGIN: + USERNAME: "Логин" + EMAIL: "Email" + USERNAME_EMAIL: "Логин/Email" + PASSWORD: "Пароль" + ACCESS_DENIED: "Доступ закрыт..." + LOGIN_FAILED: "Ошибка входа..." + LOGIN_SUCCESSFUL: "Вы успешно вошли в систему." + BTN_LOGIN: "Войти" + BTN_LOGOUT: "Выйти" + BTN_FORGOT: "Забыл" + BTN_REGISTER: "Регистрация" + BTN_RESET: "Сброс пароля" + BTN_SEND_INSTRUCTIONS: "Отправить инструкции по сбросу" + RESET_LINK_EXPIRED: "Время ссылки для сброса истекло, повторите попытку" + RESET_PASSWORD_RESET: "Пароль был сброшен" + RESET_INVALID_LINK: "Неверная ссылка сброса, повторите попытку" + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Инструкции по сбросу пароля были отправлены по электронной почте" + FORGOT_FAILED_TO_EMAIL: "Не удалось отправить инструкции по электронной почте, повторите попытку позже" + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Не удается сбросить пароль для %s, адресс электронной почты не установлен" + FORGOT_USERNAME_DOES_NOT_EXIST: "Пользователь с именем %s не существует" + FORGOT_EMAIL_NOT_CONFIGURED: "Невозможно сбросить пароль. Этот сайт не настроен для отправки писем" + FORGOT_EMAIL_SUBJECT: "%s Запрос на сброс пароля" + FORGOT_EMAIL_BODY: "

Восстановление пароля

Уважаемый %1$s,

Был сделан запрос для сброса пароля от %4$s.


Нажмите, чтобы сбросить пароль

Или скопируйте следующий URL-адрес в адресную строку браузера:

%2$s


С уважением,

%3$s

" + SESSION: "“Запомнить меня”-Сессия" + REMEMBER_ME: "Запомнить меня" + REMEMBER_ME_HELP: "Устанавливает постоянный файл cookie в вашем браузере, чтобы разрешить постоянную аутентификацию входа между сеансами." + REMEMBER_ME_STOLEN_COOKIE: "Кто-то еще использовал вашу регистрационную информацию для доступа к этой странице! Все сеансы были отключены. Войдите в свою учетную запись и проверьте свои данные." + BUILTIN_CSS: "Использовать встроенный CSS" + BUILTIN_CSS_HELP: "Использовать CSS, предоставленный плагином администратора." + ROUTE: "Путь страницы входа" + ROUTE_HELP: "Путь к пользовательской странице входа, которую предоставляет ваша тема" + ROUTE_REGISTER: "Путь регистрации" + ROUTE_REGISTER_HELP: "Путь к пользовательской странице регистрации. Заполните, если вы хотите использовать встроенную страницу регистрации. Оставьте его пустым, если у вас есть собственная регистрационная форма" + USERNAME_NOT_VALID: "Имя пользователя должно быть от 3 до 16 символов, включая строчные буквы, цифры, символы подчеркивания и дефисы. Прописные буквы, пробелы и специальные символы не допускаются" + USERNAME_NOT_AVAILABLE: "Имя пользователя %s уже существует, выберите другое имя пользователя" + PASSWORD_NOT_VALID: "Пароль должен содержать как минимум одно число, одну прописную и строчную букву, и быть не менее 8 символов" + PASSWORDS_DO_NOT_MATCH: "Пароли не совпадают. Дважды проверьте, что вы дважды ввели тот же пароль" + USER_NEEDS_EMAIL_FIELD: "Пользователю требуется поле электронной почты" + EMAIL_SENDING_FAILURE: "Произошла ошибка при отправке письма" + ACTIVATION_EMAIL_SUBJECT: "Активируйте свою учетную запись %s" + ACTIVATION_EMAIL_BODY: "Привет %s, перейдите сюда для активации вашей учетной записи %s" + WELCOME_EMAIL_SUBJECT: "Добро пожаловать в %s" + WELCOME_EMAIL_BODY: "Привет %s, добро пожаловать в %s!" + NOTIFICATION_EMAIL_SUBJECT: "Новый пользователь %s" + NOTIFICATION_EMAIL_BODY: "Привет, новый пользователь, зарегистрированный на %s. Имя пользователя: %s, email: %s" + EMAIL_FOOTER: "GetGrav.org" + ACTIVATION_LINK_EXPIRED: "Время ссылки для активации истекло" + USER_ACTIVATED_SUCCESSFULLY: "Пользователь успешно активирован" + INVALID_REQUEST: "Неверный запрос" + USER_REGISTRATION: "Регистрация пользователя" + USER_REGISTRATION_ENABLED_HELP: "Включить регистрацию пользователя" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Двойная проверка введенного пароля" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Подтвердить и сравнить два разных поля для паролей с именами `password1` и` password2`. Включите это, если у вас есть два поля пароля в регистрационной форме" + SET_USER_DISABLED: "Установить пользователя как отключенный" + SET_USER_DISABLED_HELP: "Лучше всего использовать электронную почту «Отправить электронную почту активации». Добавляет пользователя в Grav, но устанавливает его как отключенный" + LOGIN_AFTER_REGISTRATION: "Воход в систему после регистрации" + LOGIN_AFTER_REGISTRATION_HELP: "Автоматический воход в систему после регистрации. Если требуется активация электронной почты, пользователь будет входить в систему сразу после активации учетной записи" + SEND_ACTIVATION_EMAIL: "Отправить письмо активации" + SEND_ACTIVATION_EMAIL_HELP: "Отправляет электронное письмо пользователю для активации своей учетной записи. Включите параметр «Установить пользователя как отключенный» при использовании этой функции, чтобы пользователь был отключен, и для активации учетной записи будет отправлено электронное письмо" + SEND_NOTIFICATION_EMAIL: "Отправить уведомление по электронной почте" + SEND_NOTIFICATION_EMAIL_HELP: "Сообщает администратору сайта о регистрации нового пользователя. Электронная почта будет отправлена в поле «Кому» в конфигурации плагина электронной почты" + SEND_WELCOME_EMAIL: "Отправить приветственное письмо" + SEND_WELCOME_EMAIL_HELP: "Отправляет электронное письмо вновь зарегистрированному пользователю" + DEFAULT_VALUES: "Значения по умолчанию" + DEFAULT_VALUES_HELP: "Список названий полей и связанных значений, которые будут добавлены в профиль пользователя (файл yaml) по умолчанию, без настройки пользователем. Разделите несколько значений запятой, без пробелов между значениями" + ADDITIONAL_PARAM_KEY: "Параметр" + ADDITIONAL_PARAM_VALUE: "Значение" + REGISTRATION_FIELDS: "Регистрационные поля" + REGISTRATION_FIELDS_HELP: "Добавьте поля, которые будут добавлены в файл yaml пользователя. Поля, не перечисленные здесь, не будут добавлены, даже если они присутствуют в регистрационной форме" + REGISTRATION_FIELD_KEY: "Имя поля" + REDIRECT_AFTER_LOGIN: "Перенаправление после входа в систему" + REDIRECT_AFTER_LOGIN_HELP: "Пользовательский маршрут для перенаправления после входа в систему" + REDIRECT_AFTER_REGISTRATION: "Перенаправление после регистрации" + REDIRECT_AFTER_REGISTRATION_HELP: "Пользовательский маршрут для перенаправления после регистрации" + OPTIONS: "Опции" + EMAIL_VALIDATION_MESSAGE: "Адрес эл. почты должен быть действительным" + PASSWORD_VALIDATION_MESSAGE: "Пароль должен содержать как минимум одно число, одну прописную и строчную букву и быть не менее 8 символов" + TIMEOUT_HELP: "Устанавливает тайм-аут сеанса в секундах, когда функция «Запомнить меня» включена и установлена пользователем. Минимум 604800, что означает 1 неделю" + GROUPS_HELP: "Список групп, в которые войдет новый зарегистрированный пользователь" + SITE_ACCESS_HELP: "Список уровней доступа к сайту, зарегистрированных пользователей. Пример: `login` ->` true`" + WELCOME: "Добро пожаловать" + REDIRECT_AFTER_ACTIVATION: "Перенаправление после активации пользователя" + REDIRECT_AFTER_ACTIVATION_HELP: "Используется, если пользователю требуется активировать учетную запись по электронной почте. После активации этот маршрут будет показан" + REGISTRATION_DISABLED: "Регистрация отключена" + USE_PARENT_ACL_LABEL: "Использовать родительские правила доступа" + USE_PARENT_ACL_HELP: "Проверьте правила доступа к родителям, если правила не определены" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Защита защищенных страниц." + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Если этот параметр включен, то доступ к защищенной странице для входа в систему также защищен паролем, и его нельзя увидеть, если он не зарегистрирован" + SECURITY_TAB: "Безопасность" + MAX_RESETS_COUNT: "Максимальное количество сброса пароля" + MAX_RESETS_COUNT_HELP: "Настройка защиты пароля от флуда (0 - не ограничено)" + MAX_RESETS_INTERVAL: "Максимальный интервал сброса пароля" + MAX_RESETS_INTERVAL_HELP: "Интервал времени для максимального количества сбросов пароля" + FORGOT_CANNOT_RESET_IT_IS_BLOCKED: "Невозможно сбросить пароль для %s, функция сброса пароля временно отключена, попробуйте позже (максимум %s минут)" + MAX_LOGINS_COUNT: "Максимальное количество входов" + MAX_LOGINS_COUNT_HELP: "Настройка защиты от флуда (0 - не ограничено)" + MAX_LOGINS_INTERVAL: "Максимальный интервал входа" + MAX_LOGINS_INTERVAL_HELP: "Временной интервал для значения счетчика входа" + TOO_MANY_LOGIN_ATTEMPTS: "Слишком много неудачных попыток входа в настроенное время (%s минут)" + SECONDS: "секунд" + RESETS: "сбросов" + ATTEMPTS: "попыток" + ROUTES: "Маршруты" + ROUTE_FORGOT: "Забыли пароль" + ROUTE_RESET: "Сброса пароля" + ROUTE_PROFILE: "Профиля пользователя" + ROUTE_ACTIVATE: "Активации пользователя" \ No newline at end of file diff --git a/languages/test.html b/languages/test.html new file mode 100644 index 0000000..a772939 --- /dev/null +++ b/languages/test.html @@ -0,0 +1,19 @@ + + + + + Title + + +

Password Reset

+

Dear %1$s,

+

A request was made on %4$s to reset your password.

+


Click this to reset your password

+

Alternatively, copy the following URL into your browser's address bar:

+

%2$s

+


Kind regards,

%3$s

+ + + + +

Account Activation

Hi %1$s,

your account has been created, but you cannot login until it is activated.


Activate Your Account Now

Alternatively, copy the following URL into your browser's address bar:

%2$s


Kind regards,

%3$s

\ No newline at end of file From 27aed347c8f167f27d8ddda01a869523ab12ca64 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 30 Nov 2017 18:41:40 -0700 Subject: [PATCH 14/19] Email usability and formatting improvements --- CHANGELOG.md | 1 + classes/Login.php | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32c70c4..66a86f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Improved rate limiter to work without sessions and against distributed attacks * Removed `partials/messages.html.twig` and rely on new core version * Moved languages from unified file into dedicated language file structure + * Welcome / Notice / Activation emails now more flushed out and in HTML like Reset Password 1. [](#bugfix) * Do not send nonce with activation link, email app can open the link in another browser diff --git a/classes/Login.php b/classes/Login.php index 629dc5f..729bf05 100755 --- a/classes/Login.php +++ b/classes/Login.php @@ -228,7 +228,8 @@ public function sendNotificationEmail(User $user) 'PLUGIN_LOGIN.NOTIFICATION_EMAIL_BODY', $site_name, $user->username, - $user->email + $user->email, + $this->grav['base_url_absolute'], ]); $to = $this->config->get('plugins.email.from'); @@ -260,9 +261,16 @@ public function sendWelcomeEmail(User $user) } $site_name = $this->config->get('site.title', 'Website'); + $author = $this->grav['config']->get('site.author.name', ''); + $fullname = $user->fullname ?: $user->username; $subject = $this->language->translate(['PLUGIN_LOGIN.WELCOME_EMAIL_SUBJECT', $site_name]); - $content = $this->language->translate(['PLUGIN_LOGIN.WELCOME_EMAIL_BODY', $user->username, $site_name]); + $content = $this->language->translate(['PLUGIN_LOGIN.WELCOME_EMAIL_BODY', + $fullname, + $this->grav['base_url_absolute'], + $site_name, + $author + ]); $to = $user->email; $sent = EmailUtils::sendEmail($subject, $content, $to); @@ -297,16 +305,20 @@ public function sendActivationEmail(User $user) $activation_link = $this->grav['base_url_absolute'] . $this->config->get('plugins.login.route_activate') . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username; $site_name = $this->config->get('site.title', 'Website'); + $author = $this->grav['config']->get('site.author.name', ''); + $fullname = $user->fullname ?: $user->username; $subject = $this->language->translate(['PLUGIN_LOGIN.ACTIVATION_EMAIL_SUBJECT', $site_name]); - $content = $this->language->translate([ - 'PLUGIN_LOGIN.ACTIVATION_EMAIL_BODY', - $user->username, + $content = $this->language->translate(['PLUGIN_LOGIN.ACTIVATION_EMAIL_BODY', + $fullname, $activation_link, - $site_name + $site_name, + $author ]); $to = $user->email; + + $sent = EmailUtils::sendEmail($subject, $content, $to); if ($sent < 1) { @@ -483,4 +495,5 @@ public function getUser() /** @var User $user */ return $this->grav['user']; } + } From 24f18488be78d29394b99ef6261be7c603c0427c Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 30 Nov 2017 19:05:56 -0700 Subject: [PATCH 15/19] Updated README and default configuration comments --- README.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++------- login.yaml | 76 ++++++++++++++++++++++++------------------------ 2 files changed, 112 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index e8ad0f6..21a0150 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,28 @@ They use following events which can be hooked by plugins: * `onUserLoginRegisterData` Allows plugins to include their own data to be added to the user object during registration. * `onUserLoginRegistered` Allows plugins to hook into user registration just before the redirect. +New Plugin options have been added for: + +* `dynamic_page_visibility` - Integrate access into page visibility so things can be shown or hidden in the menu + # Changes in version 2.0 -The Login Plugin 2.0 has the following changes compared to 1.0: +* OAuth has been separated to its own plugin, needs to be installed separately and configured. The users account filename format has changed too, to fix an issue that involved people with the same name on a service. +* The `redirect` option has been changed to `redirect_after_login`. +* The Remember Me session minimum length is now 1 week. +* Removed the option to login from oauth without creating the corresponding user file under `user/accounts/`. + +# Messages Output -- OAuth has been separated to its own plugin, needs to be installed separately and configured. The users account filename format has changed too, to fix an issue that involved people with the same name on a service. -- The `redirect` option has been changed to `redirect_after_login`. -- The Remember Me session minimum length is now 1 week. -- Removed the option to login from oauth without creating the corresponding user file under `user/accounts/`. +There is not a guaranteed way to display system messages including those added by the Login plugin, so in order to see messages you will need to make sure your theme has a method to output the messages. This is done by adding a simple Twig include, and the best place to do this to ensure it's visible in all your pages, is to add it to the `partials/base.html.twig` (or whatever your base Twig template is called): + +```twig + {% block messages %} + {% include 'partials/messages.html.twig' ignore missing %} + {% endblock %} +``` + +A good location is probably to add this right above where your content is going to be output. # Creating Users @@ -111,6 +125,36 @@ access: >> Note: the username is based on the name of the YAML file. +# Default Configuration + +```yaml +enabled: true # Enable the plugin +built_in_css: true # Use built-in CSS +route: # Specific route for Login page (default is '/login') +redirect_to_login: true # If you try to access a page you don't have access to, should you redirect to login route +redirect_after_login: # Path to redirect to after a successful login (eg '/user_profile') +route_activate: '/activate_user' # Route for the user activation process +route_forgot: '/forgot_password' # Route for the forgot password process +route_reset: '/reset_password' # Route for the reset password process +route_profile: '/user_profile' # Route for the user profile page +route_register: '/user_register' # Route for the user registration page +route_unauthorized: '/user_unauthorized' # Route for a page to display if user is unauthorized + +dynamic_page_visibility: false # Integrate access into page visibility so things can be shown or hidden in the menu +parent_acl: false # Look to parent `access` rules for access requirements +protect_protected_page_media: false # Take `access` rules into account when directly accessing a page's media + +rememberme: + enabled: true # Enable 'remember me' functionality + timeout: 604800 # Timeout in seconds. Defaults to 1 week + name: grav-rememberme # Name prefix of the session cookie + +max_pw_resets_count: 0 # Number of password resets in a specific time frame (0 = unlimited) +max_pw_resets_interval: 60 # Time in minutes to track password resets +max_login_count: 0 # Number of failed login attempts in a specific time frame (0 = unlimited) +max_login_interval: 2 # Time in minutes to track login attempts +``` + # Usage You can add ACL to any page by typing something like below into the page header: @@ -292,13 +336,32 @@ There are several options that can be configured when registering users via `use ``` user_registration: + enabled: true # Enable User Registration Process + + fields: # List of fields to validate and store during user registration + - 'username' # This should match up with your registration form definition + - 'password' + - 'email' + - 'fullname' + - 'title' + - 'level' + + default_values: # Any default values for fields you would like to set + level: Newbie # Here the 'level' field will be pre-populated with 'Newbie' text + + access: # Default access to set for users created during registration + site: + login: 'true' + + redirect_after_registration: '' # Route to redirect to after registration + options: - validate_password1_and_password2: true - set_user_disabled: false - login_after_registration: true - send_activation_email: false - send_notification_email: false - send_welcome_email: false + validate_password1_and_password2: true # Ensure that password1 and password2 match during registration (allows you to have just 1 pw field or 2) + set_user_disabled: false # Set this `true` if you want a user to activate their account via email + login_after_registration: true # Automatically login after registration + send_activation_email: false # Send an email that requires a special link to be clicked in order to activate the account + send_notification_email: false # Send an email to the site administrator to indicate a user has registered + send_welcome_email: false # Send a welcome email to the user (probably should not be used with `send_activation_email` ``` ## Sending an activation email diff --git a/login.yaml b/login.yaml index 3b381e4..b035cbe 100644 --- a/login.yaml +++ b/login.yaml @@ -1,53 +1,53 @@ -enabled: true -built_in_css: true -route: -redirect_to_login: true -redirect_after_login: -route_activate: '/activate_user' -route_forgot: '/forgot_password' -route_reset: '/reset_password' -route_profile: '/user_profile' -route_register: '/user_register' -route_unauthorized: '/user_unauthorized' - -dynamic_page_visibility: false -parent_acl: false -protect_protected_page_media: false +enabled: true # Enable the plugin +built_in_css: true # Use built-in CSS +route: # Specific route for Login page (default is '/login') +redirect_to_login: true # If you try to access a page you don't have access to, should you redirect to login route +redirect_after_login: # Path to redirect to after a successful login (eg '/user_profile') +route_activate: '/activate_user' # Route for the user activation process +route_forgot: '/forgot_password' # Route for the forgot password process +route_reset: '/reset_password' # Route for the reset password process +route_profile: '/user_profile' # Route for the user profile page +route_register: '/user_register' # Route for the user registration page +route_unauthorized: '/user_unauthorized' # Route for a page to display if user is unauthorized + +dynamic_page_visibility: false # Integrate access into page visibility so things can be shown or hidden in the menu +parent_acl: false # Look to parent `access` rules for access requirements +protect_protected_page_media: false # Take `access` rules into account when directly accessing a page's media + +rememberme: + enabled: true # Enable 'remember me' functionality + timeout: 604800 # Timeout in seconds. Defaults to 1 week + name: grav-rememberme # Name prefix of the session cookie + +max_pw_resets_count: 0 # Number of password resets in a specific time frame (0 = unlimited) +max_pw_resets_interval: 60 # Time in minutes to track password resets +max_login_count: 0 # Number of failed login attempts in a specific time frame (0 = unlimited) +max_login_interval: 2 # Time in minutes to track login attempts user_registration: - enabled: true + enabled: true # Enable User Registration Process - fields: - - 'username' + fields: # List of fields to validate and store during user registration + - 'username' # This should match up with your registration form definition - 'password' - 'email' - 'fullname' - 'title' - 'level' - default_values: - level: Newbie + default_values: # Any default values for fields you would like to set + level: Newbie # Here the 'level' field will be pre-populated with 'Newbie' text - access: + access: # Default access to set for users created during registration site: login: 'true' - redirect_after_registration: '' + redirect_after_registration: '' # Route to redirect to after registration options: - validate_password1_and_password2: true - set_user_disabled: false - login_after_registration: true - send_activation_email: false - send_notification_email: false - send_welcome_email: false - -rememberme: - enabled: true - timeout: 604800 # Timeout in seconds. Defaults to 1 week - name: grav-rememberme # Name prefix of the session cookie - -max_pw_resets_count: 0 -max_pw_resets_interval: 60 -max_login_count: 0 -max_login_interval: 2 \ No newline at end of file + validate_password1_and_password2: true # Ensure that password1 and password2 match during registration (allows you to have just 1 pw field or 2) + set_user_disabled: false # Set this `true` if you want a user to activate their account via email + login_after_registration: true # Automatically login after registration + send_activation_email: false # Send an email that requires a special link to be clicked in order to activate the account + send_notification_email: false # Send an email to the site administrator to indicate a user has registered + send_welcome_email: false # Send a welcome email to the user (probably should not be used with `send_activation_email` From 02565bbd4476d6c85070acc3a66b9e7d248b9b3f Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 30 Nov 2017 19:35:27 -0700 Subject: [PATCH 16/19] Removed erroneous file --- languages/test.html | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 languages/test.html diff --git a/languages/test.html b/languages/test.html deleted file mode 100644 index a772939..0000000 --- a/languages/test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Title - - -

Password Reset

-

Dear %1$s,

-

A request was made on %4$s to reset your password.

-


Click this to reset your password

-

Alternatively, copy the following URL into your browser's address bar:

-

%2$s

-


Kind regards,

%3$s

- - - - -

Account Activation

Hi %1$s,

your account has been created, but you cannot login until it is activated.


Activate Your Account Now

Alternatively, copy the following URL into your browser's address bar:

%2$s


Kind regards,

%3$s

\ No newline at end of file From 781dfbdc41a660fbb40b3ee8234a44b7469b25ea Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Fri, 1 Dec 2017 16:27:36 -0700 Subject: [PATCH 17/19] lang for readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21a0150..aee0a5b 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,7 @@ Last important thing before the registration is correctly setup: make sure in th There are several options that can be configured when registering users via `user/plugins/login.yaml`, they are pretty self-explanatory: -``` +```yaml user_registration: enabled: true # Enable User Registration Process From 71e5482e7339e2e2d8e5081d05a8644289d33237 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Fri, 1 Dec 2017 16:28:18 -0700 Subject: [PATCH 18/19] Typo in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aee0a5b..bf3b5e1 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ Also, your theme needs to implement forms. Use Antimatter or another form-compat Add the following content to your registration form page: -``` +```yaml --- form: @@ -307,6 +307,7 @@ form: message: "Thanks for registering..." reset: true --- +``` # Registration of Users From 62bb28fcd8577111f234c71e72b8b2673d907494 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Tue, 5 Dec 2017 17:04:27 -0700 Subject: [PATCH 19/19] prepare for release --- CHANGELOG.md | 2 +- blueprints.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66a86f0..fbfb7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # v2.5.0 -## mm/dd/2017 +## 12/05/2017 1. [](#new) * Added `$grav['login']->login()` and `$grav['login']->logout()` functions with event hooks diff --git a/blueprints.yaml b/blueprints.yaml index b65304e..85c054b 100644 --- a/blueprints.yaml +++ b/blueprints.yaml @@ -1,5 +1,5 @@ name: Login -version: 2.4.3 +version: 2.5.0 description: Enables user authentication and login screen. icon: sign-in author: