From 3fa30dbde4b35ecefe137419dd31cc27290f9a93 Mon Sep 17 00:00:00 2001 From: tracer Date: Mon, 14 Jun 2021 19:20:55 +0200 Subject: [PATCH] before recepie upgrade --- assets/js/app.js | 4 -- assets/styles/app.scss | 2 +- bin/console | 40 +++---------- composer.json | 1 + composer.lock | 15 ++--- config/packages/security.yaml | 7 ++- config/packages/twig.yaml | 2 +- config/services.yaml | 4 ++ migrations/Version20210614155532.php | 31 ++++++++++ src/Controller/Admin/BlogCrudController.php | 4 +- src/Controller/BlogController.php | 66 +++++++++++++++++++-- src/Entity/Blog.php | 25 +++++++- src/EntityListener/BlogEntityListener.php | 43 ++++++++++++++ src/Form/BlogFormType.php | 40 +++++++++++++ src/Security/AppAuthenticator.php | 49 ++++++++------- symfony.lock | 6 +- templates/_header.html.twig | 14 +++-- templates/blog/new.html.twig | 15 +++++ templates/blog/show.html.twig | 4 ++ 19 files changed, 284 insertions(+), 88 deletions(-) create mode 100644 migrations/Version20210614155532.php create mode 100644 src/EntityListener/BlogEntityListener.php create mode 100644 src/Form/BlogFormType.php create mode 100644 templates/blog/new.html.twig diff --git a/assets/js/app.js b/assets/js/app.js index cde663c..64cec45 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -5,9 +5,5 @@ require('bootstrap'); import '../styles/app.scss'; -$(document).ready(function() { - $('.navbarDropdown').dropdown(); -}); - require('@fortawesome/fontawesome-free/css/all.min.css'); require('@fortawesome/fontawesome-free/js/all.js'); diff --git a/assets/styles/app.scss b/assets/styles/app.scss index ae603d6..7387bd8 100644 --- a/assets/styles/app.scss +++ b/assets/styles/app.scss @@ -11,7 +11,7 @@ /* debug */ * { - border: 0 solid gray; + border: 1px solid gray; } // customize some Bootstrap variables diff --git a/bin/console b/bin/console index 8fe9d49..c933dc5 100755 --- a/bin/console +++ b/bin/console @@ -3,41 +3,15 @@ use App\Kernel; use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Dotenv\Dotenv; -use Symfony\Component\ErrorHandler\Debug; -if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { - echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL; +if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) { + throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".'); } -set_time_limit(0); +require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; -require dirname(__DIR__).'/vendor/autoload.php'; +return function (array $context) { + $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); -if (!class_exists(Application::class) || !class_exists(Dotenv::class)) { - throw new LogicException('You need to add "symfony/framework-bundle" and "symfony/dotenv" as Composer dependencies.'); -} - -$input = new ArgvInput(); -if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) { - putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); -} - -if ($input->hasParameterOption('--no-debug', true)) { - putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); -} - -(new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); - -if ($_SERVER['APP_DEBUG']) { - umask(0000); - - if (class_exists(Debug::class)) { - Debug::enable(); - } -} - -$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); -$application = new Application($kernel); -$application->run($input); + return new Application($kernel); +}; diff --git a/composer.json b/composer.json index 040ac1e..26b9640 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "symfony/monolog-bundle": "^3.7", "symfony/proxy-manager-bridge": "5.3.*", "symfony/security-bundle": "5.3.*", + "symfony/string": "5.3.*", "symfony/twig-bundle": "^5.2", "symfony/validator": "5.3.*", "symfony/webpack-encore-bundle": "^1.11", diff --git a/composer.lock b/composer.lock index 7bbe825..5bd235d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "34381deb043c87393a1f92bbeab2aaff", + "content-hash": "f801003bc6731e7d38ee4787d3a9489a", "packages": [ { "name": "composer/package-versions-deprecated", @@ -6750,16 +6750,16 @@ }, { "name": "symfony/validator", - "version": "v5.3.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "14337bdf9e2e0b2e3385c9e90f13325f0c95a4f9" + "reference": "111e71ac585a47358e808bc687dcaf66e568470a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/14337bdf9e2e0b2e3385c9e90f13325f0c95a4f9", - "reference": "14337bdf9e2e0b2e3385c9e90f13325f0c95a4f9", + "url": "https://api.github.com/repos/symfony/validator/zipball/111e71ac585a47358e808bc687dcaf66e568470a", + "reference": "111e71ac585a47358e808bc687dcaf66e568470a", "shasum": "" }, "require": { @@ -6778,6 +6778,7 @@ "symfony/expression-language": "<5.1", "symfony/http-kernel": "<4.4", "symfony/intl": "<4.4", + "symfony/property-info": "<5.3", "symfony/translation": "<4.4", "symfony/yaml": "<4.4" }, @@ -6839,7 +6840,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v5.3.0" + "source": "https://github.com/symfony/validator/tree/v5.3.1" }, "funding": [ { @@ -6855,7 +6856,7 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2021-06-02T09:36:17+00:00" }, { "name": "symfony/var-dumper", diff --git a/config/packages/security.yaml b/config/packages/security.yaml index f8bd720..031db6d 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,5 +1,5 @@ security: - encoders: + password_hashers: App\Entity\User: algorithm: auto @@ -10,12 +10,14 @@ security: entity: class: App\Entity\User property: username + + enable_authenticator_manager: true + firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: true lazy: true provider: app_user_provider guard: @@ -35,5 +37,6 @@ 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/login, roles: PUBLIC_ACCESS } # - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 90c5321..02674b0 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,3 +1,3 @@ twig: default_path: '%kernel.project_dir%/templates' - form_themes: ['bootstrap_4_layout.html.twig'] + form_themes: ['bootstrap_5_layout.html.twig'] diff --git a/config/services.yaml b/config/services.yaml index 61a226d..9ab13b2 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -33,3 +33,7 @@ services: - '%env(DATABASE_URL)%' # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + App\EntityListener\BlogEntityListener: + tags: + - { name: 'doctrine.orm.entity_listener', event: 'prePersist', entity: 'App\Entity\Blog'} + - { name: 'doctrine.orm.entity_listener', event: 'preUpdate', entity: 'App\Entity\Blog'} diff --git a/migrations/Version20210614155532.php b/migrations/Version20210614155532.php new file mode 100644 index 0000000..091ea5b --- /dev/null +++ b/migrations/Version20210614155532.php @@ -0,0 +1,31 @@ +addSql('CREATE UNIQUE INDEX UNIQ_C0155143989D9B62 ON blog (slug)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_C0155143989D9B62 ON blog'); + } +} diff --git a/src/Controller/Admin/BlogCrudController.php b/src/Controller/Admin/BlogCrudController.php index 4389b79..2021042 100644 --- a/src/Controller/Admin/BlogCrudController.php +++ b/src/Controller/Admin/BlogCrudController.php @@ -6,6 +6,7 @@ use App\Entity\Blog; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; +use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; @@ -26,7 +27,8 @@ class BlogCrudController extends AbstractCrudController AssociationField::new('author') ->autocomplete(), TextField::new('title'), - TextField::new('slug'), + SlugField::new('slug') + ->setTargetFieldName('title'), TextEditorField::new('teaser'), TextEditorField::new('content'), DateTimeField::new('createdAt'), diff --git a/src/Controller/BlogController.php b/src/Controller/BlogController.php index 009dcdb..38325d1 100644 --- a/src/Controller/BlogController.php +++ b/src/Controller/BlogController.php @@ -3,8 +3,12 @@ namespace App\Controller; use App\Entity\Blog; +use App\Form\BlogFormType; use App\Repository\BlogRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -18,22 +22,76 @@ class BlogController extends AbstractController public function index(BlogRepository $blogRepository): Response { return $this->render('blog/index.html.twig', [ - 'blogs' => $blogRepository->findAll() + 'blogs' => $blogRepository->findAll() ]); } /** - * @param $id + * @param $slug * @param \App\Repository\BlogRepository $blogRepository * * @return \Symfony\Component\HttpFoundation\Response */ - #[Route('/blog/{slug}', name: 'blog')] + #[Route('/blog_show/{slug}', name: 'blog')] public function show($slug, BlogRepository $blogRepository): Response { return $this->render('blog/show.html.twig', [ - 'blog' => $blogRepository->findOneBy(['slug' => $slug]) + 'blog' => $blogRepository->findOneBy(['slug' => $slug]) ]); } + + /** + * @param \Doctrine\ORM\EntityManagerInterface $entityManager + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response + */ + #[Route('/blog/new', name: 'blog_new')] + public function new(EntityManagerInterface $entityManager, Request $request): RedirectResponse|Response + { + $form = $this->createForm(BlogFormType::class); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $blog = $form->getData(); + $entityManager->persist($blog); + $entityManager->flush(); + + return $this->redirectToRoute('blogs'); + } + return $this->render('blog/new.html.twig', [ + 'blogForm' => $form->createView(), + ]); + } + + /** + * @param \App\Entity\Blog $blog + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response + */ + #[Route('/blog/edit/{id}', name: 'blog_edit')] + public function edit(Blog $blog, Request $request): Response|RedirectResponse + { + $form = $this->createForm(BlogFormType::class, $blog); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $blog = $form->getData(); + //$blog->setAuthor($this->getUser()); + + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($blog); + $entityManager->flush(); + + return $this->redirectToRoute('blogs'); + } + return $this->render('blog/new.html.twig', [ + 'blogForm' => $form->createView(), + ]); + } + } diff --git a/src/Entity/Blog.php b/src/Entity/Blog.php index 2bebcc9..4efd917 100644 --- a/src/Entity/Blog.php +++ b/src/Entity/Blog.php @@ -7,11 +7,13 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use JetBrains\PhpStorm\Pure; -use App\Repository\SectionRepository; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\String\Slugger\SluggerInterface; /** * @ORM\Entity(repositoryClass=BlogRepository::class) * @ORM\HasLifecycleCallbacks() + * @UniqueEntity("slug") */ class Blog { @@ -79,7 +81,7 @@ class Blog private $comments; /** - * @ORM\Column(type="string", length=255) + * @ORM\Column(type="string", length=255, unique=true) */ private $slug; @@ -274,7 +276,24 @@ class Blog public function setSlug(string $slug): self { $this->slug = $slug; - return $this; } + + + /** + * @ORM\PrePersist + */ + public function onPrePersist() + { + $this->createdAt = new \DateTime(); + } + + /** + * @param SluggerInterface $slugger + */ + public function computeSlug(SluggerInterface $slugger) + { + $this->slug = $slugger->slug($this->title); + } + } diff --git a/src/EntityListener/BlogEntityListener.php b/src/EntityListener/BlogEntityListener.php new file mode 100644 index 0000000..bda7236 --- /dev/null +++ b/src/EntityListener/BlogEntityListener.php @@ -0,0 +1,43 @@ +slugger = $slugger; + } + + /** + * @param \App\Entity\Blog $blog + * @param \Doctrine\ORM\Event\LifecycleEventArgs $title + */ + #[NoReturn] + public function prePersist(Blog $blog, LifecycleEventArgs $title) + { + $blog->computeSlug($this->slugger); + } + + /** + * @param \App\Entity\Blog $blog + * @param \Doctrine\ORM\Event\LifecycleEventArgs $title + */ + #[NoReturn] + public function preUpdate(Blog $blog, LifecycleEventArgs $title) + { + //dd($title); + $blog->computeSlug($this->slugger); + } +} \ No newline at end of file diff --git a/src/Form/BlogFormType.php b/src/Form/BlogFormType.php new file mode 100644 index 0000000..2c3a0e8 --- /dev/null +++ b/src/Form/BlogFormType.php @@ -0,0 +1,40 @@ +add('title') + ->add('teaser') + ->add('content') + ->add('author'); + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Blog::class + ]); + } + + +} \ No newline at end of file diff --git a/src/Security/AppAuthenticator.php b/src/Security/AppAuthenticator.php index c0a6108..7a1dd8c 100644 --- a/src/Security/AppAuthenticator.php +++ b/src/Security/AppAuthenticator.php @@ -7,12 +7,12 @@ use Doctrine\ORM\EntityManagerInterface; use JetBrains\PhpStorm\ArrayShape; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -22,23 +22,27 @@ use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticato use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface; use Symfony\Component\Security\Http\Util\TargetPathTrait; +/** + * Class AppAuthenticator + * @package App\Security + */ class AppAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface { use TargetPathTrait; public const LOGIN_ROUTE = 'app_login'; - private $entityManager; - private $urlGenerator; - private $csrfTokenManager; - private $passwordEncoder; + private EntityManagerInterface $entityManager; + private UrlGeneratorInterface $urlGenerator; + private CsrfTokenManagerInterface $csrfTokenManager; + private UserPasswordHasherInterface $passwordHasher; - public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder) + public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordHasherInterface $passwordHasher) { $this->entityManager = $entityManager; $this->urlGenerator = $urlGenerator; $this->csrfTokenManager = $csrfTokenManager; - $this->passwordEncoder = $passwordEncoder; + $this->passwordHasher = $passwordHasher; } /** @@ -109,7 +113,7 @@ class AppAuthenticator extends AbstractFormLoginAuthenticator implements Passwor * @throws AuthenticationException * */ - public function getUser($credentials, UserProviderInterface $userProvider) + public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface { $token = new CsrfToken('authenticate', $credentials['csrf_token']); if (!$this->csrfTokenManager->isTokenValid($token)) { @@ -125,7 +129,7 @@ class AppAuthenticator extends AbstractFormLoginAuthenticator implements Passwor } if (!$user) { - throw new UsernameNotFoundException('Username or email could not be found.'); + throw new UserNotFoundException('Username or email could not be found.'); } else { return $user; } @@ -149,7 +153,7 @@ class AppAuthenticator extends AbstractFormLoginAuthenticator implements Passwor public function checkCredentials($credentials, UserInterface $user): bool { //return true; - return $this->passwordEncoder->isPasswordValid($user, $credentials['password']); + return $this->passwordHasher->isPasswordValid($user, $credentials['password']); } /** @@ -160,6 +164,17 @@ class AppAuthenticator extends AbstractFormLoginAuthenticator implements Passwor return $credentials['password']; } + /** + * Called when authentication executed and was successful! + * + * This should return the Response sent back to the user, like a + * RedirectResponse to the last page they visited. + * + * If you return null, the current request will continue, and the user + * will be authenticated. This makes sense, for example, with an API. + * + * @return Response|null + */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey) { if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { @@ -174,15 +189,3 @@ class AppAuthenticator extends AbstractFormLoginAuthenticator implements Passwor return $this->urlGenerator->generate(self::LOGIN_ROUTE); } } - -/* - -comment: -author => user, -createdAt, -editedAt, -editedby => user, -editreason - -*/ - diff --git a/symfony.lock b/symfony.lock index 9422eef..ca35d51 100644 --- a/symfony.lock +++ b/symfony.lock @@ -140,12 +140,12 @@ "version": "v5.2.8" }, "symfony/console": { - "version": "5.1", + "version": "5.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "5.1", - "ref": "c6d02bdfba9da13c22157520e32a602dbee8a75c" + "version": "5.3", + "ref": "da0c8be8157600ad34f10ff0c9cc91232522e047" }, "files": [ "bin/console" diff --git a/templates/_header.html.twig b/templates/_header.html.twig index 84985ed..1625265 100644 --- a/templates/_header.html.twig +++ b/templates/_header.html.twig @@ -10,14 +10,16 @@