mirror of
https://github.com/jbcr/core.git
synced 2026-03-29 20:32:19 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -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
|
||||
> № {{ 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
|
||||
> № {{ record.id }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
48
assets/scss/layout/_reset-password.scss
Normal file
48
assets/scss/layout/_reset-password.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
@import '_admin';
|
||||
@import '_login';
|
||||
@import '_reset-password';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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],
|
||||
];
|
||||
|
||||
2
config/packages/reset_password.yaml
Normal file
2
config/packages/reset_password.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
symfonycasts_reset_password:
|
||||
request_password_repository: Bolt\Repository\ResetPasswordRequestRepository
|
||||
@@ -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: .
|
||||
|
||||
191
src/Controller/ResetPasswordController.php
Normal file
191
src/Controller/ResetPasswordController.php
Normal 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');
|
||||
}
|
||||
}
|
||||
47
src/Entity/ResetPasswordRequest.php
Normal file
47
src/Entity/ResetPasswordRequest.php
Normal 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;
|
||||
}
|
||||
}
|
||||
51
src/Form/ChangePasswordFormType.php
Normal file
51
src/Form/ChangePasswordFormType.php
Normal 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([]);
|
||||
}
|
||||
}
|
||||
32
src/Form/ResetPasswordRequestFormType.php
Normal file
32
src/Form/ResetPasswordRequestFormType.php
Normal 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([]);
|
||||
}
|
||||
}
|
||||
33
src/Repository/ResetPasswordRequestRepository.php
Normal file
33
src/Repository/ResetPasswordRequestRepository.php
Normal 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);
|
||||
}
|
||||
}
|
||||
12
symfony.lock
12
symfony.lock
@@ -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"
|
||||
},
|
||||
|
||||
41
templates/reset_password/check_email.html.twig
Normal file
41
templates/reset_password/check_email.html.twig
Normal 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 %}
|
||||
9
templates/reset_password/email.html.twig
Normal file
9
templates/reset_password/email.html.twig
Normal 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>
|
||||
54
templates/reset_password/request.html.twig
Normal file
54
templates/reset_password/request.html.twig
Normal 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 %}
|
||||
43
templates/reset_password/reset.html.twig
Normal file
43
templates/reset_password/reset.html.twig
Normal 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 %}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user