diff --git a/app/Resources/translations/validators.de.xliff b/app/Resources/translations/validators.de.xliff index 6acda37c..9b943504 100644 --- a/app/Resources/translations/validators.de.xliff +++ b/app/Resources/translations/validators.de.xliff @@ -1,35 +1,12 @@ - + - - post.blank_summary - Gib deinem Beitrag eine Zusammenfassung! - - - post.blank_content - Dein Beitrag sollte einen Inhalt haben! - - - post.too_short_content - Der Beitragsinhalt ist zu kurz (mindestens {{ limit }} Zeichen) - - - comment.blank - Bitte gib einen Kommentar ein! - - - comment.too_short - Der Kommentar ist zu kurz (mindestens {{ limit }} Zeichen) - - - comment.too_long - Der Kommentar ist zu lang (maximal {{ limit }} Zeichen) - - - comment.is_spam - Der Inhalt des Kommentars wird als Spam eingestuft. - + + This value is not a valid role. + Dieser Wert ist keine gültige Rolle. + + diff --git a/app/Resources/views/admin/user.html.twig b/app/Resources/views/admin/user.html.twig index 31a3a081..468ca3ac 100644 --- a/app/Resources/views/admin/user.html.twig +++ b/app/Resources/views/admin/user.html.twig @@ -17,17 +17,21 @@ 'label.active': '', 'label.roles': '', 'label.actions': '', - }) }} + }, null, {'plus-square': path('admin_user_create')}) }} {% for entry in entries %} {{ entry.id }} - {{ entry.alias }} + {{ entry.alias|default(entry.username) }} {{ entry.username }} {{ entry.email }} {{ entry.title }} {{ widgets.label_visible(entry.active) }} - {{ entry.roles|join(',')|trans }} + + {% for role in entry.roles %} + {{ widgets.label_role(role) }} + {% endfor %} + {{ widgets.button_group({ 'edit': path('user_profile', {'username' : entry.username}), diff --git a/app/Resources/views/admin/user_edit.html.twig b/app/Resources/views/admin/user_edit.html.twig new file mode 100644 index 00000000..5eadabc2 --- /dev/null +++ b/app/Resources/views/admin/user_edit.html.twig @@ -0,0 +1,12 @@ +{% extends 'base.html.twig' %} + +{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %} +{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %} + +{% block main %} + {{ include('default/_form.html.twig', { + 'title': 'create'|trans, + 'form': form, + 'back': path('admin_user') + }) }} +{% endblock %} diff --git a/app/Resources/views/macros/widgets.html.twig b/app/Resources/views/macros/widgets.html.twig index a881e756..c9d46355 100644 --- a/app/Resources/views/macros/widgets.html.twig +++ b/app/Resources/views/macros/widgets.html.twig @@ -12,6 +12,11 @@ {% endif %} {% endmacro %} +{% macro label_role(role) %} + {% import _self as macro %} + {{ macro.label(role, 'primary') }} +{% endmacro %} + {% macro label_project(project) %} {% import _self as macro %} {{ macro.label(project.name, 'primary') }} diff --git a/src/AppBundle/Controller/Admin/UserController.php b/src/AppBundle/Controller/Admin/UserController.php index 5a6b9f01..0e94d10c 100644 --- a/src/AppBundle/Controller/Admin/UserController.php +++ b/src/AppBundle/Controller/Admin/UserController.php @@ -11,13 +11,15 @@ namespace AppBundle\Controller\Admin; +use AppBundle\Controller\AbstractController; use AppBundle\Entity\User; +use AppBundle\Form\UserCreateType; use Pagerfanta\Pagerfanta; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; +use Symfony\Component\HttpFoundation\Request; /** * Controller used to manage users in the admin part of the site. @@ -27,7 +29,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; * * @author Kevin Papst */ -class UserController extends Controller +class UserController extends AbstractController { /** * @Route("/", defaults={"page": 1}, name="admin_user") @@ -42,4 +44,58 @@ class UserController extends Controller return $this->render('admin/user.html.twig', ['entries' => $entries]); } + + /** + * @Route("/create", name="admin_user_create") + * @Method({"GET", "POST"}) + */ + public function createAction(Request $request) + { + $user = new User(); + $editForm = $this->createEditForm($user); + + $editForm->handleRequest($request); + + if ($editForm->isSubmitted() && $editForm->isValid()) { + $password = $this->get('security.password_encoder') + ->encodePassword($user, $user->getPlainPassword()); + $user->setPassword($password); + $user->setRoles([User::DEFAULT_ROLE]); + + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($user); + $entityManager->flush(); + + $this->flashSuccess('action.updated_successfully'); + + return $this->redirectToRoute( + 'user_profile_edit', ['username' => $user->getUsername()] + ); + } + + return $this->render( + 'admin/user_edit.html.twig', + [ + 'user' => $user, + 'form' => $editForm->createView() + ] + ); + + } + + /** + * @param User $user + * @return \Symfony\Component\Form\FormInterface + */ + private function createEditForm(User $user) + { + return $this->createForm( + UserCreateType::class, + $user, + [ + 'action' => $this->generateUrl('admin_user_create'), + 'method' => 'POST' + ] + ); + } } diff --git a/src/AppBundle/Entity/User.php b/src/AppBundle/Entity/User.php index 110334dc..028441cf 100644 --- a/src/AppBundle/Entity/User.php +++ b/src/AppBundle/Entity/User.php @@ -2,19 +2,28 @@ namespace AppBundle\Entity; +use AppBundle\Validator\Constraints as KimaiAssert; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * User * * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository") - * @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"})}) + * @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"}), @ORM\UniqueConstraint(name="mail", columns={"mail"})}) + * @UniqueEntity("username") + * @UniqueEntity("email") + * + * @author Kevin Papst */ class User implements UserInterface { + const DEFAULT_ROLE = 'ROLE_USER'; + const DEFAULT_LANGUAGE = 'de'; + /** * @var int * @@ -27,7 +36,7 @@ class User implements UserInterface /** * @var string * - * @ORM\Column(name="name", type="string", length=160, nullable=false, unique=true) + * @ORM\Column(name="name", type="string", length=60, nullable=false, unique=true) * @Assert\NotBlank() * @Assert\Length(min=6, max=160) */ @@ -60,7 +69,8 @@ class User implements UserInterface /** * @var string * - * @ORM\Column(name="alias", type="string", length=160, nullable=true) + * @ORM\Column(name="alias", type="string", length=60, nullable=true) + * @Assert\Length(max=160) */ private $alias; @@ -68,13 +78,15 @@ class User implements UserInterface * @var string * * @ORM\Column(name="language", type="string", length=5, nullable=true) + * @Assert\Language() */ - private $language = 'de'; + private $language = self::DEFAULT_LANGUAGE; /** * @var boolean * * @ORM\Column(name="active", type="boolean", nullable=false) + * @Assert\NotNull() */ private $active = true; @@ -103,6 +115,7 @@ class User implements UserInterface * @var string[] * * @ORM\Column(type="json_array") + * @KimaiAssert\Role() */ private $roles = []; @@ -166,7 +179,7 @@ class User implements UserInterface */ public function setActive($active) { - $this->active = $active; + $this->active = (bool) $active; return $this; } @@ -305,10 +318,12 @@ class User implements UserInterface /** * @param string $language + * @return $this */ public function setLanguage($language) { $this->language = $language; + return $this; } /** diff --git a/src/AppBundle/Form/UserCreateType.php b/src/AppBundle/Form/UserCreateType.php new file mode 100644 index 00000000..236a58f9 --- /dev/null +++ b/src/AppBundle/Form/UserCreateType.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace AppBundle\Form; + +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\RepeatedType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Defines the form used to create Users. + * + * @author Kevin Papst + */ +class UserCreateType extends UserEditType +{ + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('username', null, [ + 'label' => 'label.username', + 'required' => true + ]) + ->add('plainPassword', RepeatedType::class, [ + 'required' => true, + 'type' => PasswordType::class, + 'first_options' => array('label' => 'security.label.password'), + 'second_options' => array('label' => 'security.label.password_repeat'), + ]); + + parent::buildForm($builder, $options); + } + + /** + * {@inheritdoc} + */ + public function __configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'class' => 'AppBundle:User', + ]); + } +} diff --git a/src/AppBundle/Validator/Constraints/Role.php b/src/AppBundle/Validator/Constraints/Role.php new file mode 100644 index 00000000..5957a935 --- /dev/null +++ b/src/AppBundle/Validator/Constraints/Role.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace AppBundle\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Kevin Papst + */ +class Role extends Constraint +{ + const ROLE_ERROR = 'xd5hffg-dsfef3-426a-83d7-1f2d33hs5d84'; + + protected static $errorNames = array( + self::ROLE_ERROR => 'ROLE_ERROR', + ); + + public $message = 'This value is not a valid role.'; +} diff --git a/src/AppBundle/Validator/Constraints/RoleValidator.php b/src/AppBundle/Validator/Constraints/RoleValidator.php new file mode 100644 index 00000000..1ad369a8 --- /dev/null +++ b/src/AppBundle/Validator/Constraints/RoleValidator.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace AppBundle\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Kevin Papst + */ +class RoleValidator extends ConstraintValidator +{ + + protected $allowedRoles = ['ROLE_CUSTOMER', 'ROLE_USER', 'ROLE_TEAMLEAD', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN']; + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Role) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Role'); + } + + $roles = $value; + + if (!is_array($roles)) { + $roles = [$roles]; + } + + foreach($roles as $role) { + if (!is_string($role) || !in_array($role, $this->allowedRoles)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($role)) + ->setCode(Role::ROLE_ERROR) + ->addViolation(); + } + } + } +}