Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Kevin Pfeifer
2020-11-17 17:53:35 +01:00
22 changed files with 784 additions and 21 deletions

View File

@@ -1,18 +1,20 @@
<template>
<div class="listing__row--item is-meta">
<ul class="listing__row--list text-nowrap">
<li>
<span class="status" :class="`is-${record.status}`" :title="record.status"></span
>{{ record.publishedAt ? record.publishedAt : record.createdAt | date }}
<ul class="listing__row--list">
<li class="text-nowrap">
<span class="status" :class="`is-${record.status}`" :title="record.status"></span>
{{ record.publishedAt ? record.publishedAt : record.createdAt | date }}
</li>
<li v-if="size === 'normal'"><i class="fas fa-user"></i> {{ record.authorName }}</li>
<li v-if="size === 'normal'">
<i class="fas" :class="record.extras.icon"></i>
<template v-if="type === 'dashboard'"
><a :href="`/bolt/content/${record.contentType}`"> {{ record.extras.singular_name }}</a>
</template>
<template v-else>{{ record.extras.singular_name }}</template
>&nbsp; {{ record.id }}
<div>
<template v-if="type === 'dashboard'">
<a :href="`/bolt/content/${record.contentType}`">{{ record.extras.singular_name }}</a>
</template>
<template v-else>{{ record.extras.singular_name }}</template
>&nbsp; {{ record.id }}
</div>
</li>
</ul>
</div>

View File

@@ -0,0 +1,48 @@
.page--reset-password,
.page--reset-password--success,
.page--reset-password--reset {
background: linear-gradient(rgba(200, 200, 200, 0.6), rgba(200, 200, 200, 0.2));
> div {
max-width: 25rem;
margin: 0 auto;
padding: 2rem 1rem;
@include media-breakpoint-up(sm) {
padding-top: 4rem;
}
@include media-breakpoint-up(md) {
padding-top: 5rem;
}
@include media-breakpoint-up(lg) {
padding-top: 7rem;
}
}
.card {
box-shadow: 0 7.5px 15px -5px rgba(0, 0, 0, 0.4);
}
.reset-password__logo {
padding: 0 2rem;
img {
max-width: 100%;
display: block;
margin-bottom: 2rem;
}
}
.reset-password__description {
margin-bottom: 1rem;
}
&.with-background {
// Unsplash's daily picture is usually always pretty, but often too distracting: https://source.unsplash.com/daily
background: linear-gradient(rgba(128, 128, 128, 0.6), rgba(128, 128, 128, 0.2)), url(https://source.unsplash.com/1920x1080/?abstract);
background-position: center;
background-size: cover;
}
}

View File

@@ -2,3 +2,4 @@
@import '_admin';
@import '_login';
@import '_reset-password';

View File

@@ -182,6 +182,7 @@ $checkbox-row-width: 32px;
@include media-breakpoint-up(sm) {
flex: 1 0 100%;
margin-top: 1rem;
}
@include media-breakpoint-up(md) {
@@ -189,6 +190,7 @@ $checkbox-row-width: 32px;
max-width: 130px;
order: 3;
padding: $spacer*0.5;
margin-top: 0;
}
}
@@ -231,12 +233,16 @@ $checkbox-row-width: 32px;
}
li {
//display: flex;
padding-right: 1rem;
//align-items: center;
display: flex;
align-items: center;
margin-bottom: $spacer / 4;
color: var(--shade);
text-transform: capitalize;
margin-right: 1rem;
@include media-breakpoint-up(md) {
margin-right: 0;
}
> :first-child,
.status,

View File

@@ -64,6 +64,7 @@
"symfony/web-server-bundle": "^4.4",
"symfony/webpack-encore-bundle": "^1.7",
"symfony/yaml": "^5.1",
"symfonycasts/reset-password-bundle": "^1.1",
"tightenco/collect": "^7.25",
"twig/twig": "^3.0",
"ua-parser/uap-php": "^3.9",

View File

@@ -199,3 +199,11 @@ user_avatar:
upload_path: avatars
extensions_allowed: ['png', 'jpeg', 'jpg', 'gif']
default_avatar: '' # Put path of file locate in files directory
# Settings for the reset password logic
reset_password_settings:
show_already_requested_password_notice: true
mail_from: "do-not-reply@example.org"
mail_name: "Bolt CMS"
mail_subject: "Your password reset request"
mail_template: "reset_password/email.html.twig"

View File

@@ -25,4 +25,5 @@ return [
Symplify\ConsoleColorDiff\ConsoleColorDiffBundle::class => ['dev' => true, 'test' => true],
Translation\Bundle\TranslationBundle::class => ['all' => true],
Translation\PlatformAdapter\Loco\Bridge\Symfony\TranslationAdapterLocoBundle::class => ['dev' => true, 'local' => true],
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
];

View File

@@ -0,0 +1,2 @@
symfonycasts_reset_password:
request_password_repository: Bolt\Repository\ResetPasswordRequestRepository

View File

@@ -19,6 +19,11 @@ controllers:
resource: '../../src/Controller/ImageController.php'
type: annotation
# Reset Password Form
reset_password:
resource: '../../src/Controller/ResetPasswordController.php'
type: annotation
# Routes added by Extensions get added here, by Bolt\Extension\RoutesLoader
extensions:
resource: .

View File

@@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
namespace Bolt\Controller;
use Bolt\Configuration\Config;
use Bolt\Entity\User;
use Bolt\Form\ChangePasswordFormType;
use Bolt\Form\ResetPasswordRequestFormType;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
/**
* @Route("/reset-password")
*/
class ResetPasswordController extends AbstractController
{
use ResetPasswordControllerTrait;
private $resetPasswordHelper;
/** @var Config */
private $config;
public function __construct(ResetPasswordHelperInterface $resetPasswordHelper, Config $config)
{
$this->resetPasswordHelper = $resetPasswordHelper;
$this->config = $config;
}
/**
* Display & process form to request a password reset.
*
* @Route("", name="bolt_forgot_password_request")
*/
public function request(Request $request, MailerInterface $mailer): Response
{
$form = $this->createForm(ResetPasswordRequestFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
return $this->processSendingPasswordResetEmail(
$form->get('email')->getData(),
$mailer
);
}
return $this->render('reset_password/request.html.twig', [
'requestForm' => $form->createView(),
]);
}
/**
* Confirmation page after a user has requested a password reset.
*
* @Route("/check-email", name="bolt_check_email")
*/
public function checkEmail(): Response
{
// We prevent users from directly accessing this page
if (! $this->canCheckEmail()) {
return $this->redirectToRoute('bolt_forgot_password_request');
}
return $this->render('reset_password/check_email.html.twig', [
'tokenLifetime' => $this->resetPasswordHelper->getTokenLifetime(),
]);
}
/**
* Validates and process the reset URL that the user clicked in their email.
*
* @Route("/reset/{token}", name="bolt_reset_password")
*/
public function reset(Request $request, UserPasswordEncoderInterface $passwordEncoder, ?string $token = null): Response
{
if ($token) {
// We store the token in session and remove it from the URL, to avoid the URL being
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
$this->storeTokenInSession($token);
return $this->redirectToRoute('bolt_reset_password');
}
$token = $this->getTokenFromSession();
if ($token === null) {
throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
}
try {
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
} catch (ResetPasswordExceptionInterface $e) {
$this->addFlash('reset_password_error', sprintf(
'There was a problem validating your reset request - %s',
$e->getReason()
));
return $this->redirectToRoute('bolt_forgot_password_request');
}
// The token is valid; allow the user to change their password.
$form = $this->createForm(ChangePasswordFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// A password reset token should be used only once, remove it.
$this->resetPasswordHelper->removeResetRequest($token);
// Encode the plain password, and set it.
$encodedPassword = $passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
);
$user->setPassword($encodedPassword);
$this->getDoctrine()->getManager()->flush();
// The session is cleaned up after the password has been changed.
$this->cleanSessionAfterReset();
// Added an additional flash message to show if password reset was successful
$this->addFlash('reset_password_success', 'Your password has been reset successfully');
return $this->redirectToRoute('bolt_login');
}
return $this->render('reset_password/reset.html.twig', [
'resetForm' => $form->createView(),
]);
}
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer): RedirectResponse
{
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy([
'email' => $emailFormData,
]);
// Marks that you are allowed to see the bolt_check_email page.
$this->setCanCheckEmailInSession();
// Do not reveal whether a user account was found or not.
if (! $user) {
return $this->redirectToRoute('bolt_check_email');
}
// Global config/bolt/config.yml file.
$config = $this->config->get('general/reset_password_settings');
try {
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
} catch (ResetPasswordExceptionInterface $e) {
// If you want to tell the user why a reset email was not sent, uncomment
// the lines below and change the redirect to 'bolt_forgot_password_request'.
// Caution: This may reveal if a user is registered or not.
if ($config['show_already_requested_password_notice']) {
$this->addFlash('reset_password_error', sprintf(
'There was a problem handling your password reset request - %s',
$e->getReason()
));
}
return $this->redirectToRoute('bolt_check_email');
}
$email = (new TemplatedEmail())
->from(new Address($config['mail_from'], $config['mail_name']))
->to($user->getEmail())
->subject($config['mail_subject'])
->htmlTemplate($config['mail_template'])
->context([
'resetToken' => $resetToken,
'tokenLifetime' => $this->resetPasswordHelper->getTokenLifetime(),
]);
$mailer->send($email);
return $this->redirectToRoute('bolt_check_email');
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Bolt\Entity;
use Bolt\Repository\ResetPasswordRequestRepository;
use Doctrine\ORM\Mapping as ORM;
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestTrait;
/**
* @ORM\Entity(repositoryClass=ResetPasswordRequestRepository::class)
*/
class ResetPasswordRequest implements ResetPasswordRequestInterface
{
use ResetPasswordRequestTrait;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
*/
private $user;
public function __construct(object $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken)
{
$this->user = $user;
$this->initialize($expiresAt, $selector, $hashedToken);
}
public function getId(): ?int
{
return $this->id;
}
public function getUser(): object
{
return $this->user;
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Bolt\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class ChangePasswordFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => [
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
'label' => 'New password',
],
'second_options' => [
'label' => 'Repeat Password',
],
'invalid_message' => 'The password fields must match.',
// Instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([]);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Bolt\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class ResetPasswordRequestFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, [
'constraints' => [
new NotBlank([
'message' => 'Please enter your email',
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([]);
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Bolt\Repository;
use Bolt\Entity\ResetPasswordRequest;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
use SymfonyCasts\Bundle\ResetPassword\Persistence\Repository\ResetPasswordRequestRepositoryTrait;
use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepositoryInterface;
/**
* @method ResetPasswordRequest|null find($id, $lockMode = null, $lockVersion = null)
* @method ResetPasswordRequest|null findOneBy(array $criteria, array $orderBy = null)
* @method ResetPasswordRequest[] findAll()
* @method ResetPasswordRequest[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ResetPasswordRequestRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface
{
use ResetPasswordRequestRepositoryTrait;
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ResetPasswordRequest::class);
}
public function createResetPasswordRequest(object $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken): ResetPasswordRequestInterface
{
return new ResetPasswordRequest($user, $expiresAt, $selector, $hashedToken);
}
}

View File

@@ -1065,6 +1065,18 @@
"symfony/yaml": {
"version": "v4.4.4"
},
"symfonycasts/reset-password-bundle": {
"version": "1.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "1.0",
"ref": "97c1627c0384534997ae1047b93be517ca16de43"
},
"files": [
"config/packages/reset_password.yaml"
]
},
"symplify/auto-bind-parameter": {
"version": "v7.2.2"
},

View File

@@ -0,0 +1,41 @@
{% extends '@bolt/_base/layout_blank.html.twig' %}
{% block title %}
{{ 'reset_password.check_email_sent_header'|trans }}
{% endblock %}
{% block html_id %}{% endblock %}
{% block html_class %}{% endblock %}
{% block body_class %}page--reset-password--success{% if not config.get('general/omit_backgrounds') %} with-background{% endif %}{% endblock %}
{% block javascripts %}
{{ parent() }}
{% endblock %}
{% block main %}
<div>
<div class="reset-password__logo">
<img class="logo" alt="Bolt CMS logo"
{% if not config.get('general/omit_backgrounds') %}
src="{{ asset('assets/images/bolt_logo_loginpage--bg.svg') }}">
{% else %}
src="{{ asset('assets/images/bolt_logo_loginpage.svg') }}">
{% endif %}
</div>
<div class="card">
<div class="card-header">
<i class="fa fa-lock" aria-hidden="true"></i>
{{ 'reset_password.request_header'|trans }}
</div>
<div class="card-body">
<p>{{ 'reset_password.check_email_sent_text_1'|trans({'%hours%': tokenLifetime|date('g')}) }}</p>
<p>{{ 'reset_password.check_email_sent_text_2'|trans({'%tryagain%': '<a href="' ~ path('bolt_forgot_password_request') ~ '">try again</a>' }) | raw }}</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,9 @@
<h1>{{ 'reset_password.email_title'|trans }}</h1>
<p>{{ 'reset_password.email_description'|trans }}</p>
<a href="{{ url('bolt_reset_password', {token: resetToken.token}) }}">{{ url('bolt_reset_password', {token: resetToken.token}) }}</a>
<p>{{ 'reset_password.email_expire'|trans({'%hours%': tokenLifetime|date('g')}) }}</p>
<p>{{ 'reset_password.email_thanks'|trans }}</p>

View File

@@ -0,0 +1,54 @@
{% extends '@bolt/_base/layout_blank.html.twig' %}
{% block title %}
{{ 'reset_password.request_header'|trans }}
{% endblock %}
{% block html_id %}{% endblock %}
{% block html_class %}{% endblock %}
{% block body_class %}page--reset-password{% if not config.get('general/omit_backgrounds') %} with-background{% endif %}{% endblock %}
{% block javascripts %}
{{ parent() }}
{% endblock %}
{% block main %}
<div>
<div class="reset-password__logo">
<img class="logo" alt="Bolt CMS logo"
{% if not config.get('general/omit_backgrounds') %}
src="{{ asset('assets/images/bolt_logo_loginpage--bg.svg') }}">
{% else %}
src="{{ asset('assets/images/bolt_logo_loginpage.svg') }}">
{% endif %}
</div>
{% for flashError in app.flashes('reset_password_error') %}
<div class="alert alert-danger" role="alert">{{ flashError }}</div>
{% endfor %}
<div class="card">
<div class="card-header">
<i class="fa fa-lock" aria-hidden="true"></i>
{{ 'reset_password.request_header'|trans }}
</div>
<div class="card-body">
{{ form_start(requestForm, {'attr': {'class': 'reset-password__form'}}) }}
{{ form_row(requestForm.email) }}
<div class="reset-password__description">
{{ 'reset_password.request_description'|trans }}
</div>
<button class="btn btn-primary">{{ 'reset_password.request_send'|trans }}</button>
<a href="{{ path('bolt_login') }}" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i>
{{ 'reset_password.back-to-login'|trans }}
</a>
{{ form_end(requestForm) }}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends '@bolt/_base/layout_blank.html.twig' %}
{% block title %}
{{ 'reset_password.reset_header'|trans }}
{% endblock %}
{% block html_id %}{% endblock %}
{% block html_class %}{% endblock %}
{% block body_class %}page--reset-password--reset{% if not config.get('general/omit_backgrounds') %} with-background{% endif %}{% endblock %}
{% block javascripts %}
{{ parent() }}
{% endblock %}
{% block main %}
<div>
<div class="reset-password__logo">
<img class="logo" alt="Bolt CMS logo"
{% if not config.get('general/omit_backgrounds') %}
src="{{ asset('assets/images/bolt_logo_loginpage--bg.svg') }}">
{% else %}
src="{{ asset('assets/images/bolt_logo_loginpage.svg') }}">
{% endif %}
</div>
<div class="card">
<div class="card-header">
<i class="fa fa-lock" aria-hidden="true"></i>
{{ 'reset_password.reset_header'|trans }}
</div>
<div class="card-body">
{{ form_start(resetForm) }}
{{ form_row(resetForm.plainPassword) }}
<button class="float-right btn btn-tertiary">{{ 'reset_password.reset_btn'|trans }}</button>
{{ form_end(resetForm) }}
</div>
</div>
</div>
{% endblock %}

View File

@@ -22,13 +22,17 @@
<div class="login__logo">
<img class="logo" alt="Bolt CMS logo"
{% if not config.get('general/omit_backgrounds') %}
src="{{ asset('assets/images/bolt_logo_loginpage--bg.svg', 'public') }}">
src="{{ asset('assets/images/bolt_logo_loginpage--bg.svg', 'bolt') }}">
{% else %}
src="{{ asset('assets/images/bolt_logo_loginpage.svg', 'public') }}">
src="{{ asset('assets/images/bolt_logo_loginpage.svg', 'bolt') }}">
{% endif %}
<h1>{{ config.get('general/sitename') }}</h1>
</div>
{% for flashError in app.flashes('reset_password_success') %}
<div class="alert alert-success" role="alert">{{ flashError }}</div>
{% endfor %}
<div class="card login__login-form">
<div class="card-header">
@@ -53,6 +57,10 @@
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
{{- 'action.log_in'|trans -}}
</button>
<a href="{{ path('bolt_forgot_password_request') }}" class="btn btn-primary">
<i class="fas fa-question-circle"></i>
{{ 'login.forgotpassword'|trans }}
</a>
{{ form_end(loginForm) }}
</div>

View File

@@ -2451,5 +2451,95 @@
<target>Avatar</target>
</segment>
</unit>
<unit id="i37ZNqN" name="login.forgotpassword">
<segment>
<source>login.forgotpassword</source>
<target>Forgot Password</target>
</segment>
</unit>
<unit id="y.dIywE" name="reset_password.request_header">
<segment>
<source>reset_password.request_header</source>
<target>Reset Password</target>
</segment>
</unit>
<unit id="SwOgM7K" name="reset_password.request_description">
<segment>
<source>reset_password.request_description</source>
<target>Enter your email address and we we will send you a link to reset your password.</target>
</segment>
</unit>
<unit id="RX0Iuzi" name="reset_password.request_send">
<segment>
<source>reset_password.request_send</source>
<target>Submit</target>
</segment>
</unit>
<unit id="lpzL089" name="Email">
<segment>
<source>Email</source>
<target>Email</target>
</segment>
</unit>
<unit id="k9hekeu" name="reset_password.back-to-login">
<segment>
<source>reset_password.back-to-login</source>
<target>Back to Login</target>
</segment>
</unit>
<unit id="gVc0Uf4" name="reset_password.reset_header">
<segment>
<source>reset_password.reset_header</source>
<target>Reset your password</target>
</segment>
</unit>
<unit id="U6qdalg" name="reset_password.check_email_sent_header">
<segment>
<source>reset_password.check_email_sent_header</source>
<target>Password Reset Email Sent</target>
</segment>
</unit>
<unit id="SxW1D3L" name="reset_password.check_email_sent_text_1">
<segment>
<source>reset_password.check_email_sent_text_1</source>
<target>An email has been sent that contains a link that you can click to reset your password. This link will expire in %hours% hour(s).</target>
</segment>
</unit>
<unit id="z25k4DS" name="reset_password.check_email_sent_text_2">
<segment>
<source>reset_password.check_email_sent_text_2</source>
<target>If you don't receive an email please check your spam folder or %tryagain%.</target>
</segment>
</unit>
<unit id="QpUMHfK" name="reset_password.reset_btn">
<segment>
<source>reset_password.reset_btn</source>
<target>Reset Password</target>
</segment>
</unit>
<unit id="3prMgfK" name="reset_password.email_title">
<segment>
<source>reset_password.email_title</source>
<target>Hi!</target>
</segment>
</unit>
<unit id="oaRkfvE" name="reset_password.email_description">
<segment>
<source>reset_password.email_description</source>
<target>To reset your password, please visit the following link</target>
</segment>
</unit>
<unit id="Afb4dal" name="reset_password.email_expire">
<segment>
<source>reset_password.email_expire</source>
<target>This link will expire in %hours% hour(s).</target>
</segment>
</unit>
<unit id="9ga3FlV" name="reset_password.email_thanks">
<segment>
<source>reset_password.email_thanks</source>
<target>Cheers!</target>
</segment>
</unit>
</file>
</xliff>

View File

@@ -562,7 +562,7 @@
</segment>
</unit>
<unit id="kouGbMq" name="WoordPers: Kinda looks like that other CMS">
<segment state="translated" subState="poedit:fuzzy">
<segment>
<source>WoordPers: Kinda looks like that other CMS</source>
<target>WoordPers: Kinda looks like that other CMS</target>
</segment>
@@ -574,7 +574,7 @@
</segment>
</unit>
<unit id="xuNRuUu" name="caption.translations: messages">
<segment state="translated" subState="poedit:fuzzy">
<segment>
<source>caption.translations: messages</source>
<target>caption.translations: messages</target>
</segment>
@@ -592,7 +592,7 @@
</segment>
</unit>
<unit id="cIiIXu_" name="caption.routing set up">
<segment state="translated" subState="poedit:fuzzy">
<segment>
<source>caption.routing set up</source>
<target>caption.routing set up</target>
</segment>
@@ -976,7 +976,7 @@
</segment>
</unit>
<unit id="Wou02nK" name="caption.kitchensink">
<segment state="translated" subState="poedit:fuzzy">
<segment>
<source>caption.kitchensink</source>
<target>The Kitchensink</target>
</segment>
@@ -1732,7 +1732,7 @@
</segment>
</unit>
<unit id="4N41oTU" name="controller.omnisearch.subtitle">
<segment state="translated" subState="poedit:fuzzy">
<segment>
<source>controller.omnisearch.subtitle</source>
<target>To search, in an omni-like fashion</target>
</segment>
@@ -1978,7 +1978,7 @@
</segment>
</unit>
<unit id="x9qAwrz" name="listing.title_browser">
<segment state="translated" subState="poedit:fuzzy">
<segment>
<source>listing.title_browser</source>
<target>Браузер / платформа</target>
</segment>
@@ -2332,7 +2332,7 @@
</segment>
</unit>
<unit id="f.rvF6y" name="action.new">
<segment state="translated" subState="poedit:fuzzy">
<segment>
<source>action.new</source>
<target>Создать</target>
</segment>
@@ -2373,5 +2373,83 @@
<target>Выбрать …</target>
</segment>
</unit>
<unit id="2SSLP1K" name="form.empty_username_email">
<segment>
<source>form.empty_username_email</source>
<target>Пожалуйста, введите ваше имя пользователя или адрес электронной почты</target>
</segment>
</unit>
<unit id="5Qf_RiO" name="form.empty_password">
<segment>
<source>form.empty_password</source>
<target>Пожалуйста введите ваш пароль</target>
</segment>
</unit>
<unit id="fAmcKUQ" name="listing.title_filterby_field">
<segment>
<source>listing.title_filterby_field</source>
<target>Фильтр по полю</target>
</segment>
</unit>
<unit id="kciiidS" name="image.button_from_url">
<segment>
<source>image.button_from_url</source>
<target>По URL</target>
</segment>
</unit>
<unit id="QcN1pi8" name="files_cards.copy_to_clipboard">
<segment>
<source>files_cards.copy_to_clipboard</source>
<target>Скопировать ссылку на файл</target>
</segment>
</unit>
<unit id="S9k1S7Z" name="warning">
<segment>
<source>warning</source>
<target>Предупреждение</target>
</segment>
</unit>
<unit id="4fyuUU." name="filemanager.create_folder_already_exists">
<segment>
<source>filemanager.create_folder_already_exists</source>
<target>Папка уже существует</target>
</segment>
</unit>
<unit id="aSxePCB" name="filemanager.create_folder_error">
<segment>
<source>filemanager.create_folder_error</source>
<target>Не удалось создать папку</target>
</segment>
</unit>
<unit id="wbFKypA" name="filemanager.create_folder_success">
<segment>
<source>filemanager.create_folder_success</source>
<target>Папка успешно создана.</target>
</segment>
</unit>
<unit id="lZxOEzh" name="filemanager.delete_folder_successful">
<segment>
<source>filemanager.delete_folder_successful</source>
<target>Папка успешно удалена</target>
</segment>
</unit>
<unit id="a6Xhtq0" name="folder.create_new">
<segment>
<source>folder.create_new</source>
<target>Новая папка</target>
</segment>
</unit>
<unit id="FcXvWL8" name="title.add_user">
<segment>
<source>title.add_user</source>
<target>Добавить пользователя</target>
</segment>
</unit>
<unit id="nWMH_Nr" name="label.avatar">
<segment>
<source>label.avatar</source>
<target>Аватар</target>
</segment>
</unit>
</file>
</xliff>