Этот коммит содержится в:
Igor V Belousov 2019-06-22 16:03:39 +03:00
родитель c1951ca06a
Коммит 6d282daa30
21 изменённых файлов: 808 добавлений и 4 удалений

Просмотреть файл

@ -1,14 +1,27 @@
security: security:
encoders:
App\Entity\User:
algorithm: argon2i
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers: providers:
in_memory: { memory: ~ } # used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
firewalls: firewalls:
dev: dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
main: main:
anonymous: true anonymous: true
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
# activate different ways to authenticate # activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication # https://symfony.com/doc/current/security.html#firewalls-authentication
@ -18,5 +31,5 @@ security:
# Easy way to control access for large sections of your site # Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
# - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/admin/*, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER } # - { path: ^/profile, roles: ROLE_USER }

Просмотреть файл

@ -2,3 +2,4 @@ twig:
default_path: '%kernel.project_dir%/templates' default_path: '%kernel.project_dir%/templates'
debug: '%kernel.debug%' debug: '%kernel.debug%'
strict_variables: '%kernel.debug%' strict_variables: '%kernel.debug%'
form_themes: ['bootstrap_4_layout.html.twig']

17
src/Controller/IndexController.php Обычный файл
Просмотреть файл

@ -0,0 +1,17 @@
<?php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class IndexController extends AbstractController
{
/**
* @Route("/", name="index_page", methods={"GET"})
*/
public function index()
{
return $this->render('index.html.twig');
}
}

55
src/Controller/RegistrationController.php Обычный файл
Просмотреть файл

@ -0,0 +1,55 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\LoginFormAuthenticator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
class RegistrationController extends AbstractController
{
/**
* @Route("/register", name="app_register")
*/
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, LoginFormAuthenticator $authenticator): Response
{
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
)
);
$user->setRoles(['ROLE_USER'])->setActive(false);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
// do anything else you need here, like send an email
return $guardHandler->authenticateUserAndHandleSuccess(
$user,
$request,
$authenticator,
'main' // firewall name in security.yaml
);
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
}

33
src/Controller/SecurityController.php Обычный файл
Просмотреть файл

@ -0,0 +1,33 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
/**
* @Route("/logout", name="app_logout", methods={"GET"})
*/
public function logout()
{
// controller can be blank: it will never be executed!
throw new \Exception('Don\'t forget to activate logout in security.yaml');
}
}

101
src/Controller/UserController.php Обычный файл
Просмотреть файл

@ -0,0 +1,101 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\UserType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
/**
* @Route("/admin/user")
*/
class UserController extends AbstractController
{
/**
* @Route("/", name="user_index", methods={"GET"})
*/
public function index(UserRepository $userRepository): Response
{
return $this->render('user/index.html.twig', [
'users' => $userRepository->findAll(),
]);
}
/**
* @Route("/new", name="user_new", methods={"GET","POST"})
*/
public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
$user = new User();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$user->setPassword(
$passwordEncoder->encodePassword($user, $user->getPassword())
);
$entityManager->persist($user);
$entityManager->flush();
return $this->redirectToRoute('user_index');
}
return $this->render('user/new.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
/**
* @Route("/{id}", name="user_show", methods={"GET"})
*/
public function show(User $user): Response
{
return $this->render('user/show.html.twig', [
'user' => $user,
]);
}
/**
* @Route("/{id}/edit", name="user_edit", methods={"GET","POST"})
*/
public function edit(Request $request, User $user): Response
{
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('user_index', [
'id' => $user->getId(),
]);
}
return $this->render('user/edit.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
/**
* @Route("/{id}", name="user_delete", methods={"DELETE"})
*/
public function delete(Request $request, User $user): Response
{
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($user);
$entityManager->flush();
}
return $this->redirectToRoute('user_index');
}
}

136
src/Entity/User.php Обычный файл
Просмотреть файл

@ -0,0 +1,136 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
* @UniqueEntity(fields={"username"}, message="There is already an account with this username")
*/
class User implements UserInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, unique=true)
*/
private $username;
/**
* @ORM\Column(type="simple_array")
*/
private $roles = [];
/**
* @var string The hashed password
* @ORM\Column(type="string")
*/
private $password;
/**
* @var bool Пользователь активный или нет
* @ORM\Column(type="boolean", options={"default":false})
*/
private $active;
public function getId(): ?int
{
return (int) $this->id;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUsername(): string
{
return (string) $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* @return bool
*/
public function isActive(): bool
{
return (bool) $this->active;
}
/**
* @param bool $active
*
* @return $this
*/
public function setActive(bool $active)
{
$this->active = $active;
return $this;
}
}

28
src/Form/UserType.php Обычный файл
Просмотреть файл

@ -0,0 +1,28 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('roles')
->add('password')
->add('active')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

35
src/Migrations/Version20190622050819.php Обычный файл
Просмотреть файл

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20190622050819 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:simple_array)\', password VARCHAR(255) NOT NULL, active TINYINT(1) DEFAULT \'0\' NOT NULL, UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP TABLE user');
}
}

50
src/Repository/UserRepository.php Обычный файл
Просмотреть файл

@ -0,0 +1,50 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* @method User|null find($id, $lockMode = null, $lockVersion = null)
* @method User|null findOneBy(array $criteria, array $orderBy = null)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, User::class);
}
// /**
// * @return User[] Returns an array of User objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->orderBy('u.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?User
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

103
src/Security/LoginFormAuthenticator.php Обычный файл
Просмотреть файл

@ -0,0 +1,103 @@
<?php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return 'app_login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'username' => $request->request->get('username'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Username could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
$check_password = $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
if ($check_password && 1 === $user->getId() && !$user->isActive()) {//Если пользователь c id = 1, тогда делаем его админом
$user->setRoles(['ROLE_ADMIN', 'ROLE_USER'])->setActive(true);
$em = $this->entityManager;
$em->persist($user);
$em->flush();
}
return ($user->isActive() && $check_password) ? true : false;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('index_page'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}

Просмотреть файл

@ -1,12 +1,36 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="ru-RU">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title> <title>{% block title %}Welcome!{% endblock %}&mdash;Тест</title>
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous">
{% block stylesheets %}{% endblock %} {% block stylesheets %}{% endblock %}
</head> </head>
<body> <body>
<header>
<div class="container pt-5 pb-3 mb-5">
<h1>
<svg style="width: 50px;height: 50px;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve"> <g> <path d="M978.3,610.6c-3.9,4-7.4,7.6-10.5,11.1c-3.1,3.5-5.8,6.4-8.2,8.8c-3.1,3.2-5.8,5.5-8.1,7l-98.8-97.9c4.6-3.9,9.9-8.6,15.7-14c5.8-5.5,10.6-9.8,14.6-12.9c9.3-8.6,19.4-12.2,30.2-11.1c10.8,1.2,19.7,3.7,26.7,7.6c7.8,3.9,16.1,10.7,25,20.4c8.9,9.8,15.7,20,20.3,31c2.3,6.2,3.9,14,4.7,23.3C990.7,593.1,986.8,602.1,978.3,610.6L978.3,610.6L978.3,610.6z M799.2,789L719,869.4c-10.9,10.9-20,20.2-27.3,28c-7.4,7.7-11.4,12-12.2,12.8c-3.9,3.1-8.1,6.4-12.8,10c-4.6,3.4-9.3,6.4-13.9,8.7c-4.6,2.3-11.8,5.2-21.5,8.8c-9.7,3.5-19.6,6.7-29.6,9.9c-10.1,3.1-19.8,5.8-29.1,8.1c-9.3,2.4-16.3,3.9-20.9,4.7c-9.3,1.6-15.5,0.4-18.6-3.5c-3.1-3.9-3.9-10.5-2.3-19.8c0.8-4.7,2.3-11.7,4.6-21c2.3-9.4,5-18.9,8.2-28.6c3.1-9.8,6-18.9,8.7-27.5c2.7-8.5,4.8-14.4,6.3-17.4c4.7-10.1,10.9-19,18.6-26.8l15.1-15.2l29-29.1c11.6-11.7,24.4-24.7,38.3-39c13.9-14.4,27.9-28.6,41.8-42.5c33.4-33.4,70.9-70.8,112.8-111.9l97.6,97.9L799.2,789L799.2,789z M702.2,183.2c0-15.9-12.9-28.9-28.8-28.9h-57.7V96.7h115.4c15.9,0,28.8,12.9,28.8,28.9v392.2l-57.7,63.5V183.2L702.2,183.2L702.2,183.2z M558,212h-28.9c-15.9,0-28.8-12.9-28.8-28.9V67.8c0-15.9,12.9-28.9,28.8-28.9H558c15.9,0,28.8,12.9,28.8,28.9v115.4C586.8,199.1,573.9,212,558,212L558,212z M298.4,96.7h173.1v57.7H298.4V96.7z M240.7,212h-28.8c-15.9,0-28.9-12.9-28.9-28.9V67.8c0-15.9,12.9-28.9,28.9-28.9h28.8c15.9,0,28.9,12.9,28.9,28.9v115.4C269.6,199.1,256.7,212,240.7,212L240.7,212z M67.7,183.2v634.5c0,15.9,12.9,28.9,28.9,28.9H461l-52.4,57.7H38.8c-15.9,0-28.8-12.9-28.8-28.8V125.5c0-16,12.9-28.9,28.8-28.9h115.4v57.7H96.5C80.6,154.3,67.7,167.3,67.7,183.2L67.7,183.2L67.7,183.2z M615.7,659.1c0,7.9-6.5,14.4-14.4,14.4H168.6c-8,0-14.4-6.5-14.4-14.4v-28.8c0-8,6.4-14.4,14.4-14.4h432.6c8,0,14.4,6.4,14.4,14.4V659.1z M601.3,442.8H168.6c-8,0-14.4-6.5-14.4-14.4v-28.9c0-8,6.4-14.4,14.4-14.4h432.7c8,0,14.4,6.4,14.4,14.4v28.9C615.7,436.3,609.2,442.8,601.3,442.8L601.3,442.8L601.3,442.8z"/> </g> </svg>
{{ block('title') }} &mdash; Тест
</h1>
</div>
</header>
{% block body %}{% endblock %} {% block body %}{% endblock %}
<footer style="background-color: #232425;" class="mt-5 pt-2">
<div class="container pt-5 pb-4">
<div class="text-white text-right pb-4">
{{ "now"|date("Y") }}г.
</div>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
{% block javascripts %}{% endblock %} {% block javascripts %}{% endblock %}
</body> </body>
</html> </html>

17
templates/index.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,17 @@
{% extends "base.html.twig" %}
{% block title %}
Главная
{% endblock %}
{% block body %}
<div class="container">
{% if app.user == null %}
<a href="{{ path('app_login') }}" class="btn btn-primary">Войти</a>
{% else %}
<a href="{{ path('app_logout') }}" class="btn btn-primary">Выйти</a>
{% endif %}
<br class="mb-5">
{% if is_granted('ROLE_ADMIN') %}
<a href="{{ path('user_index') }}" class="btn btn-primary">Список пользователей</a>
{% endif %}
</div>
{% endblock %}

21
templates/registration/register.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,21 @@
{% extends 'base.html.twig' %}
{% block title %}Регистрация{% endblock %}
{% block body %}
<div class="container">
<a href="{{ path('app_login') }}" class="btn btn-primary mb-5">← Вход</a>
<div class="row justify-content-md-center">
<div class="col-md-4">
{{ form_start(registrationForm) }}
{{ form_row(registrationForm.username, {'label': 'Имя пользователя'}) }}
{{ form_row(registrationForm.plainPassword, {'label': 'Пароль'}) }}
<button class="btn btn-primary">Зарегистрироваться</button>
{{ form_end(registrationForm) }}
</div>
</div>
</div>
{% endblock %}

40
templates/security/login.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,40 @@
{% extends 'base.html.twig' %}
{% block title %}Вход{% endblock %}
{% block body %}
<div class="container">
<a href="{{ path('index_page') }}" class="btn btn-primary mb-5">← Главная</a>
<div class="row justify-content-md-center">
<div class="col-md-4">
<form method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Войдите</h1>
<div class="form-group">
<label for="inputUsername" class="sr-only">Пользователь</label>
<input type="text" value="{{ last_username }}" name="username" id="inputUsername" class="form-control" placeholder="Пользователь" required autofocus>
</div>
<div class="form-group">
<label for="inputPassword" class="sr-only">Пароль</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Пароль" required>
</div>
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"
>
<button class="btn btn-primary" type="submit">
Войти
</button>
<a href="{{ path('app_register') }}"
class="btn btn-outline-primary float-right">Регистрация</a>
</form>
</div>
</div>
</div>
{% endblock %}

5
templates/user/_delete_form.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,5 @@
<form method="post" action="{{ path('user_delete', {'id': user.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ user.id) }}">
<button class="btn btn-danger float-right">Удалить</button>
</form>

4
templates/user/_form.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,4 @@
{{ form_start(form) }}
{{ form_widget(form) }}
<button class="btn btn-success float-left">{{ button_label|default('Сохранить') }}</button>
{{ form_end(form) }}

17
templates/user/edit.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,17 @@
{% extends 'base.html.twig' %}
{% block title %}Edit User{% endblock %}
{% block body %}
<div class="container">
<a href="{{ path('user_index') }}" class="btn btn-primary mb-5">← к&nbsp;списку</a>
<div class="row">
<div class="col-md-6">
{{ include('user/_form.html.twig', {'button_label': 'Обновить'}) }}
{{ include('user/_delete_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}

48
templates/user/index.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,48 @@
{% extends 'base.html.twig' %}
{% block title %}Список пользователей{% endblock %}
{% block body %}
<div class="container">
<a href="{{ path('index_page') }}" class="btn btn-primary float-left mb-4">← Главная</a>
<a href="{{ path('user_new') }}" class="btn btn-success float-right mb-4">Добавить пользователя</a>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Username</th>
<th>Роли</th>
<th>Хеш пароля</th>
<th>Активный</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>
{% for role in user.roles %}
{{ role }},<br>
{% endfor %}
</td>
<td style="max-width: 18.75rem;"><div class="d-inline-block w-100 text-truncate" title="{{ user.password }}">{{ user.password }}</div></td>
<td>{{ user.active ? 'Да' : 'Нет' }}</td>
<td>
<a href="{{ path('user_show', {'id': user.id}) }}" class="btn btn-outline-primary float-right ml-2">Просмотр</a>
<a href="{{ path('user_edit', {'id': user.id}) }}" class="btn btn-outline-success float-right">Правка</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="6">нет записей</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

14
templates/user/new.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,14 @@
{% extends 'base.html.twig' %}
{% block title %}New User{% endblock %}
{% block body %}
<div class="container">
<a href="{{ path('user_index') }}" class="btn btn-primary mb-5">← к&nbsp;списку</a>
<div class="row">
<div class="col-md-6">
{{ include('user/_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}

42
templates/user/show.html.twig Обычный файл
Просмотреть файл

@ -0,0 +1,42 @@
{% extends 'base.html.twig' %}
{% block title %}Просмотр пользователя{% endblock %}
{% block body %}
<div class="container">
<a href="{{ path('user_index') }}" class="btn btn-primary mb-5">← к&nbsp;списку</a>
<table class="table">
<tbody>
<tr>
<th>Id</th>
<td>{{ user.id }}</td>
</tr>
<tr>
<th>Username</th>
<td>{{ user.username }}</td>
</tr>
<tr>
<th>Roles</th>
<td>{% for role in user.roles %}
{{ role }},<br>
{% endfor %}</td>
</tr>
<tr>
<th>Password</th>
<td>{{ user.password }}</td>
</tr>
<tr>
<th>Active</th>
<td>{{ user.active ? 'Yes' : 'No' }}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-md-5">
<a href="{{ path('user_edit', {'id': user.id}) }}" class="btn btn-success float-left">Правка</a>
{{ include('user/_delete_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}