]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
WIP
authorThomas Citharel <tcit@tcit.fr>
Tue, 31 Jan 2017 20:13:33 +0000 (21:13 +0100)
committerThomas Citharel <tcit@tcit.fr>
Fri, 23 Jun 2017 07:26:41 +0000 (09:26 +0200)
16 files changed:
src/Wallabag/CoreBundle/Entity/Entry.php
src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
src/Wallabag/GroupBundle/Controller/ManageController.php
src/Wallabag/GroupBundle/Entity/Group.php
src/Wallabag/GroupBundle/Entity/Invitation.php [new file with mode: 0644]
src/Wallabag/GroupBundle/Entity/UserGroup.php [new file with mode: 0644]
src/Wallabag/GroupBundle/Form/GroupType.php
src/Wallabag/GroupBundle/Form/NewGroupType.php
src/Wallabag/GroupBundle/Form/UserGroupType.php [new file with mode: 0644]
src/Wallabag/GroupBundle/Repository/GroupRepository.php [new file with mode: 0644]
src/Wallabag/GroupBundle/Resources/config/services.yml [new file with mode: 0644]
src/Wallabag/GroupBundle/Resources/views/Manage/edit.html.twig
src/Wallabag/GroupBundle/Resources/views/Manage/index.html.twig
src/Wallabag/GroupBundle/Resources/views/Manage/new.html.twig
src/Wallabag/GroupBundle/Service/Sha256Salted.php [new file with mode: 0644]
src/Wallabag/UserBundle/Entity/User.php

index a0503c3918170dfbb9fc8bb29a0e8a4f22f72f4c..84555b3689800623e97a1b7dfff64c7ea0036276 100644 (file)
@@ -233,6 +233,12 @@ class Entry
      */
     private $tags;
 
+    /**
+     * @var ArrayCollection
+     * @ORM\ManyToMany(targetEntity="Wallabag\GroupBundle\Entity\Group", inversedBy="presentations", cascade={"persist"})
+     */
+    private $groupShares;
+
     /*
      * @param User     $user
      */
index beb8f3245a76a325143f49f158b7b50ddc002765..d909cf15c7ea8f5b0b1990b02a1d8587b6f2490f 100644 (file)
@@ -515,8 +515,8 @@ developer:
 
 group:
     page_title: Gestion des groupes
-    new_group: Créer un nouveau group
-    edit_group: Éditer un nouveau groupe
+    new_group: Créer un nouveau groupe
+    edit_group: Éditer le groupe « %group% »
     description: Ici vous pouvez gérer vos groupes (création, mise à jour et suppression)
     list:
         actions: Actions
@@ -531,6 +531,24 @@ group:
         delete: Supprimer
         delete_confirm: Êtes-vous sur ?
         back_to_list: Revenir à la liste
+        role_label: Rôles par défaut
+        access_label: Accès
+        password_label: Mot de passe (si nécessaire)
+    roles:
+        readonly: Lecture seule
+        write: Écriture
+        manage_entries: Gestion des articles
+        manage_users: Gestion des utilisateurs
+        admin: Administrateur
+    access:
+        open: Ouvert
+        request: Sur demande
+        password: Par mot de passe
+        invitation: Sur invitation
+        hidden: Sur invitation et privée
+    tab_menu:
+        public: Groupes publics
+        own: Mes groupes
 
 user:
     page_title: "Gestion des utilisateurs"
index 7015a46504c5af0fc8f3a5a5bdfce010c7508545..94196040856a668c52d27b9699fc408616ed8b2e 100644 (file)
@@ -2,11 +2,20 @@
 
 namespace Wallabag\GroupBundle\Controller;
 
+use Pagerfanta\Adapter\DoctrineORMAdapter;
+use Pagerfanta\Exception\OutOfRangeCurrentPageException;
+use Pagerfanta\Pagerfanta;
+use Strut\StrutBundle\Service\Sha256Salted;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Symfony\Component\HttpFoundation\Response;
 use Wallabag\GroupBundle\Entity\Group;
+use Wallabag\GroupBundle\Entity\UserGroup;
+use Wallabag\GroupBundle\Form\GroupType;
+use Wallabag\GroupBundle\Form\NewGroupType;
+use Wallabag\UserBundle\Entity\User;
 
 /**
  * Group controller.
@@ -14,19 +23,32 @@ use Wallabag\GroupBundle\Entity\Group;
 class ManageController extends Controller
 {
     /**
-     * Lists all Group entities.
+     * Lists all public Group entities.
      *
-     * @Route("/", name="group_index")
+     * @Route("/{page}", name="group_index", defaults={"page" = "1"})
      * @Method("GET")
      */
-    public function indexAction()
+    public function indexAction($page = 1)
     {
         $em = $this->getDoctrine()->getManager();
 
-        $groups = $em->getRepository('WallabagGroupBundle:Group')->findAll();
+        $groups = $em->getRepository('WallabagGroupBundle:Group')->findPublicGroups();
+
+        $pagerAdapter = new DoctrineORMAdapter($groups->getQuery(), true, false);
+        $pagerFanta = new Pagerfanta($pagerAdapter);
+        $pagerFanta->setMaxPerPage(1);
+
+        try {
+            $pagerFanta->setCurrentPage($page);
+        } catch (OutOfRangeCurrentPageException $e) {
+            if ($page > 1) {
+                return $this->redirect($this->generateUrl('group_index', ['page' => $pagerFanta->getNbPages()]), 302);
+            }
+        }
 
         return $this->render('WallabagGroupBundle:Manage:index.html.twig', array(
-            'groups' => $groups,
+            'groups' => $pagerFanta,
+            'currentPage' => $page,
         ));
     }
 
@@ -38,14 +60,26 @@ class ManageController extends Controller
      */
     public function newAction(Request $request)
     {
-        $group = new Group('');
+        $group = new Group();
 
-        $form = $this->createForm('Wallabag\GroupBundle\Form\NewGroupType', $group);
+        $form = $this->createForm(NewGroupType::class, $group);
         $form->handleRequest($request);
 
         if ($form->isSubmitted() && $form->isValid()) {
             $em = $this->getDoctrine()->getManager();
+
+            if ($group->getAcceptSystem() == Group::ACCESS_PASSWORD) {
+                /** @var Sha256Salted $encoder */
+                $encoder = $this->get('sha256salted_encoder');
+                $password = $encoder->encodePassword($group->getPassword(), $this->getParameter('secret'));
+                $group->setPassword($password);
+            }
+
             $em->persist($group);
+
+            $groupUser = new UserGroup($this->getUser(), $group, Group::ROLE_ADMIN);
+            $groupUser->setAccepted(true);
+            $em->persist($groupUser);
             $em->flush();
 
             $this->get('session')->getFlashBag()->add(
@@ -70,12 +104,23 @@ class ManageController extends Controller
      */
     public function editAction(Request $request, Group $group)
     {
+        if ($this->getUser()->getGroupRoleForUser($group) < Group::ROLE_ADMIN) {
+            $this->createAccessDeniedException();
+        }
+
         $deleteForm = $this->createDeleteForm($group);
-        $editForm = $this->createForm('Wallabag\GroupBundle\Form\GroupType', $group);
+        $editForm = $this->createForm(GroupType::class, $group);
         $editForm->handleRequest($request);
 
         if ($editForm->isSubmitted() && $editForm->isValid()) {
             $em = $this->getDoctrine()->getManager();
+
+            if ($group->getAcceptSystem() === Group::ACCESS_PASSWORD) {
+                $encoder = $this->get('sha256salted_encoder');
+                $password = $encoder->encodePassword($group->getPlainPassword(), $this->getParameter('secret'));
+                $group->setPassword($password);
+            }
+
             $em->persist($group);
             $em->flush();
 
@@ -134,4 +179,33 @@ class ManageController extends Controller
             ->getForm()
         ;
     }
+
+    /**
+     * @Route("/group-user-exclude/{group}/{user}", name="group-user-exclude")
+     * @param Group $group
+     * @param User $user
+     * @return Response
+     */
+    public function excludeMemberAction(Group $group, User $user)
+    {
+        $logger = $this->get('logger');
+        $logger->info('User ' . $this->getUser()->getUsername() . ' wants to exclude user ' . $user->getUsername() . ' from group ' . $group->getName());
+
+        if (!$this->getUser()->inGroup($group) || $this->getUser()->getGroupRoleForUser($group) < Group::ROLE_MANAGE_USERS) {
+            $logger->info('User ' . $this->getUser()->getUsername() . ' has not enough rights on group ' . $group->getName() . ' to exclude user ' . $user->getUsername());
+            throw $this->createAccessDeniedException();
+        }
+
+        if ($user->inGroup($group) && $user->getGroupRoleForUser($group) < Group::ROLE_ADMIN) {
+            $em = $this->getDoctrine()->getManager();
+
+            $logger->info('Removing user ' . $this->getUser()->getUsername() . ' from group ' . $group->getName());
+            $em->remove($this->getUser()->getUserGroupFromGroup($group));
+
+            $em->flush();
+
+            return $this->redirectToRoute('group-manage', ['group' => $group->getId()]);
+        }
+        throw $this->createAccessDeniedException();
+    }
 }
index 1381d1eaf34bd37ffee041bf11a8c300e053c64d..31c828377aabd073ca386f88cd00c492b2608501 100644 (file)
@@ -8,11 +8,50 @@ use Doctrine\ORM\Mapping as ORM;
 use Wallabag\UserBundle\Entity\User;
 
 /**
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Wallabag\GroupBundle\Repository\GroupRepository")
  * @ORM\Table(name="`group`")
  */
 class Group extends BaseGroup
 {
+
+    /**
+     * User Roles
+     */
+
+    /** User can only preview presentations */
+    const ROLE_READ_ONLY = 1;
+
+    /** User can create new presentations */
+    const ROLE_WRITE = 2;
+
+    /** User can manage all group presentations */
+    const ROLE_MANAGE_ENTRIES = 3;
+
+    /** User can manage users in the group */
+    const ROLE_MANAGE_USERS = 5;
+
+    /** User can rename and delete the group */
+    const ROLE_ADMIN = 10;
+
+    /**
+     * Group join access
+     */
+
+    /** Any user can join the group */
+    const ACCESS_OPEN = 1;
+
+    /** An user needs to request to join the group */
+    const ACCESS_REQUEST = 2;
+
+    /** An user need the password to access the group */
+    const ACCESS_PASSWORD = 3;
+
+    /** An user needs to be invited to join the group */
+    const ACCESS_INVITATION_ONLY = 4;
+
+    /** An user needs to be invited to join the group, and the group is not publicly listed */
+    const ACCESS_HIDDEN = 10;
+
     /**
      * @ORM\Id
      * @ORM\Column(type="integer")
@@ -21,38 +60,141 @@ class Group extends BaseGroup
     protected $id;
 
     /**
-     * @ORM\ManyToMany(targetEntity="Wallabag\UserBundle\Entity\User", mappedBy="groups", cascade={"persist"})
+     * @ORM\Column(type="integer", options={"default" : 1})
+     */
+    protected $acceptSystem;
+
+    /**
+     * @ORM\Column(type="integer", options={"default" : 2})
+     */
+    protected $defaultRole;
+
+    /**
+     * @ORM\Column(type="string", nullable=true)
+     */
+    protected $password;
+    protected $plainPassword;
+
+    /**
+     * @ORM\ManyToMany(targetEntity="Wallabag\CoreBundle\Entity\Entry", mappedBy="groupShares", cascade={"persist"})
+     */
+    protected $entries;
+
+    /**
+     * @ORM\OneToMany(targetEntity="UserGroup", mappedBy="group", cascade={"persist"})
      */
     protected $users;
 
-    public function getUsers()
+    public function __construct($name = '', array $roles = [])
     {
-        return $this->users ?: $this->users = new ArrayCollection();
+        parent::__construct($name, $roles);
+        $this->defaultRole = self::ROLE_READ_ONLY;
+        $this->acceptSystem = self::ACCESS_REQUEST;
     }
 
-    public function addUser(User $user)
+    /**
+     * @return ArrayCollection
+     */
+    public function getUsers()
     {
-        if (!$this->getUsers()->contains($user)) {
-            $this->getUsers()->add($user);
+        $userObj = new ArrayCollection();
+        foreach ($this->users as $userGroup) {
+            /** @var UserGroup $userGroup */
+            $userObj->add($userGroup->getUser());
         }
+        return $userObj;
+    }
 
-        return $this;
+    /**
+     * @return int
+     */
+    public function getDefaultRole()
+    {
+        return $this->defaultRole;
     }
 
     /**
-     * {@inheritdoc}
+     * @return int
      */
-    public function removeUser(User $user)
+    public function getAcceptSystem()
     {
-        if ($this->getUsers()->contains($user)) {
-            $this->getUsers()->removeElement($user);
-        }
+        return $this->acceptSystem;
+    }
+
+    /**
+     * @param int $acceptSystem
+     */
+    public function setAcceptSystem($acceptSystem)
+    {
+        $this->acceptSystem = $acceptSystem;
+    }
 
-        return $this;
+    /**
+     * @return string
+     */
+    public function getPassword()
+    {
+        return $this->password ?: '';
+    }
+
+    /**
+     * @param string $password
+     */
+    public function setPassword($password)
+    {
+        $this->password = $password;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPlainPassword()
+    {
+        return $this->plainPassword ?: '';
+    }
+
+    /**
+     * @param string $plainPassword
+     */
+    public function setPlainPassword($plainPassword)
+    {
+        $this->plainPassword = $plainPassword;
+    }
+
+    /**
+     * @param int $defaultRole
+     */
+    public function setDefaultRole($defaultRole)
+    {
+        $this->defaultRole = $defaultRole;
     }
 
     public function __toString()
     {
-        return $this->getName();
+        return $this->name;
+    }
+
+    public function getRequests()
+    {
+        $requests = new ArrayCollection();
+        foreach ($this->users as $user) /** @var UserGroup $user */
+        {
+            if (!$user->isAccepted()) {
+                $requests->add($user->getUser());
+            }
+        }
+        return $requests;
+    }
+
+    public function getInvited()
+    {
+        $invited = new ArrayCollection();
+        foreach ($this->users as $userGroup) /** @var UserGroup $userGroup */
+        {
+            if ($userGroup->getInvitation()) {
+                $invited->add($userGroup);
+            }
+        }
+        return $invited;
     }
 }
diff --git a/src/Wallabag/GroupBundle/Entity/Invitation.php b/src/Wallabag/GroupBundle/Entity/Invitation.php
new file mode 100644 (file)
index 0000000..6946bed
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+namespace Wallabag\GroupBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ORM\Entity
+ */
+class Invitation
+{
+    /**
+     * @ORM\Id
+     * @ORM\Column(type="string", length=6)
+     */
+    protected $code;
+
+    /**
+     * @var \DateTime
+     * @ORM\Column(type="datetime")
+     */
+    protected $date;
+
+    /**
+     * @ORM\OneToOne(targetEntity="UserGroup", mappedBy="invitation")
+     */
+    protected $userGroup;
+
+    public function __construct(UserGroup $userGroup)
+    {
+        // generate identifier only once, here a 6 characters length code
+        $this->code = substr(md5(uniqid(rand(), true)), 0, 6);
+        $this->date = new \DateTime();
+        $this->userGroup = $userGroup;
+    }
+
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     * @return \DateTime
+     */
+    public function getDate(): \DateTime
+    {
+        return $this->date;
+    }
+
+    /**
+     * @param \DateTime $date
+     */
+    public function setDate(\DateTime $date)
+    {
+        $this->date = $date;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getUserGroup(): UserGroup
+    {
+        return $this->userGroup;
+    }
+
+    /**
+     * @param mixed $userGroup
+     */
+    public function setUserGroup(UserGroup $userGroup)
+    {
+        $this->userGroup = $userGroup;
+    }
+}
diff --git a/src/Wallabag/GroupBundle/Entity/UserGroup.php b/src/Wallabag/GroupBundle/Entity/UserGroup.php
new file mode 100644 (file)
index 0000000..22d1400
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+
+namespace Wallabag\GroupBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use FOS\UserBundle\Model\GroupInterface;
+use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
+use Wallabag\UserBundle\Entity\User;
+
+/**
+ * @ORM\Entity(repositoryClass="Wallabag\GroupBundle\Repository\UserGroupRepository")
+ * @UniqueEntity({"user_id", "group_id"})
+ * @ORM\Table(name="fos_user_group")
+ */
+class UserGroup
+{
+    /**
+     * @ORM\Column(name="id", type="integer")
+     * @ORM\Id
+     * @ORM\GeneratedValue(strategy="AUTO")
+     */
+    private $id;
+
+    /**
+     * @ORM\Column(name="role", type="integer")
+     */
+    private $role;
+
+    /**
+     * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="userGroups")
+     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
+     */
+    private $user;
+
+    /**
+     * @ORM\ManyToOne(targetEntity="Wallabag\GroupBundle\Entity\Group", inversedBy="users")
+     * @ORM\JoinColumn(name="group_id", referencedColumnName="id")
+     */
+    private $group;
+
+    /**
+     * @ORM\Column(name="accepted", type="boolean", options={"default" : false})
+     */
+    private $accepted;
+
+    /**
+     * @ORM\OneToOne(targetEntity="Wallabag\GroupBundle\Entity\Invitation", inversedBy="userGroup", cascade={"persist", "remove"})
+     * @ORM\JoinColumn(name="invitation", referencedColumnName="code")
+     */
+    protected $invitation;
+
+    /**
+     * UserGroup constructor.
+     * @param User $user
+     * @param Group $group
+     * @param $role
+     */
+    public function __construct(User $user, Group $group, $role, $request = false)
+    {
+        $this->user = $user;
+        $this->group = $group;
+        $this->role = $role;
+        $this->accepted = $request;
+    }
+
+    /**
+     * @return Group
+     */
+    public function getGroup()
+    {
+        return $this->group;
+    }
+
+    /**
+     * @return int
+     */
+    public function getRole()
+    {
+        return $this->role;
+    }
+
+    /**
+     * @return User
+     */
+    public function getUser()
+    {
+        return $this->user;
+    }
+
+    /**
+     * @param int $role
+     * @return UserGroup
+     */
+    public function setRole($role)
+    {
+        $this->role = $role;
+        return $this;
+    }
+
+    /**
+     * @param bool $accepted
+     */
+    public function setAccepted($accepted)
+    {
+        $this->accepted = $accepted;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAccepted()
+    {
+        return $this->accepted;
+    }
+
+    public function setInvitation($invitation)
+    {
+        $this->invitation = $invitation;
+    }
+
+    public function getInvitation()
+    {
+        return $this->invitation;
+    }
+}
index c2ad764bc2f39df7306b3005c115a07b5d02fbab..c708bdb8931dacadb56bd5ef808daab4b3d4abfe 100644 (file)
@@ -3,10 +3,13 @@
 namespace Wallabag\GroupBundle\Form;
 
 use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\PasswordType;
 use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\OptionsResolver\OptionsResolver;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Wallabag\GroupBundle\Entity\Group;
 
 class GroupType extends AbstractType
 {
@@ -21,6 +24,31 @@ class GroupType extends AbstractType
                 'required' => false,
                 'label' => 'group.form.name_label',
             ])
+            ->add('defaultRole', ChoiceType::class, [
+                'label' => 'group.form.role_label',
+                'choices' => [
+                    'group.roles.readonly' => Group::ROLE_READ_ONLY,
+                    'group.roles.write' => Group::ROLE_WRITE,
+                    'group.roles.manage_entries' => Group::ROLE_MANAGE_ENTRIES,
+                    'group.roles.manage_users' => Group::ROLE_MANAGE_USERS,
+                    'group.roles.admin' => Group::ROLE_ADMIN,
+                ],
+            ])
+            ->add('acceptSystem', ChoiceType::class, [
+                'label' => 'group.form.access_label',
+                'choices' => [
+                    'group.access.open' => Group::ACCESS_OPEN,
+                    'group.access.request' => Group::ACCESS_REQUEST,
+                    'group.access.password' => Group::ACCESS_PASSWORD,
+                    'group.access.invitation' => Group::ACCESS_INVITATION_ONLY,
+                    'group.access.hidden' => Group::ACCESS_HIDDEN,
+                ],
+            ])
+            ->add('plainPassword', PasswordType::class, [
+                'label' => 'group.form.password_label',
+                'required' => false,
+                'attr' => ['autocomplete' => 'off'],
+            ])
             ->add('save', SubmitType::class, [
                 'label' => 'group.form.save',
             ])
index 29b20fe089f375d97a719b709088fb3885edb6a6..55eebc46d76ff7e0298018bed9bdaee0cc5ffa25 100644 (file)
@@ -3,10 +3,13 @@
 namespace Wallabag\GroupBundle\Form;
 
 use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\PasswordType;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\OptionsResolver\OptionsResolver;
+use Wallabag\GroupBundle\Entity\Group;
 
 class NewGroupType extends AbstractType
 {
@@ -17,6 +20,31 @@ class NewGroupType extends AbstractType
                 'required' => true,
                 'label' => 'group.form.name_label',
             ])
+            ->add('defaultRole', ChoiceType::class, [
+                'label' => 'group.form.role_label',
+                'choices' => [
+                    'group.roles.readonly' => Group::ROLE_READ_ONLY,
+                    'group.roles.write' => Group::ROLE_WRITE,
+                    'group.roles.manage_entries' => Group::ROLE_MANAGE_ENTRIES,
+                    'group.roles.manage_users' => Group::ROLE_MANAGE_USERS,
+                    'group.roles.admin' => Group::ROLE_ADMIN,
+                ],
+            ])
+            ->add('acceptSystem', ChoiceType::class, [
+                'label' => 'group.form.access_label',
+                'choices' => [
+                    'group.access.open' => Group::ACCESS_OPEN,
+                    'group.access.request' => Group::ACCESS_REQUEST,
+                    'group.access.password' => Group::ACCESS_PASSWORD,
+                    'group.access.invitation' => Group::ACCESS_INVITATION_ONLY,
+                    'group.access.hidden' => Group::ACCESS_HIDDEN,
+                ],
+            ])
+            ->add('plainPassword', PasswordType::class, [
+                'label' => 'group.form.password_label',
+                'required' => false,
+                'attr' => ['autocomplete' => 'off'],
+            ])
             ->add('save', SubmitType::class, [
                 'label' => 'group.form.save',
             ])
diff --git a/src/Wallabag/GroupBundle/Form/UserGroupType.php b/src/Wallabag/GroupBundle/Form/UserGroupType.php
new file mode 100644 (file)
index 0000000..e8f814e
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Wallabag\GroupBundle\Form\Type;
+
+use Wallabag\GroupBundle\Entity\Group;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+
+class UserGroupType extends AbstractType
+{
+    /**
+     * @param FormBuilderInterface $builder
+     * @param array                $options
+     */
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $builder
+            ->add('role', ChoiceType::class, [
+                'label' => 'group.edit_user.role',
+                'choices' => [
+                    'group.roles.readonly' => Group::ROLE_READ_ONLY,
+                    'group.roles.write' => Group::ROLE_WRITE,
+                    'group.roles.manage_prez' => Group::ROLE_MANAGE_PREZ,
+                    'group.roles.manage_users' => Group::ROLE_MANAGE_USERS,
+                    'group.roles.admin' => Group::ROLE_ADMIN,
+                ],
+            ])
+            ->add('save', SubmitType::class, [
+                'label' => 'user.form.save',
+            ])
+        ;
+    }
+
+    /**
+     * @param OptionsResolver $resolver
+     */
+    public function configureOptions(OptionsResolver $resolver)
+    {
+        $resolver->setDefaults(array(
+            'data_class' => 'Strut\StrutBundle\Entity\UserGroup',
+        ));
+    }
+}
diff --git a/src/Wallabag/GroupBundle/Repository/GroupRepository.php b/src/Wallabag/GroupBundle/Repository/GroupRepository.php
new file mode 100644 (file)
index 0000000..47340d0
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace Wallabag\GroupBundle\Repository;
+
+use Doctrine\ORM\EntityRepository;
+use Doctrine\ORM\QueryBuilder;
+use Wallabag\UserBundle\Entity\User;
+
+class GroupRepository extends EntityRepository
+{
+    /**
+     * Return a query builder to used by other getBuilderFor* method.
+     *
+     * @return QueryBuilder
+     */
+    public function getBuilder()
+    {
+        return $this->createQueryBuilder('g');
+    }
+
+    public function findPublicGroups()
+    {
+        return $this->getBuilder()
+            ->where('g.acceptSystem < 10');
+    }
+
+    public function findGroupsByUser(User $user)
+    {
+        return $this->getBuilder()
+            ->join('Wallabag\GroupBundle\Entity\UserGroup', 'u', 'WITH', 'u.group = g.id')
+            ->where('u.user = :user')->setParameter(':user', $user->getId());
+    }
+}
diff --git a/src/Wallabag/GroupBundle/Resources/config/services.yml b/src/Wallabag/GroupBundle/Resources/config/services.yml
new file mode 100644 (file)
index 0000000..76e8427
--- /dev/null
@@ -0,0 +1,3 @@
+services:
+    sha256salted_encoder:
+          class: Strut\StrutBundle\Service\Sha256Salted
\ No newline at end of file
index 7de68c353587d0101365e1c9735b99e24a65c79c..c12e8625adbba922fc122d383315d62e13215e05 100644 (file)
@@ -1,6 +1,6 @@
 {% extends "WallabagCoreBundle::layout.html.twig" %}
 
-{% block title %}{{ 'group.page_title'|trans }}{% endblock %}
+{% block title %}{{ 'group.page_title' | trans }}{% endblock %}
 
 {% block content %}
 
@@ -9,7 +9,7 @@
             <div class="card-panel">
                 <div class="row">
                     <div class="input-field col s12">
-                        <h4>{{ 'group.edit_group'|trans }}</h4>
+                        <h4>{{ 'group.edit_group'|trans({'%group%': group.name }) }}</h4>
 
                         <div id="set6" class="col s12">
                             {{ form_start(edit_form) }}
                                     </div>
                                 </div>
 
+                                <div class="row">
+                                    <div class="input-field col s12">
+                                        {{ form_label(edit_form.defaultRole) }}
+                                        {{ form_errors(edit_form.defaultRole) }}
+                                        {{ form_widget(edit_form.defaultRole) }}
+                                    </div>
+                                </div>
+
+                                <div class="row">
+                                    <div class="input-field col s12">
+                                        {{ form_label(edit_form.acceptSystem) }}
+                                        {{ form_errors(edit_form.acceptSystem) }}
+                                        {{ form_widget(edit_form.acceptSystem) }}
+                                    </div>
+                                </div>
+
+                                <div class="row">
+                                    <div class="input-field col s12">
+                                        {{ form_label(edit_form.plainPassword) }}
+                                        {{ form_errors(edit_form.plainPassword) }}
+                                        {{ form_widget(edit_form.plainPassword) }}
+                                    </div>
+                                </div>
+
                                 <br/>
 
                                 {{ form_widget(edit_form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
index 58af3a756664667c68c76f62c3f6d61da28562ba..01466e5200db7294802e7f88b2d4cd763eaf8695 100644 (file)
@@ -37,6 +37,9 @@
                         <p>
                             <a href="{{ path('group_new') }}" class="waves-effect waves-light btn">{{ 'group.list.create_new_one'|trans }}</a>
                         </p>
+                        {% if groups.getNbPages > 1 %}
+                            {{ pagerfanta(groups, 'twitter_bootstrap_translated', {'proximity': 1}) }}
+                        {% endif %}
                     </div>
                 </div>
             </div>
index 3f1c2ad51bd315e8252c89bc9d61918da4a1c4c7..cb009b5dd8ec7e5e6cbcb047e30a3476b990ef9a 100644 (file)
                                     </div>
                                 </div>
 
+                                <div class="row">
+                                    <div class="input-field col s12">
+                                        {{ form_label(form.defaultRole) }}
+                                        {{ form_errors(form.defaultRole) }}
+                                        {{ form_widget(form.defaultRole) }}
+                                    </div>
+                                </div>
+
+                                <div class="row">
+                                    <div class="input-field col s12">
+                                        {{ form_label(form.acceptSystem) }}
+                                        {{ form_errors(form.acceptSystem) }}
+                                        {{ form_widget(form.acceptSystem) }}
+                                    </div>
+                                </div>
+
+                                <div class="row">
+                                    <div class="input-field col s12">
+                                        {{ form_label(form.plainPassword) }}
+                                        {{ form_errors(form.plainPassword) }}
+                                        {{ form_widget(form.plainPassword) }}
+                                    </div>
+                                </div>
+
                                 {{ form_widget(form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
                                 {{ form_rest(form) }}
                             </form>
diff --git a/src/Wallabag/GroupBundle/Service/Sha256Salted.php b/src/Wallabag/GroupBundle/Service/Sha256Salted.php
new file mode 100644 (file)
index 0000000..c72486a
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+namespace Strut\StrutBundle\Service;
+
+use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
+
+class Sha256Salted implements PasswordEncoderInterface
+{
+    public function encodePassword($raw, $salt)
+    {
+        return hash('sha256', $salt . $raw); // Custom function for password encrypt
+    }
+
+    public function isPasswordValid($encoded, $raw, $salt)
+    {
+        return $encoded === $this->encodePassword($raw, $salt);
+    }
+}
index ff658ca5f70f2d0e048c8afefe2c3567c3378b4a..2b73e344a75aea4038df0a648474e0254ac3ac45 100644 (file)
@@ -15,6 +15,8 @@ use Symfony\Component\Security\Core\User\UserInterface;
 use Wallabag\ApiBundle\Entity\Client;
 use Wallabag\CoreBundle\Entity\Config;
 use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\GroupBundle\Entity\Group;
+use Wallabag\GroupBundle\Entity\UserGroup;
 
 /**
  * User.
@@ -98,13 +100,11 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
     private $authCode;
 
     /**
-     * @ORM\ManyToMany(targetEntity="Wallabag\GroupBundle\Entity\Group", inversedBy="users", cascade={"persist"})
-     * @ORM\JoinTable(name="user_group",
-     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
-     *      inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")}
-     * )
+     * @var ArrayCollection
+     *
+     * @ORM\OneToMany(targetEntity="Wallabag\GroupBundle\Entity\UserGroup", mappedBy="user", cascade={"persist", "remove"})
      */
-    protected $groups;
+    protected $userGroups;
 
     /**
      * @var bool Enabled yes/no
@@ -136,6 +136,7 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
     {
         parent::__construct();
         $this->entries = new ArrayCollection();
+        $this->userGroups = new ArrayCollection();
         $this->roles = ['ROLE_USER'];
     }
 
@@ -323,11 +324,65 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
     }
 
     /**
-     * @param string $name
+     * @param Group $group
+     * @return UserGroup
+     */
+    public function getUserGroupFromGroup(Group $group)
+    {
+        foreach ($this->userGroups as $userGroup) {
+            if ($userGroup->getGroup() == $group) {
+                return $userGroup;
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * @param Group $group
+     * @param $role
+     */
+    public function setGroupRole(Group $group, $role)
+    {
+        if ($userGroup = $this->getUserGroupFromGroup($group)) {
+            $userGroup->setRole($role);
+        }
+    }
+
+    /**
+     * @param Group $group
+     * @return int
+     */
+    public function getGroupRoleForUser(Group $group)
+    {
+        if ($userGroup = $this->getUserGroupFromGroup($group)) {
+            return $userGroup->getRole();
+        }
+        return 0;
+    }
+
+    /**
+     * @param Group $group
      * @return bool
      */
-    public function hasGroup($name = '')
+    public function inGroup(Group $group)
     {
-        return in_array($name, $this->getGroupNames());
+        if ($group::ACCESS_REQUEST === $group->getAcceptSystem()) {
+            $userGroup = $this->getUserGroupFromGroup($group);
+            return $userGroup->isAccepted();
+        }
+        return null !== $this->getUserGroupFromGroup($group);
+    }
+
+    /**
+     * @return ArrayCollection<Group>
+     */
+    public function getGroups()
+    {
+        $groups = new ArrayCollection();
+        foreach ($this->userGroups as $userGroup) {
+            $groups->add($userGroup->getGroup());
+        }
+        return $groups;
     }
 }