- 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:
@@ -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))
|
||||
;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
76
src/Event/UserPreferenceEvent.php
Normal file
76
src/Event/UserPreferenceEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
141
src/EventSubscriber/UserPreferenceSubscriber.php
Normal file
141
src/EventSubscriber/UserPreferenceSubscriber.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
57
src/Form/Type/SkinType.php
Normal file
57
src/Form/Type/SkinType.php
Normal 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;
|
||||
}
|
||||
}
|
||||
64
src/Form/Type/UserPreferenceType.php
Normal file
64
src/Form/Type/UserPreferenceType.php
Normal 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,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'),
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
56
src/Form/UserPreferencesForm.php
Normal file
56
src/Form/UserPreferencesForm.php
Normal 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',
|
||||
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user