symfony complete
Этот коммит содержится в:
родитель
c1951ca06a
Коммит
6d282daa30
@ -1,14 +1,27 @@
|
||||
security:
|
||||
encoders:
|
||||
App\Entity\User:
|
||||
algorithm: argon2i
|
||||
|
||||
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-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:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
anonymous: true
|
||||
guard:
|
||||
authenticators:
|
||||
- App\Security\LoginFormAuthenticator
|
||||
|
||||
logout:
|
||||
path: app_logout
|
||||
# activate different ways to authenticate
|
||||
# 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
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||
- { path: ^/admin/*, roles: ROLE_ADMIN }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
|
@ -2,3 +2,4 @@ twig:
|
||||
default_path: '%kernel.project_dir%/templates'
|
||||
debug: '%kernel.debug%'
|
||||
strict_variables: '%kernel.debug%'
|
||||
form_themes: ['bootstrap_4_layout.html.twig']
|
||||
|
17
src/Controller/IndexController.php
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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>
|
||||
<html>
|
||||
<html lang="ru-RU">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<title>{% block title %}Welcome!{% endblock %}—Тест</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 %}
|
||||
</head>
|
||||
<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') }} — Тест
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
{% 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 %}
|
||||
</body>
|
||||
</html>
|
||||
|
17
templates/index.html.twig
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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">← к списку</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
Обычный файл
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
Обычный файл
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">← к списку</a>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ include('user/_form.html.twig') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
42
templates/user/show.html.twig
Обычный файл
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">← к списку</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 %}
|
Загрузка…
Ссылка в новой задаче
Block a user