added dynamic user preferences #7 #9 (#90)

- fixed user settings database #9
- added dynamic user preferences #9
- added form to edit user preferences #9
- removed user language #7
This commit is contained in:
Kevin Papst
2018-01-14 22:47:20 +01:00
committed by GitHub
parent 023fef6a70
commit 49d8796f6b
23 changed files with 758 additions and 181 deletions

View File

@@ -70,9 +70,8 @@ class CreateUserCommand extends Command
->setDescription('Create a new user')
->setHelp('This command allows you to create a new user.')
->addArgument('username', InputArgument::REQUIRED, 'New username (must be unique)')
->addArgument('email', InputArgument::REQUIRED, 'Users email address (must be unique)')
->addArgument('password', InputArgument::REQUIRED, 'Users password')
->addArgument('language', InputArgument::OPTIONAL, 'Users language', User::DEFAULT_LANGUAGE)
->addArgument('email', InputArgument::REQUIRED, 'Users email address (must be unique)')
->addArgument('role', InputArgument::OPTIONAL, 'Users role (comma separated list)', User::DEFAULT_ROLE)
;
}
@@ -87,17 +86,14 @@ class CreateUserCommand extends Command
$username = $input->getArgument('username');
$email = $input->getArgument('email');
$password = $input->getArgument('password');
$language = $input->getArgument('language');
$role = $input->getArgument('role');
$language = $language ?: User::DEFAULT_LANGUAGE;
$role = $role ?: User::DEFAULT_ROLE;
$user = new User();
$user->setUsername($username)
->setPlainPassword($password)
->setEmail($email)
->setLanguage($language)
->setRoles(explode(',', $role))
;

View File

@@ -14,7 +14,9 @@ namespace App\Controller;
use App\Entity\User;
use App\Form\UserEditType;
use App\Form\UserPasswordType;
use App\Form\UserPreferencesForm;
use App\Form\UserRolesType;
use App\Voter\UserVoter;
use Symfony\Component\Form\Form;
use App\Entity\Timesheet;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
@@ -40,7 +42,7 @@ class ProfileController extends AbstractController
*/
public function indexAction(User $profile)
{
return $this->getProfileView($profile);
return $this->getProfileView($profile, 'charts');
}
/**
@@ -50,10 +52,10 @@ class ProfileController extends AbstractController
*/
public function editAction(User $profile, Request $request)
{
$editForm = $this->createEditForm($profile);
$editForm->handleRequest($request);
$form = $this->createEditForm($profile);
$form->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($profile);
$entityManager->flush();
@@ -63,7 +65,7 @@ class ProfileController extends AbstractController
return $this->redirectToRoute('user_profile', ['username' => $profile->getUsername()]);
}
return $this->getProfileView($profile, $editForm, null, null, 'profile');
return $this->getProfileView($profile, 'settings', $form);
}
/**
@@ -73,10 +75,10 @@ class ProfileController extends AbstractController
*/
public function passwordAction(User $profile, Request $request)
{
$pwdForm = $this->createPasswordForm($profile);
$pwdForm->handleRequest($request);
$form = $this->createPasswordForm($profile);
$form->handleRequest($request);
if ($pwdForm->isSubmitted() && $pwdForm->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
$password = $this->get('security.password_encoder')
->encodePassword($profile, $profile->getPlainPassword());
$profile->setPassword($password);
@@ -90,7 +92,7 @@ class ProfileController extends AbstractController
return $this->redirectToRoute('user_profile', ['username' => $profile->getUsername()]);
}
return $this->getProfileView($profile, null, $pwdForm, null, 'password');
return $this->getProfileView($profile, 'password', null, $form);
}
/**
@@ -100,10 +102,10 @@ class ProfileController extends AbstractController
*/
public function rolesAction(User $profile, Request $request)
{
$rolesForm = $this->createRolesForm($profile);
$rolesForm->handleRequest($request);
$form = $this->createRolesForm($profile);
$form->handleRequest($request);
if ($rolesForm->isSubmitted() && $rolesForm->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($profile);
$entityManager->flush();
@@ -113,24 +115,73 @@ class ProfileController extends AbstractController
return $this->redirectToRoute('user_profile', ['username' => $profile->getUsername()]);
}
return $this->getProfileView($profile, null, null, $rolesForm, 'roles');
return $this->getProfileView($profile, 'roles', null, null, $form);
}
/**
* @Route("/{username}/prefs", name="user_profile_preferences")
* @Method({"GET", "POST"})
* @Security("is_granted('preferences', profile)")
*/
public function savePreferencesAction(User $profile, Request $request)
{
$original = [];
foreach ($profile->getPreferences() as $preference) {
$original[$preference->getName()] = $preference;
}
$form = $this->createPreferencesForm($profile);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$preferences = $profile->getPreferences();
// do not allow to add unknown preferences
foreach ($preferences as $preference) {
if (!isset($original[$preference->getName()])) {
$preferences->removeElement($preference);
}
}
// but allow to delete already saved settings
foreach ($original as $name => $preference) {
if (false === $profile->getPreferences()->contains($preference)) {
$entityManager->remove($preference);
}
}
foreach ($preferences as $preference) {
$preference->setUser($profile);
$entityManager->persist($preference);
$entityManager->flush();
}
$this->flashSuccess('action.updated_successfully');
return $this->redirectToRoute('user_profile', ['username' => $profile->getUsername()]);
}
return $this->getProfileView($profile, 'preferences', null, null, null, $form);
}
/**
* @param User $user
* @param string $tab
* @param Form|null $editForm
* @param Form|null $pwdForm
* @param Form|null $rolesForm
* @param string $tab
* @param Form|null $prefsForm
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Doctrine\ORM\NonUniqueResultException
*/
protected function getProfileView(
User $user,
string $tab,
Form $editForm = null,
Form $pwdForm = null,
Form $rolesForm = null,
$tab = 'charts'
Form $prefsForm = null
) {
/* @var $timesheetRepo TimesheetRepository */
$timesheetRepo = $this->getDoctrine()->getRepository(Timesheet::class);
@@ -142,27 +193,45 @@ class ProfileController extends AbstractController
'user' => $user,
'stats' => $userStats,
'years' => $monthlyStats,
'form' => null,
'form_password' => null,
'form_roles' => null,
'forms' => []
];
if ($this->isGranted('edit', $user)) {
if ($this->isGranted(UserVoter::EDIT, $user)) {
$editForm = $editForm ?: $this->createEditForm($user);
$viewVars['form'] = $editForm->createView();
$viewVars['forms']['settings'] = $editForm->createView();
}
if ($this->isGranted('password', $user)) {
if ($this->isGranted(UserVoter::PASSWORD, $user)) {
$pwdForm = $pwdForm ?: $this->createPasswordForm($user);
$viewVars['form_password'] = $pwdForm->createView();
$viewVars['forms']['password'] = $pwdForm->createView();
}
if ($this->isGranted('roles', $user)) {
if ($this->isGranted(UserVoter::ROLES, $user)) {
$rolesForm = $rolesForm ?: $this->createRolesForm($user);
$viewVars['form_roles'] = $rolesForm->createView();
$viewVars['forms']['roles'] = $rolesForm->createView();
}
if ($this->isGranted(UserVoter::PREFERENCES, $user)) {
$prefsForm = $prefsForm ?: $this->createPreferencesForm($user);
$viewVars['forms']['preferences'] = $prefsForm->createView();
}
return $this->render('user/profile.html.twig', $viewVars);
}
/**
* @param User $user
* @return \Symfony\Component\Form\FormInterface
*/
private function createPreferencesForm(User $user)
{
return $this->createForm(
UserPreferencesForm::class,
$user,
[
'action' => $this->generateUrl('user_profile_preferences', ['username' => $user->getUsername()]),
'method' => 'POST'
]
);
}
/**
* @param User $user
* @return \Symfony\Component\Form\FormInterface

View File

@@ -11,6 +11,7 @@
namespace App\Controller;
use App\Form\UserPreferencesForm;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
@@ -22,13 +23,32 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
*/
class SidebarController extends AbstractController
{
/**
* @return \Symfony\Component\HttpFoundation\Response
*/
public function homeAction()
{
return $this->render('sidebar/home.html.twig', []);
}
/**
* @return \Symfony\Component\HttpFoundation\Response
*/
public function settingsAction()
{
return $this->render('sidebar/settings.html.twig', []);
/*
$user = $this->getUser();
$form = $this->createForm(UserPreferencesForm::class, $user, [
'action' => $this->generateUrl('user_profile_preferences', ['username' => $user->getUsername()]),
'method' => 'POST',
]);
return $this->render('sidebar/settings.html.twig', [
'form' => $form->createView(),
]);
*/
}
}

View File

@@ -12,13 +12,14 @@
namespace App\Doctrine;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\Common\EventSubscriber;
/**
* Adds a prefix to every doctrine entity AKA database table
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class TablePrefixSubscriber implements \Doctrine\Common\EventSubscriber
class TablePrefixSubscriber implements EventSubscriber
{
protected $prefix = '';

View File

@@ -3,6 +3,8 @@
namespace App\Entity;
use App\Validator\Constraints as KimaiAssert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
@@ -10,7 +12,7 @@ use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* User
* Application main User entity.
*
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
* @ORM\Table(
@@ -29,7 +31,6 @@ class User implements UserInterface, AdvancedUserInterface
{
const DEFAULT_ROLE = 'ROLE_USER';
const DEFAULT_LANGUAGE = 'de';
/**
* @var int
@@ -81,14 +82,6 @@ class User implements UserInterface, AdvancedUserInterface
*/
private $alias;
/**
* @var string
*
* @ORM\Column(name="language", type="string", length=5, nullable=true)
* @Assert\Language()
*/
private $language = self::DEFAULT_LANGUAGE;
/**
* @var boolean
*
@@ -126,12 +119,20 @@ class User implements UserInterface, AdvancedUserInterface
*/
private $roles = [];
/**
* @var UserPreference[]|Collection
*
* @ORM\OneToMany(targetEntity="App\Entity\UserPreference", mappedBy="user")
*/
private $preferences;
/**
* User constructor.
*/
public function __construct()
{
$this->registeredAt = new \DateTime();
$this->preferences = new ArrayCollection();
}
/**
@@ -151,10 +152,10 @@ class User implements UserInterface, AdvancedUserInterface
}
/**
* @param $registeredAt
* @param \DateTime $registeredAt
* @return $this
*/
public function setRegisteredAt($registeredAt)
public function setRegisteredAt(\DateTime $registeredAt)
{
$this->registeredAt = $registeredAt;
@@ -199,15 +200,6 @@ class User implements UserInterface, AdvancedUserInterface
return $this->active;
}
/**
* @deprecated
* @return boolean
*/
public function getActive()
{
return $this->isActive();
}
/**
* @param string $password
* @return $this
@@ -324,24 +316,6 @@ class User implements UserInterface, AdvancedUserInterface
return $this;
}
/**
* @return string
*/
public function getLanguage()
{
return $this->language;
}
/**
* @param string $language
* @return $this
*/
public function setLanguage($language)
{
$this->language = $language;
return $this;
}
/**
* Returns the roles or permissions granted to the user for security.
*
@@ -369,6 +343,37 @@ class User implements UserInterface, AdvancedUserInterface
return $this;
}
/**
* @return UserPreference[]|Collection
*/
public function getPreferences(): Collection
{
return $this->preferences;
}
/**
* @param UserPreference[]|Collection<UserPreference> $preferences
* @return User
*/
public function setPreferences(array $preferences)
{
if (!($preferences instanceof Collection) && is_array($preferences)) {
$preferences = new ArrayCollection($preferences);
}
$this->preferences = $preferences;
return $this;
}
/**
* @param UserPreference $preference
* @return User
*/
public function addPreference(UserPreference $preference)
{
$this->preferences->add($preference);
return $this;
}
/**
* @inheritdoc
*/

View File

@@ -3,56 +3,124 @@
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints as Assert;
/**
* UserPreference
*
* @ORM\Table(name="preferences",indexes={@ORM\Index(columns={"userid", "option"})}))
* @ORM\Entity
* @ORM\Entity()
* @ORM\Table(
* name="user_preferences",
* uniqueConstraints={
* @ORM\UniqueConstraint(columns={"user_id", "name"})
* }
* )
*/
class UserPreference
{
const HOURLY_RATE = 'hourly_rate';
const SKIN = 'skin';
/**
* @var integer
* @var int
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(name="userID", type="integer")
* @ORM\Column(name="id", type="integer")
*/
private $userid;
private $id;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="preferences")
* @ORM\JoinColumn(onDelete="CASCADE")
* @Assert\NotNull()
*/
private $user;
/**
* @var string
*
* @ORM\Column(name="option", type="string", length=255)
* @ORM\Column(name="name", type="string", length=50, nullable=false)
* @Assert\Length(min=2, max=50)
*/
private $option;
private $name;
/**
* @var string
*
* @ORM\Column(name="value", type="string", length=255, nullable=false)
* @ORM\Column(name="value", type="string", length=255, nullable=true)
*/
private $value;
/**
* Set value
*
* @param string $value
*
* @var string
*/
protected $type = TextType::class;
/**
* @var Constraint[]
*/
protected $constraints = [];
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @param int $id
* @return UserPreference
*/
public function setValue($value)
public function setId(int $id): UserPreference
{
$this->value = $value;
$this->id = $id;
return $this;
}
/**
* @return User
*/
public function getUser(): User
{
return $this->user;
}
/**
* @param User $user
* @return UserPreference
*/
public function setUser(User $user): UserPreference
{
$this->user = $user;
return $this;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
* @return UserPreference
*/
public function setName(string $name): UserPreference
{
$this->name = $name;
return $this;
}
/**
* Get value
*
* @return string
*/
public function getValue()
@@ -61,50 +129,64 @@ class UserPreference
}
/**
* Set option
*
* @param string $option
*
* @param string $value
* @return UserPreference
*/
public function setOption($option)
public function setValue(string $value): UserPreference
{
$this->option = $option;
$this->value = $value;
return $this;
}
/**
* Get option
* Sets the form type to edit that setting.
*
* @param string $type
* @return UserPreference
*/
public function setType(string $type)
{
$this->type = $type;
return $this;
}
/**
* @return string
*/
public function getOption()
public function getType()
{
return $this->option;
return $this->type;
}
/**
* Set userid
* Set the constraints which are used for validation of the value.
*
* @param integer $userid
*
* @return UserPreference
* @param Constraint[] $constraints
* @return $this
*/
public function setUserid($userid)
public function setConstraints(array $constraints)
{
$this->userid = $userid;
$this->constraints = $constraints;
return $this;
}
/**
* Get userid
* Adds a constraint which is used for validation of the value.
*
* @return integer
* @param Constraint $constraint
* @return $this
*/
public function getUserid()
public function addConstraint(Constraint $constraint)
{
return $this->userid;
$this->constraints[] = $constraint;
return $this;
}
/**
* @return Constraint[]
*/
public function getConstraints()
{
return $this->constraints;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Event;
use App\Entity\User;
use App\Entity\UserPreference;
use Symfony\Component\EventDispatcher\Event;
/**
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class UserPreferenceEvent extends Event
{
const CONFIGURE = 'app.user_preferences';
/**
* @var User
*/
protected $user;
/**
* @var UserPreference[]
*/
protected $preferences;
/**
* UserPreferenceEvent constructor.
* @param User $user
* @param UserPreference[] $preferences
*/
public function __construct(User $user, array $preferences)
{
$this->user = $user;
$this->preferences = $preferences;
}
/**
* @return User
*/
public function getUser()
{
return $this->user;
}
/**
* @return UserPreference[]
*/
public function getPreferences()
{
return $this->preferences;
}
/**
* @param UserPreference $preference
*/
public function addUserPreference(UserPreference $preference)
{
foreach ($this->preferences as $pref) {
if ($pref->getName() == $preference->getName()) {
throw new \InvalidArgumentException(
'Cannot add preference, a preference with the name "'.$preference->getName().'" is already existing'
);
}
}
$this->preferences[] = $preference;
}
}

View File

@@ -11,6 +11,7 @@
namespace App\EventListener;
use App\Entity\UserPreference;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\LifecycleEventArgs;
@@ -59,11 +60,23 @@ class TimesheetListener implements EventSubscriber
$entity = $args->getObject();
if ($entity instanceof Timesheet) {
$duration = 0;
if ($entity->getEnd() !== null) {
$entity->setDuration($entity->getEnd()->getTimestamp() - $entity->getBegin()->getTimestamp());
}
$duration = $entity->getEnd()->getTimestamp() - $entity->getBegin()->getTimestamp();
$entity->setDuration($duration);
// TODO calculate hourly rate
// TODO allow to set hourly rate on activity, project and customer and prefer these
$hourlyRate = 0;
foreach ($entity->getUser()->getPreferences() as $preference) {
if ($preference->getName() == UserPreference::HOURLY_RATE) {
$hourlyRate = (int) $preference->getValue();
}
}
$rate = $hourlyRate * ($duration / 3600);
$entity->setRate($rate);
}
}
}
}

View File

@@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\EventSubscriber;
use App\Entity\User;
use App\Entity\UserPreference;
use App\Event\UserPreferenceEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Validator\Constraints\Range;
/**
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class UserPreferenceSubscriber implements EventSubscriberInterface
{
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* @var TokenStorageInterface
*/
protected $storage;
/**
* PreferenceService constructor.
* @param EventDispatcherInterface $dispatcher
* @param TokenStorageInterface $storage
*/
public function __construct(EventDispatcherInterface $dispatcher, TokenStorageInterface $storage)
{
$this->eventDispatcher = $dispatcher;
$this->storage = $storage;
}
/**
* @return array
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => ['onKernelEvent', -1],
];
}
/**
* @return UserPreference[]
*/
public function getDefaultPreferences()
{
return [
(new UserPreference())
->setName(UserPreference::HOURLY_RATE)
->setValue(0)
->setType(IntegerType::class)
->addConstraint(new Range(['min' => 0])),
/*
(new UserPreference())
->setName(UserPreference::SKIN)
->setValue('blue')
->setType(SkinType::class),
(new UserPreference())
->setName('language')
->setValue('de')
->setType(LanguageType::class),
*/
];
}
/**
* @param User $user
* @return User
*/
public function setupUserPreferences(User $user)
{
$prefs = [];
foreach ($user->getPreferences() as $preference) {
$prefs[$preference->getName()] = $preference;
}
$event = new UserPreferenceEvent($user, $this->getDefaultPreferences());
$this->eventDispatcher->dispatch(UserPreferenceEvent::CONFIGURE, $event);
foreach ($event->getPreferences() as $preference) {
if (isset($prefs[$preference->getName()])) {
/** @var UserPreference $pref */
$prefs[$preference->getName()]
->setType($preference->getType())
->setConstraints($preference->getConstraints())
;
} else {
$prefs[$preference->getName()] = $preference;
}
}
$user->setPreferences(array_values($prefs));
return $user;
}
/**
* @param KernelEvent $event
*/
public function onKernelEvent(KernelEvent $event): void
{
// Ignore sub-requests
if (!$event->isMasterRequest()) {
return;
}
// ignore events like the toolbar where we do not have a token
if ($this->storage->getToken() === null) {
return;
}
/** @var User $user */
$user = $this->storage->getToken()->getUser();
if (!($user instanceof User)) {
return;
}
$this->setupUserPreferences($user);
}
}

View File

@@ -1,9 +1,9 @@
<?php
/*
* This file is part of the Symfony package.
* This file is part of the Kimai package.
*
* (c) Fabien Potencier <fabien@symfony.com>
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -31,8 +31,8 @@ class LanguageType extends AbstractType
{
$resolver->setDefaults([
'choices' => array(
Intl::getLocaleBundle()->getLocaleName('de', \Locale::getDefault()) => 'de',
Intl::getLocaleBundle()->getLocaleName('en', \Locale::getDefault()) => 'en',
Intl::getLocaleBundle()->getLocaleName('de', 'de') => 'de',
Intl::getLocaleBundle()->getLocaleName('en', 'en') => 'en',
)
]);
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Custom form field type to select the themes skin.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class SkinType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'required' => true,
'choices' => array(
'blue' => 'blue',
'black' => 'black',
'green' => 'green',
'purple' => 'purple',
'red' => 'red',
'yellow' => 'yellow',
'blue-light' => 'blue-light',
'black-light' => 'black-light',
'green-light' => 'green-light',
'purple-light' => 'purple-light',
'red-light' => 'red-light',
'yellow-light' => 'yellow-light',
)
]);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return ChoiceType::class;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Form\Type;
use App\Entity\UserPreference;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Custom form field type to edit a user preference.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class UserPreferenceType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
/** @var UserPreference $preference */
$preference = $event->getData();
if ($preference instanceof UserPreference) {
// prevents unconfigured values from showing up in the form
if ($preference->getType() === null) {
return;
}
$event->getForm()->add('value', $preference->getType(), [
'label' => 'label.' . $preference->getName(),
'constraints' => $preference->getConstraints()
]);
}
}
);
$builder->add('name', HiddenType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => UserPreference::class,
));
}
}

View File

@@ -37,8 +37,8 @@ class UserCreateType extends UserEditType
->add('plainPassword', RepeatedType::class, [
'required' => true,
'type' => PasswordType::class,
'first_options' => array('label' => 'security.label.password'),
'second_options' => array('label' => 'security.label.password_repeat'),
'first_options' => array('label' => 'label.password'),
'second_options' => array('label' => 'label.password_repeat'),
]);
parent::buildForm($builder, $options);

View File

@@ -52,10 +52,6 @@ class UserEditType extends AbstractType
->add('email', TextType::class, [
'label' => 'label.email',
])
// string - length 5
->add('language', LanguageType::class, [
'label' => 'label.language',
])
// boolean
->add('active', YesNoType::class, [
'label' => 'label.active',

View File

@@ -34,8 +34,8 @@ class UserPasswordType extends AbstractType
$builder
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => array('label' => 'security.label.password'),
'second_options' => array('label' => 'security.label.password_repeat'),
'first_options' => array('label' => 'label.password'),
'second_options' => array('label' => 'label.password_repeat'),
])
;
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Form;
use App\Entity\User;
use App\Form\Type\UserPreferenceType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Defines the form used to edit the user preferences.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class UserPreferencesForm extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('preferences', CollectionType::class, [
'entry_type' => UserPreferenceType::class,
'entry_options' => array('label' => false),
'allow_add' => false,
'allow_delete' => false,
'label' => false,
]);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'edit_user_preferences',
]);
}
}

View File

@@ -27,8 +27,20 @@ class UserVoter extends AbstractVoter
const DELETE = 'delete';
const PASSWORD = 'password';
const ROLES = 'roles';
const PREFERENCES = 'preferences';
const VIEW_ALL = 'view_all';
const ALLOWED_ATTRIBUTES = [
self::VIEW,
self::VIEW_ALL,
self::EDIT,
self::CREATE,
self::ROLES,
self::PASSWORD,
self::DELETE,
self::PREFERENCES
];
/**
* @param string $attribute
* @param mixed $subject
@@ -36,10 +48,7 @@ class UserVoter extends AbstractVoter
*/
protected function supports($attribute, $subject)
{
if (!in_array(
$attribute,
[self::VIEW, self::VIEW_ALL, self::EDIT, self::CREATE, self::ROLES, self::PASSWORD, self::DELETE]
)) {
if (!in_array($attribute, self::ALLOWED_ATTRIBUTES)) {
return false;
}
@@ -69,6 +78,7 @@ class UserVoter extends AbstractVoter
return $this->canView($subject, $user, $token);
case self::EDIT:
case self::PASSWORD:
case self::PREFERENCES:
return $this->canEdit($subject, $user, $token);
case self::DELETE:
return $this->canDelete($subject, $user, $token);