Skip to content

Commit

Permalink
Merge pull request #26 from digitaldreams/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
digitaldreams authored Oct 20, 2023
2 parents 1df2f49 + 7c9fb06 commit 54ac094
Show file tree
Hide file tree
Showing 46 changed files with 966 additions and 364 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.4|^8.0",
"php": "^8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.10",
Expand Down
2 changes: 1 addition & 1 deletion config/packages/doctrine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ doctrine:
mappings:
App:
is_bundle: false
type: annotation
type: attribute
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
2 changes: 1 addition & 1 deletion config/packages/reset_password.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
symfonycasts_reset_password:
request_password_repository: App\Repository\ResetPasswordRequestRepository
request_password_repository: App\Persistence\Repository\ResetPasswordRequestRepository
7 changes: 5 additions & 2 deletions config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ security:
form_login:
login_path: app_login
check_path: app_login
default_target_path: profile_show
post_only: true
use_referer: true
logout:
path: app_logout
# where to redirect after logout
Expand All @@ -42,5 +45,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: ^/profile, roles: ROLE_USER }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/app, roles: ROLE_USER }
13 changes: 13 additions & 0 deletions src/Attribute/FillDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_PARAMETER)]
readonly class FillDto
{
public function __construct()
{
}
}
55 changes: 55 additions & 0 deletions src/Attribute/FillDtoResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace App\Attribute;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

use function Symfony\Component\String\u;

class FillDtoResolver implements ValueResolverInterface
{

public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$attribute = $argument->getAttributesOfType(
FillDto::class,
ArgumentMetadata::IS_INSTANCEOF
)[0] ?? null;

if (!$attribute) {
return [];
}

$fillDto = $argument->getAttributesOfType(FillDto::class, ArgumentMetadata::IS_INSTANCEOF)[0];

if ($argument->isVariadic()) {
throw new \LogicException(
sprintf('Mapping variadic argument "$%s" is not supported.', $argument->getName())
);
}
$reflectionClass = new \ReflectionClass($argument->getType());
$dtoClass = $reflectionClass->newInstanceWithoutConstructor();

$queryParams = $request->query->all();
$requestParams = $request->request->all();
foreach (array_merge($queryParams, $requestParams) as $property => $value) {
$attribute = u($property)->camel();
if (property_exists($dtoClass, $attribute)) {
$reflectionProperty = $reflectionClass->getProperty($attribute);
$reflectionProperty->setValue($dtoClass, $value);
}
}
$files = $request->files->all();
foreach ($files as $fileKey => $file) {
$attribute = u($fileKey)->camel();
if (property_exists($dtoClass, $attribute)) {
$reflectionProperty = $reflectionClass->getProperty($attribute);
$reflectionProperty->setValue($dtoClass, $file);
}
}

return [$dtoClass];
}
}
Empty file removed src/Controller/.gitignore
Empty file.
11 changes: 5 additions & 6 deletions src/Controller/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Core\User\UserInterface;
class HomeController extends AbstractController
{
/**
* @Route("/home", name="home")
* @IsGranted("IS_AUTHENTICATED_FULLY")
*/
public function index()

#[Route("/", name: "home")]
public function index(#[CurrentUser] UserInterface $user)
{
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
class LoginController extends AbstractController
{
/**
* @Route("/login", name="app_login")
*/
#[Route("/auth/login", name:"app_login")]
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
Expand All @@ -25,12 +23,4 @@ public function login(AuthenticationUtils $authenticationUtils): Response

return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}

/**
* @Route("/logout", name="app_logout")
*/
public function logout()
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}
75 changes: 32 additions & 43 deletions src/Controller/PasswordChangeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,51 @@

namespace App\Controller;

use App\Service\Password\PasswordChangeRequest;
use App\Service\Password\PasswordChangeService;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bridge\Twig\Attribute\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Exception\ValidationFailedException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use App\Attribute\FillDto;

use Symfony\Contracts\Translation\TranslatorInterface;

use function Symfony\Component\Translation\t;

class PasswordChangeController extends AbstractController
{
/**
* @Route("/password/change", name="password_change",methods="GET|HEAD")
* @IsGranted("IS_AUTHENTICATED_FULLY")
*/
public function index()
{
return $this->render('password_change/index.html.twig');
public function __construct(
private PasswordChangeService $passwordChangeService,
private TranslatorInterface $translator
) {
}

/**
* @Route("/password_save/save", name="password_save",methods="POST|PUT")
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface $passwordEncoder
* @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator
*
* @param \Doctrine\ORM\EntityManagerInterface $entityManager
*
* @return \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse
*/
public function change(Request $request, UserPasswordHasherInterface $passwordEncoder, ValidatorInterface $validator, EntityManagerInterface $entityManager)
{
$input = $request->request->all();
$constraint = new Assert\Collection([
'old_password' => new UserPassword(["message" => "Wrong value for your current password"]),
'new_password' => new Assert\Length(['min' => 6]),
'confirm_new_password' => new Assert\EqualTo([
'value' => $request->get('new_password'),
'message' => 'Confirm password does not match.',
]),
]);
$errors = $validator->validate($input, $constraint);

if (count($errors) > 0) {
return $this->render('password_change/index.html.twig', [
'errors' => $errors,
]);
#[Route("/app/password/change", name: "password_change", methods: ['GET', 'POST'])]
#[Template('password_change/index.html.twig')]
public function change(
#[FillDto] PasswordChangeRequest $passwordChangeRequest,
Request $request,
ValidatorInterface $validator
) {
$errors = [];
if ($request->getMethod() === 'POST') {
$errors = $validator->validate($passwordChangeRequest);
if (count($errors) == 0) {
$this->passwordChangeService->execute($passwordChangeRequest);
$this->addFlash('message', $this->translator->trans('password.changed_successfully'));
return $this->redirectToRoute('home');
}
}
$user = $this->getUser();

$user->setPassword($passwordEncoder->hashPassword($user, $request->get('new_password')));
$entityManager->persist($user);
$entityManager->flush();

$this->addFlash('message', 'Password changed successfully');
return $this->redirectToRoute('home');
return ['errors' => $errors];
}
}
112 changes: 30 additions & 82 deletions src/Controller/ProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

namespace App\Controller;

use App\Attribute\FillDto;
use App\Service\User\UpdateProfileRequest;
use App\Service\User\UpdateProfileService;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bridge\Twig\Attribute\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -12,101 +16,45 @@
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\String\Slugger\SluggerInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class ProfileController extends AbstractController
{

/**
* @Route("/profile", name="profile")
* @param \Symfony\Component\Security\Core\User\UserInterface $user
* @IsGranted("IS_AUTHENTICATED_FULLY")
*
* @return \Symfony\Component\HttpFoundation\Response
*
*/
public function index(UserInterface $user)
public function __construct(private UpdateProfileService $profileService, private TranslatorInterface $translator)
{
return $this->render('profile/index.html.twig', [
'user' => $user,
]);
}

/**
* @Route("/profile/update", name="profile_update")
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Doctrine\ORM\EntityManagerInterface $entityManager
* @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator
* @param \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface $csrfTokenManager
*
* @param \Symfony\Component\String\Slugger\SluggerInterface $slugger
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
* @IsGranted("IS_AUTHENTICATED_FULLY")
*
*/
public function store(Request $request, EntityManagerInterface $entityManager, ValidatorInterface $validator, CsrfTokenManagerInterface $csrfTokenManager, SluggerInterface $slugger)
{
$fileErrors = [];
$token = new CsrfToken('authenticate', $request->get('_csrf_token'));
if (!$csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}

$user = $this->getUser();
$user->setName($request->get('name'));
$user->setUsername($request->get('username'));
$user->setEmail($request->get('email'));


if ($request->files->has('avatar')) {
$avatarFile = $request->files->get('avatar');

$constraint = new Assert\Collection([
'avatar' => new Assert\Image([
'maxSize' => 2048,
]),
]);
$fileErrors = $validator->validate(['avatar' => $request->files->has('avatar')]);
if (count($fileErrors) > 0) {
return $this->render('profile/index.html.twig', [
'user' => $user,
'errors' => $fileErrors,
]);
#[Route("/app/profile", name: "profile_show", methods: ['GET', 'POST'])]
#[Template('profile/index.html.twig')]
public function update(
Request $request,
ValidatorInterface $validator,
CsrfTokenManagerInterface $csrfTokenManager,
#[CurrentUser] UserInterface $user,
#[FillDto] UpdateProfileRequest $updateProfileRequest,
) {
$errors = [];
if ($request->getMethod() === 'POST') {
$fileErrors = [];
$token = new CsrfToken('authenticate', $request->get('_csrf_token'));
if (!$csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$originalFilename = pathinfo($avatarFile->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $avatarFile->guessExtension();

// Move the file to the directory where avatar are stored
try {
$avatarFile->move(
'images',
$newFilename
);
$user->setAvatar('/images/' . $newFilename);
} catch (FileException $e) {
$this->addFlash('message', $e->getMessage());
return $this->redirectToRoute('profile');
$errors = $validator->validate($updateProfileRequest);
if (count($errors) === 0) {
$this->profileService->execute($updateProfileRequest);
$this->addFlash('message', $this->translator->trans('user.profile_updated'));
}
}

$errors = $validator->validate($user);
if (count($errors) > 0) {

return $this->render('profile/index.html.twig', [
'user' => $user,
'errors' => $errors,
]);
}

$entityManager->persist($user);
$entityManager->flush();

return $this->redirectToRoute('home');

return [
'errors' => $errors,
'user' => $user
];
}
}
Loading

0 comments on commit 54ac094

Please sign in to comment.