├── .gitignore ├── Resources ├── config │ ├── routing.yml │ ├── doctrine │ │ └── Project.orm.yml │ ├── routing │ │ └── project.yml │ └── services.yml ├── public │ └── css │ │ ├── project-show-sidebar.css │ │ └── project-table.css ├── translations │ ├── validators.sv.yml │ ├── validators.en.yml │ ├── messages.en.yml │ └── messages.sv.yml └── views │ ├── Project │ ├── edit.html.twig │ ├── new.html.twig │ ├── base.html.twig │ ├── delete.html.twig │ ├── index.html.twig │ ├── sidebar.html.twig │ ├── table.html.twig │ └── show.html.twig │ ├── base.html.twig │ └── Macro │ └── tableMacro.html.twig ├── HappyrUserProjectBundle.php ├── Model ├── ProjectMemberInterface.php └── ProjectObjectInterface.php ├── Event ├── ProjectEvent.php ├── JoinRequestEvent.php ├── ProjectObjectEvent.php └── BaseEvent.php ├── .travis.yml ├── DependencyInjection ├── Configuration.php └── HappyrUserProjectExtension.php ├── composer.json ├── Tests └── Factory │ └── ProjectFactoryTest.php ├── Form └── ProjectType.php ├── phpunit.xml.dist ├── Manager ├── SecurityManager.php ├── SecureProjectManager.php ├── BaseAclManager.php ├── ProjectManager.php └── PermissionManager.php ├── Entity ├── ProjectRepository.php └── Project.php ├── README.md ├── Factory └── ProjectFactory.php ├── Service └── ProjectService.php └── Controller └── ProjectController.php /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | phpunit.xml 3 | composer.lock 4 | vendor 5 | -------------------------------------------------------------------------------- /Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | happyr_user_project_project: 2 | resource: "@HappyrUserProjectBundle/Resources/config/routing/project.yml" 3 | prefix: /projects 4 | -------------------------------------------------------------------------------- /Resources/public/css/project-show-sidebar.css: -------------------------------------------------------------------------------- 1 | #project-show-sidebar-extend h3 { 2 | margin-top: 0.5em; 3 | } 4 | 5 | #project-show-sidebar-extend section { 6 | margin: 0.5em 0 1.5em; 7 | 8 | } -------------------------------------------------------------------------------- /Resources/translations/validators.sv.yml: -------------------------------------------------------------------------------- 1 | happyr.user.project.name: 2 | short: 'Projektets namn är för kort' 3 | blank: 'Du måste ange ett namn på projektet' 4 | long: 'Projektets namn är för långt' 5 | -------------------------------------------------------------------------------- /Resources/translations/validators.en.yml: -------------------------------------------------------------------------------- 1 | happyr.user.project.name: 2 | long: 'The name of the project is too long' 3 | short: 'The name of the project is too short' 4 | blank: 'You have to write a name of the project' 5 | -------------------------------------------------------------------------------- /Resources/views/Project/edit.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'HappyrUserProjectBundle:Project:base.html.twig' %} 2 | 3 | 4 | {% block content -%} 5 |

{% trans %}happyr.user.project.project.edit.heading{% endtrans %}

6 | 7 | {{ form(form) }} 8 | 9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /HappyrUserProjectBundle.php: -------------------------------------------------------------------------------- 1 | {% trans %}happyr.user.project.project.new.heading{% endtrans %} 5 |

{% trans %}happyr.user.project.project.new.paragraph{% endtrans %}

6 | 7 | {{ form(form) }} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /Resources/views/Project/base.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@HappyrUserProject/base.html.twig' %} 2 | 3 | 4 | {% block headercontent %} 5 |

{% trans %}happyr.user.project.project.base.title{% endtrans %}

6 | {% endblock %} 7 | 8 | {% block sidebar %} 9 | {% include "HappyrUserProjectBundle:Project:sidebar.html.twig" %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /Model/ProjectMemberInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | {% block sidebar %} 11 | 12 | {% endblock %} 13 |
14 |
15 | {% block content %} 16 | 17 | {% endblock %} 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Resources/views/Project/delete.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'HappyrUserProjectBundle:Project:base.html.twig' %} 2 | 3 | {% block content -%} 4 |

{% trans %}happyr.user.project.project.delete.heading{% endtrans %}

5 |

{% trans %}happyr.user.project.project.delete.paragraph{% endtrans %}

6 |

{% trans with {"%%project%%":project.name} %}happyr.user.project.project.delete.paragraph2{% endtrans %}

7 | 8 | {{ form(form) }} 9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /Event/ProjectEvent.php: -------------------------------------------------------------------------------- 1 | root('happyr_user_project'); 20 | 21 | return $treeBuilder; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Resources/views/Project/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'HappyrUserProjectBundle:Project:base.html.twig' %} 2 | 3 | {% block content -%} 4 |

{% trans %}happyr.user.project.project.index.heading{% endtrans %}

5 |

{% trans %}happyr.user.project.project.index.paragraph{% endtrans %}

6 | 7 |
8 |

{% trans %}happyr.user.project.project.index.my_projects{% endtrans %}

9 | 10 | {% include "HappyrUserProjectBundle:Project:table.html.twig" with {projects:myProjects, requestToJoin:false} %} 11 |
12 |
13 |

{% trans %}happyr.user.project.project.index.company_projects{% endtrans %}

14 | 15 | {% include "HappyrUserProjectBundle:Project:table.html.twig" with {requestToJoin:true} %} 16 |
17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /Resources/views/Macro/tableMacro.html.twig: -------------------------------------------------------------------------------- 1 | {% macro projectUserPermission(project, user) %} 2 | {% spaceless %} 3 | {% set options=static('Happyr\\UserProjectBundle\\Manager\\PermissionManager', 'validMasks') %} 4 | 16 | {% endspaceless %} 17 | {% endmacro %} 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happyr/user-project-bundle", 3 | "type": "symfony-bundle", 4 | "description": "If you have multiple users that will share access to one or more objects.", 5 | "keywords": ["User management", "Project"], 6 | "homepage": "http://developer.happyr.com/symfony2-bundles/happyr-user-project-bundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Tobias Nyholm", 11 | "email": "tobias@happyr.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.2", 16 | "symfony/security": "~2.3" 17 | }, 18 | "require-dev": { 19 | "mockery/mockery": "~0.9.4" 20 | }, 21 | "autoload": { 22 | "psr-0": { 23 | "Happyr\\UserProjectBundle": "" 24 | } 25 | }, 26 | "target-dir": "Happyr/UserProjectBundle" 27 | 28 | } 29 | -------------------------------------------------------------------------------- /DependencyInjection/HappyrUserProjectExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 24 | 25 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 26 | $loader->load('services.yml'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/Factory/ProjectFactoryTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('getId')->andReturn('4711') 19 | ->getMock(); 20 | 21 | $project = m::mock('Happyr\UserProjectBundle\Entity\Project'); 22 | $project 23 | ->shouldReceive('setName')->with('_private_4711')->andReturn($project) 24 | ->shouldReceive('setPublic')->with(false)->andReturn($project) 25 | ->shouldReceive('addUser')->with($user)->andReturn($project); 26 | 27 | $factory = new ProjectFactory( 28 | m::mock('Doctrine\ORM\EntityManagerInterface'), 29 | m::mock('Happyr\UserProjectBundle\Manager\PermissionManager') 30 | ); 31 | 32 | $factory->makePrivate($project, $user); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Project.orm.yml: -------------------------------------------------------------------------------- 1 | Happyr\UserProjectBundle\Entity\Project: 2 | type: entity 3 | table: HappyrUserProject 4 | repositoryClass: Happyr\UserProjectBundle\Entity\ProjectRepository 5 | lifecycleCallbacks: 6 | preUpdate: [updateUpdatedAt] 7 | oneToMany: 8 | objects: 9 | targetEntity: Happyr\UserProjectBundle\Model\ProjectObjectInterface 10 | mappedBy: project 11 | cascade: [persist] 12 | manyToMany: 13 | users: 14 | targetEntity: Happyr\UserProjectBundle\Model\ProjectMemberInterface 15 | joinTable: 16 | name: HappyrUserProject_Users 17 | joinColumns: 18 | project_id: 19 | referencedColumnName: id 20 | inverseJoinColumns: 21 | user_id: 22 | referencedColumnName: id 23 | 24 | id: 25 | id: 26 | type: integer 27 | generator: {strategy: AUTO} 28 | fields: 29 | public: 30 | type: boolean 31 | permissions: 32 | type: array 33 | name: 34 | type: string 35 | length: 255 36 | description: 37 | type: text 38 | nullable: true 39 | createdAt: 40 | type: datetime 41 | updatedAt: 42 | type: datetime -------------------------------------------------------------------------------- /Resources/views/Project/sidebar.html.twig: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /Form/ProjectType.php: -------------------------------------------------------------------------------- 1 | add('name') 24 | ->add( 25 | 'description', 26 | 'textarea', 27 | array( 28 | 'required' => false, 29 | ) 30 | ); 31 | } 32 | 33 | /** 34 | * @param OptionsResolverInterface $resolver 35 | */ 36 | public function setDefaultOptions(OptionsResolverInterface $resolver) 37 | { 38 | $resolver->setDefaults( 39 | array( 40 | 'data_class' => 'Happyr\UserProjectBundle\Entity\Project', 41 | ) 42 | ); 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getName() 49 | { 50 | return 'happyr_user_project_project_form'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Resources/config/routing/project.yml: -------------------------------------------------------------------------------- 1 | happyr_user_project_project_index: 2 | path: / 3 | defaults: { _controller: HappyrUserProjectBundle:Project:index } 4 | methods: [GET] 5 | 6 | happyr_user_project_project_show: 7 | path: /{id} 8 | defaults: { _controller: HappyrUserProjectBundle:Project:show } 9 | methods: [GET] 10 | requirements: 11 | id: \d+ 12 | 13 | happyr_user_project_project_leave: 14 | path: /{id}/leave 15 | defaults: { _controller: HappyrUserProjectBundle:Project:leave } 16 | methods: [GET] 17 | requirements: 18 | id: \d+ 19 | 20 | happyr_user_project_project_create: 21 | path: /new 22 | defaults: { _controller: HappyrUserProjectBundle:Project:new } 23 | methods: [GET, POST] 24 | 25 | happyr_user_project_project_edit: 26 | path: /{id}/edit 27 | defaults: { _controller: HappyrUserProjectBundle:Project:edit } 28 | methods: [GET, POST] 29 | requirements: 30 | id: \d+ 31 | 32 | happyr_user_project_project_delete: 33 | path: /{id}/delete 34 | defaults: { _controller: HappyrUserProjectBundle:Project:delete } 35 | methods: [GET, POST] 36 | requirements: 37 | id: \d+ 38 | 39 | happyr_user_project_project_join_request: 40 | path: /{id}/join-request 41 | defaults: { _controller: HappyrUserProjectBundle:Project:joinRequest } 42 | methods: [GET] 43 | requirements: 44 | id: \d+ 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Event/JoinRequestEvent.php: -------------------------------------------------------------------------------- 1 | administrators = $admins; 35 | 36 | parent::__construct($project, $user); 37 | } 38 | 39 | /** 40 | * @param array $user 41 | * 42 | * @return $this 43 | */ 44 | public function setAdministrators(array $admins) 45 | { 46 | $this->administrators = $admins; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return array 53 | */ 54 | public function getAdministrators() 55 | { 56 | return $this->administrators; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Event/ProjectObjectEvent.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ProjectObjectEvent extends Event 13 | { 14 | /** 15 | * This event is dispatch when someone is added to a project. 16 | */ 17 | const OBJECT_ADDED = 'happyr.user.project.object.added'; 18 | 19 | /** 20 | * This event is dispatch when someone is removed to a project. 21 | */ 22 | const OBJECT_REMOVED = 'happyr.user.project.object.removed'; 23 | 24 | /** 25 | * @var ProjectObjectInterface 26 | */ 27 | protected $object; 28 | 29 | /** 30 | * @var Project 31 | */ 32 | protected $project; 33 | 34 | /** 35 | * @param Project $project 36 | * @param ProjectObjectInterface $object 37 | */ 38 | public function __construct(Project $project, ProjectObjectInterface $object) 39 | { 40 | $this->object = $object; 41 | $this->project = $project; 42 | } 43 | 44 | /** 45 | * @return \Happyr\UserProjectBundle\Entity\Project 46 | */ 47 | public function getProject() 48 | { 49 | return $this->project; 50 | } 51 | 52 | /** 53 | * @return ProjectObjectInterface 54 | */ 55 | public function getObject() 56 | { 57 | return $this->object; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | ./Tests 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | ./vendor/ 32 | ./Tests/ 33 | ./Resources/ 34 | ./DependencyInjection/ 35 | 36 | 37 | ./ 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Resources/views/Project/table.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for project in projects %} 12 | 13 | 20 | 21 | 22 | 29 | 30 | {% else %} 31 | 32 | 35 | 36 | 37 | {% endfor %} 38 | 39 |
{% trans %}happyr.user.project.project.index.table.name{% endtrans %}{% trans %}happyr.user.project.project.index.table.members{% endtrans %}{% trans %}happyr.user.project.project.index.table.objects{% endtrans %}
14 | {%- if not requestToJoin or is_granted('ROLE_DIRECTOR') -%} 15 | {{ project.name }} 16 | {%- else -%} 17 | {{ project.name }} 18 | {%- endif -%} 19 | {{ project.users|length }}{{ project.objects|length }} 23 | {%- if requestToJoin -%} 24 | 25 | {%- trans %}happyr.user.project.project.index.request{% endtrans -%} 26 | 27 | {%- endif -%} 28 |
33 | {%- trans %}happyr.user.project.project.index.no_projects{% endtrans -%} 34 |
40 | -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | 3 | services: 4 | happyr.user.project.permission_manager: 5 | class: Happyr\UserProjectBundle\Manager\PermissionManager 6 | public: false 7 | arguments: ["@security.acl.provider"] 8 | 9 | happyr.user.project.project_manager: 10 | class: Happyr\UserProjectBundle\Manager\ProjectManager 11 | public: true 12 | arguments: 13 | - "@doctrine.orm.entity_manager" 14 | - "@happyr.user.project.permission_manager" 15 | - "@happyr.user.project.project_factory" 16 | - "@event_dispatcher" 17 | 18 | happyr.user.project.secure_project_manager: 19 | class: Happyr\UserProjectBundle\Manager\SecureProjectManager 20 | arguments: 21 | - "@doctrine.orm.entity_manager" 22 | - "@happyr.user.project.permission_manager" 23 | - "@happyr.user.project.project_factory" 24 | - "@event_dispatcher" 25 | public: true 26 | calls: 27 | - [setSecurityManager, ["@happyr.user.project.security_manager"]] 28 | 29 | happyr.user.project.project_factory: 30 | class: Happyr\UserProjectBundle\Factory\ProjectFactory 31 | arguments: ["@doctrine.orm.entity_manager", "@happyr.user.project.permission_manager"] 32 | public: true 33 | 34 | happyr.user.project.project_service: 35 | class: Happyr\UserProjectBundle\Service\ProjectService 36 | arguments: ["@doctrine.orm.entity_manager", "@happyr.user.project.project_factory", "@event_dispatcher"] 37 | 38 | happyr.user.project.security_manager: 39 | class: Happyr\UserProjectBundle\Manager\SecurityManager 40 | arguments: ["@security.authorization_checker"] 41 | public: false 42 | -------------------------------------------------------------------------------- /Event/BaseEvent.php: -------------------------------------------------------------------------------- 1 | user = $user; 33 | $this->project = $project; 34 | } 35 | 36 | /** 37 | * @param \Happyr\UserProjectBundle\Entity\Project $project 38 | * 39 | * @return $this 40 | */ 41 | public function setProject(Project $project) 42 | { 43 | $this->project = $project; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * @return \Happyr\UserProjectBundle\Entity\Project 50 | */ 51 | public function getProject() 52 | { 53 | return $this->project; 54 | } 55 | 56 | /** 57 | * @param ProjectMemberInterface $user 58 | * 59 | * @return $this 60 | */ 61 | public function setUser(ProjectMemberInterface $user) 62 | { 63 | $this->user = $user; 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * @return ProjectMemberInterface 70 | */ 71 | public function getUser() 72 | { 73 | return $this->user; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Manager/SecurityManager.php: -------------------------------------------------------------------------------- 1 | authChecker = $authChecker; 27 | } 28 | 29 | /** 30 | * Does the current user have $mask permissions on $object? 31 | * 32 | * @param string $mask can be VIEW, EDIT, DELETE, OPERATOR etc 33 | * @param mixed $object any entity 34 | * 35 | * @return bool 36 | */ 37 | public function userIsGranted($mask, $object) 38 | { 39 | // check for access with ACL 40 | return $this->authChecker->isGranted($mask, $object); 41 | } 42 | 43 | /** 44 | * Throws a AccessDeniedException if the user is not granted the $mask on the Object. 45 | * 46 | * @param string $mask can be VIEW, EDIT, DELETE, OPERATOR etc 47 | * @param mixed $object any entity 48 | * 49 | * @return bool 50 | * 51 | * @throws AccessDeniedHttpException 52 | */ 53 | public function verifyUserIsGranted($mask, $object) 54 | { 55 | if ($this->userIsGranted($mask, $object)) { 56 | return true; 57 | } 58 | 59 | throw new AccessDeniedHttpException(sprintf('You have no privileges to "%s" this resource.', strtolower($mask))); 60 | } 61 | 62 | /** 63 | * Throws a AccessDeniedException if the project is not public. 64 | * 65 | * @param Project $project 66 | * 67 | * @return bool 68 | * 69 | * @throws AccessDeniedHttpException 70 | */ 71 | public function verifyProjectIsPublic(Project $project) 72 | { 73 | if ($project->isPublic()) { 74 | return true; 75 | } 76 | 77 | throw new AccessDeniedHttpException('This is not a public project.'); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Resources/translations/messages.en.yml: -------------------------------------------------------------------------------- 1 | happyr.user.project: 2 | project: 3 | delete: 4 | heading: 'Remove project' 5 | paragraph: 'Deleting a project also removes access for all users invited to the project. Objects will not be deleted.' 6 | paragraph2: 'Use the button below to delete %%project%%' 7 | show: 8 | projects: 9 | permissions: Permissions 10 | heading: 'User projects' 11 | desciption: 'Here is a list of all the projects the user is a member of.' 12 | empty: 'This user is not a member of any projects.' 13 | sidebar: 14 | users: 15 | headline: 'Add users' 16 | objects: 17 | headline: 'Add objects' 18 | headline: 'Extend the project' 19 | users: 20 | heading: 'Project members' 21 | description: 'These users are members of the project. Click on the X to remove them.' 22 | empty: 'There are no users in this project yet.' 23 | objects: 24 | heading: 'Objects' 25 | description: 'These objects are in the project. Click on the X to remove them.' 26 | empty: 'There are no objects in this project yet.' 27 | edit: 28 | heading: Edit 29 | form: 30 | users: Members 31 | name: 'Project name' 32 | description: 'Project description' 33 | user: 34 | label: 'Choose users' 35 | help: 'Choose users that you want to participate in this project' 36 | object: 37 | label: 'Choose objects' 38 | help: 'Choose objects that you want to be part of this project' 39 | index: 40 | table: 41 | name: Name 42 | description: Description 43 | objects: Objects 44 | members: Members 45 | my_projects: 'My projects' 46 | heading: 'All projects' 47 | request: Join! 48 | company_projects: 'All projects' 49 | no_projects: 'There is no projects here yet' 50 | paragraph: 'This is your project page. Use projects to invite and collaborate with colleagues.' 51 | sidebar: 52 | remove: Remove 53 | projects: 'All projects' 54 | create: 'Create new' 55 | edit: Edit 56 | leave: Leave 57 | base: 58 | title: Project 59 | new: 60 | heading: 'Create new project' 61 | paragraph: 'If you want to collaborate with a colleague you could create a project and invite your colleagues.' 62 | mask: 63 | VIEW: Watcher 64 | EDIT: 'Manage objects' 65 | CREATE: 'Create new object' 66 | DELETE: 'Create and remove objects' 67 | MASTER: Administrator 68 | word.name: Name -------------------------------------------------------------------------------- /Entity/ProjectRepository.php: -------------------------------------------------------------------------------- 1 | findOneBy( 26 | array( 27 | 'name' => '_private_'.$user->getId(), 28 | 'public' => false, 29 | ) 30 | ); 31 | } 32 | 33 | /** 34 | * Find the projects that this user is a member of. 35 | * This will not fetch private projects. 36 | * 37 | * @param ProjectMemberInterface $user 38 | * 39 | * @return array 40 | */ 41 | public function findUserProjects(ProjectMemberInterface $user) 42 | { 43 | $query = $this->getUserProjectsQb() 44 | ->setParameter('user_id', $user->getId()) 45 | ->getQuery(); 46 | 47 | return $query->getResult(); 48 | } 49 | 50 | /** 51 | * Get query builder for user project. 52 | * 53 | * 54 | * @return \Doctrine\ORM\QueryBuilder 55 | */ 56 | protected function getUserProjectsQb() 57 | { 58 | $qb = $this->getEntityManager()->createQueryBuilder(); 59 | 60 | return $qb 61 | ->select('e') 62 | ->from('HappyrUserProjectBundle:Project', 'e') 63 | ->join('e.users', 'u') 64 | ->where('u.id = :user_id') 65 | ->andWhere('e.public = 1'); 66 | } 67 | 68 | /** 69 | * Find the projects that this user is not a member of. 70 | * This will not fetch private projects. 71 | * 72 | * @param ProjectMemberInterface $user 73 | * 74 | * @return array 75 | */ 76 | public function findNonUserProjects(ProjectMemberInterface $user) 77 | { 78 | $qb = $this->getEntityManager()->createQueryBuilder(); 79 | 80 | $query = $qb 81 | ->select('p') 82 | ->from('HappyrUserProjectBundle:Project', 'p') 83 | ->andWhere('p.public = 1') 84 | ->andWhere($qb->expr()->notIn('p.id', $this->getUserProjectsQb()->getDQL())) 85 | ->setParameter('user_id', $user->getId()) 86 | ->getQuery(); 87 | 88 | return $query->getResult(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Resources/translations/messages.sv.yml: -------------------------------------------------------------------------------- 1 | happyr.user.project: 2 | project: 3 | base: 4 | title: Projekt 5 | sidebar: 6 | projects: 'Alla projekt' 7 | create: 'Skapa nytt' 8 | edit: Redigera 9 | remove: 'Ta bort' 10 | leave: 'Lämna' 11 | index: 12 | heading: 'Alla projekt' 13 | paragraph: 'Detta är din projektsida. Projekt är ett utmärkt verktyg om du vill samarbeta med en eller flera kollegor.' 14 | request: 'Gå med!' 15 | my_projects: 'Mina projekt' 16 | company_projects: 'Alla projekt' 17 | no_projects: 'Det finns inga projekt här ännu.' 18 | table: 19 | name: Namn 20 | description: Beskivning 21 | members: Medlemmar 22 | objects: Objekt 23 | form: 24 | name: 'Projektets namn' 25 | description: 'Projektets beskrivning' 26 | users: Medlemmar 27 | user: 28 | label: 'Välj användare' 29 | help: 'Välj en användare som du vill ska vara med i detta projekt' 30 | objects: 31 | label: 'Välj objekt' 32 | help: 'Välj ett objekt som du vill ska vara med i detta projekt' 33 | new: 34 | heading: 'Skapa nytt projekt' 35 | paragraph: 'Om du vill samarbeta med en kollega så skapa ett projekt och bjud in hen och lägg till ett par objekt.' 36 | edit: 37 | heading: Redigera 38 | show: 39 | users: 40 | heading: Projektmedlemmar 41 | description: 'Dessa användare ingår i projektet. Klicka på krysset för att ta bort dem från projektet.' 42 | empty: 'Det finns inga användare i detta projekt ännu.' 43 | objects: 44 | heading: Objekt 45 | description: 'Dessa objekt ingår i projektet. Klicka på krysset för att ta bort dem från projektet.' 46 | empty: 'Det finns inga objekt i detta projekt ännu.' 47 | projects: 48 | heading: 'Användarens projekt' 49 | desciption: 'Här är en lista på dem projekt som anvädaren är medlem i.' 50 | empty: 'Denna användare har inga projekt.' 51 | permissions: Behörighet 52 | sidebar: 53 | headline: 'Utöka projektet' 54 | users: 55 | headline: 'Lägg till användare' 56 | objects: 57 | headline: 'Lägg till objekt' 58 | delete: 59 | heading: 'Ta bort projekt' 60 | paragraph: 'När du tar bort ett projekt så försvinner projektmedlemmarnas rättigheter till objekten. Inga objekt kommer att raderas.' 61 | paragraph2: 'Klicka på knappen nedan för att ta bort %%project%%.' 62 | mask: 63 | VIEW: Åskådare 64 | EDIT: 'Hantera objekt' 65 | CREATE: 'Skapa nya objekt' 66 | DELETE: 'Skapa och ta bort objekt' 67 | MASTER: Administratör 68 | word.name: Namn -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Happyr User Project Bundle 2 | 3 | If you have multiple users that will share access to one or more objects. 4 | 5 | Installation 6 | ------------ 7 | 8 | ### Step 1: Using Composer 9 | 10 | Install it with Composer! 11 | 12 | ```js 13 | // composer.json 14 | { 15 | // ... 16 | require: { 17 | // ... 18 | "happyr/user-project-bundle": "dev-master", 19 | } 20 | } 21 | ``` 22 | 23 | Then, you can install the new dependencies by running Composer's ``update`` 24 | command from the directory where your ``composer.json`` file is located: 25 | 26 | ```bash 27 | $ php composer.phar update 28 | ``` 29 | 30 | ### Step 2: Register the bundle 31 | 32 | To register the bundles with your kernel: 33 | 34 | ```php 35 | id 88 | } 89 | 90 | public function getProject() 91 | { 92 | $this->project; 93 | } 94 | 95 | /** 96 | * 97 | * @param Project $project 98 | * 99 | */ 100 | public function setProject(Project $project) 101 | { 102 | $this->project=$project; 103 | } 104 | } 105 | ``` 106 | 107 | ### Routing 108 | 109 | ```yml 110 | # app/config/routing.yml 111 | happyr_user_project: 112 | resource: "@HappyrUserProjectBundle/Resources/config/routing.yml" 113 | prefix: / 114 | ``` 115 | -------------------------------------------------------------------------------- /Resources/views/Project/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'HappyrUserProjectBundle:Project:base.html.twig' %} 2 | 3 | 4 | {% import "HappyrUserProjectBundle:Macro:tableMacro.html.twig" as macro %} 5 | {% block content -%} 6 |
7 |

{{ project.name }}

8 | 9 |

{{ project.description }}

10 | 11 |
12 |

{% trans %}happyr.user.project.project.show.users.heading{% endtrans %}

13 | 14 |

{% trans %}happyr.user.project.project.show.users.description{% endtrans %}

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for u in project.users %} 24 | 25 | 26 | {# make sure you are administrator or director, but you cant edit yourself #} 27 | {% if (is_granted('MASTER', project) or is_granted('ROLE_DIRECTOR')) and u.id != _user.id %} 28 | 29 | 31 | {% else %} 32 | 33 | 34 | {% endif %} 35 | 36 | {% else %} 37 | 38 | 39 | 40 | 41 | 42 | {% endfor %} 43 |
{% trans %}word.name{% endtrans %}
{{ u }}{{ macro.projectUserPermission(project, u) }}

{% trans %}happyr.user.project.project.show.users.empty{% endtrans %}

44 | 45 |
46 | 47 |
48 |

{% trans %}happyr.user.project.project.show.objects.heading{% endtrans %}

49 | 50 |

{% trans %}happyr.user.project.project.show.objects.description{% endtrans %}

51 | 52 | 53 | 54 | 55 | 56 | 57 | {% for o in project.objects %} 58 | 59 | 60 | 66 | 67 | {% else %} 68 | 69 | 70 | 71 | 72 | {% endfor %} 73 |
{% trans %}word.name{% endtrans %}
{{ o.headline }} 61 | {% if (is_granted('DELETE', project) or is_granted('ROLE_DIRECTOR')) %} 62 | 64 | {% endif %} 65 |

{% trans %}happyr.user.project.project.show.objects.empty{% endtrans %}

74 | 75 |
76 |
77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /Manager/SecureProjectManager.php: -------------------------------------------------------------------------------- 1 | securityManager = $securityManager; 29 | } 30 | 31 | /** 32 | * Add a user to a project. 33 | * 34 | * 35 | * @param Project $project 36 | * @param ProjectMemberInterface $user 37 | * @param string $mask 38 | * 39 | * @return bool 40 | */ 41 | public function addUser(Project $project, ProjectMemberInterface $user, $mask = 'VIEW') 42 | { 43 | $this->securityManager->verifyProjectIsPublic($project); 44 | $this->securityManager->verifyUserIsGranted('MASTER', $project); 45 | 46 | return parent::addUser($project, $user, $mask); 47 | } 48 | 49 | /** 50 | * Add object to project. 51 | * 52 | * WARNING: This will remove the object from any previuos projects 53 | * 54 | * @param Project $project 55 | * @param ProjectObjectInterface $object 56 | * 57 | * @return bool 58 | */ 59 | public function addObject(Project $project, ProjectObjectInterface $object) 60 | { 61 | $this->securityManager->verifyUserIsGranted('CREATE', $project); 62 | 63 | return parent::addObject($project, $object); 64 | } 65 | 66 | /** 67 | * Remove user. 68 | * 69 | * @param Project $project 70 | * @param ProjectMemberInterface $user 71 | */ 72 | public function removeUser(Project $project, ProjectMemberInterface $user) 73 | { 74 | $this->securityManager->verifyUserIsGranted('MASTER', $project); 75 | 76 | parent::removeUser($project, $user); 77 | } 78 | 79 | /** 80 | * Remove object from project. 81 | * 82 | * @param Project $project 83 | * @param ProjectObjectInterface $object 84 | */ 85 | public function removeObject(Project $project, ProjectObjectInterface $object) 86 | { 87 | $this->securityManager->verifyUserIsGranted('DELETE', $project); 88 | 89 | parent::removeObject($project, $user); 90 | } 91 | 92 | /** 93 | * Alias for PermissionManager. 94 | * 95 | * @param Project $project 96 | * @param ProjectMemberInterface $user 97 | * @param string $mask 98 | */ 99 | public function changeUserPermissions(Project $project, ProjectMemberInterface $user, $mask) 100 | { 101 | $this->securityManager->verifyUserIsGranted('MASTER', $project); 102 | 103 | parent::changeUserPermissions($project, $user, $mask); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Factory/ProjectFactory.php: -------------------------------------------------------------------------------- 1 | em = $em; 37 | $this->permissionManager = $pm; 38 | } 39 | 40 | /** 41 | * Returns a new object with all empty values. 42 | * 43 | * @return Project 44 | */ 45 | public function getNew() 46 | { 47 | return new Project(); 48 | } 49 | 50 | /** 51 | * Mark a project as private. 52 | * 53 | * @param Project $project 54 | * @param ProjectMemberInterface $user 55 | */ 56 | public function makePrivate(Project $project, ProjectMemberInterface $user) 57 | { 58 | $project 59 | ->setName('_private_'.$user->getId()) 60 | ->setPublic(false) 61 | ->addUser($user); 62 | } 63 | 64 | /** 65 | * Clone a private project. Get a new project with owner of the private one. 66 | * 67 | * You have to name the new project and add objects to it. 68 | * 69 | * @param Project $project 70 | * 71 | * @return Project 72 | */ 73 | public function clonePrivateProject(Project $project) 74 | { 75 | $owner = $project->getUsers()->first(); 76 | 77 | $project = $this->getNew(); 78 | $project->setName('Project - '.$owner->getId()); 79 | 80 | $this->create($project); 81 | $this->permissionManager->addUser($project, $owner, 'OWNER'); 82 | 83 | return $project; 84 | } 85 | 86 | /** 87 | * Saves the projects. 88 | * 89 | * @param Project $project 90 | */ 91 | public function create(Project $project) 92 | { 93 | $this->em->persist($project); 94 | $this->em->flush(); 95 | 96 | if (!$project->isPublic()) { 97 | //make the user master over his private project 98 | $user = $project->getUsers()->first(); 99 | $this->permissionManager->addUser($project, $user, 'OWNER'); 100 | } 101 | } 102 | 103 | /** 104 | * Remove a project. 105 | * 106 | * @param Project $project 107 | */ 108 | public function remove(Project $project) 109 | { 110 | $users = $project->getUsers(); 111 | 112 | foreach ($users as $user) { 113 | $this->permissionManager->removeUser($project, $user); 114 | } 115 | 116 | $objects = $project->getObjects(); 117 | foreach ($objects as $o) { 118 | $o->removeProject(); 119 | $this->em->persist($o); 120 | } 121 | $this->em->flush(); 122 | 123 | $this->em->remove($project); 124 | $this->em->flush(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Service/ProjectService.php: -------------------------------------------------------------------------------- 1 | em = $em; 43 | $this->projectFactory = $pf; 44 | $this->dispatcher = $dispatcher; 45 | } 46 | 47 | /** 48 | * Get the administrator for an object. 49 | * 50 | * @param ProjectObjectInterface $object 51 | * 52 | * @return ProjectMemberInterface|null 53 | */ 54 | public function getAdministratorForObject(ProjectObjectInterface $object) 55 | { 56 | if (null == $project = $object->getProject()) { 57 | return; 58 | } 59 | 60 | return $this->getAdministrator($project); 61 | } 62 | 63 | /** 64 | * Get an administrator for a project. 65 | * 66 | * @param Project $project 67 | * 68 | * @return ProjectMemberInterface|null 69 | */ 70 | public function getAdministrator(Project $project) 71 | { 72 | $user = $project->getUsers()->filter( 73 | function ($user) use ($project) { 74 | if ($project->getPermission($user) == 'MASTER') { 75 | return $user; 76 | } 77 | } 78 | )->first(); 79 | 80 | if ($user !== false) { 81 | return $user; 82 | } 83 | 84 | return; 85 | } 86 | 87 | /** 88 | * This will always return a private project. If there is none at the moment 89 | * we will create one. 90 | * 91 | * @param ProjectMemberInterface $user 92 | * 93 | * @return Project 94 | */ 95 | public function getUserPrivateProject(ProjectMemberInterface $user) 96 | { 97 | $project = $this->em->getRepository('HappyrUserProjectBundle:Project') 98 | ->findPrivateProject($user); 99 | 100 | if (!$project) { 101 | $project = $this->projectFactory->getNew(); 102 | $this->projectFactory->makePrivate($project, $user); 103 | $this->projectFactory->create($project); 104 | } 105 | 106 | return $project; 107 | } 108 | 109 | /** 110 | * Add a request to join the project. 111 | * 112 | * @param Project $project 113 | * @param ProjectMemberInterface $user 114 | */ 115 | public function addJoinRequest(Project $project, ProjectMemberInterface $user) 116 | { 117 | /** 118 | * Get admins. 119 | */ 120 | $administrators = array(); 121 | foreach ($project->getUsers() as $u) { 122 | if ($project->getPermission($u) == 'MASTER') { 123 | $administrators[] = $u; 124 | } 125 | } 126 | 127 | //fire event 128 | $event = new JoinRequestEvent($project, $administrators, $user); 129 | $this->dispatcher->dispatch(JoinRequestEvent::USER_JOIN_REQUEST, $event); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Manager/BaseAclManager.php: -------------------------------------------------------------------------------- 1 | aclProvider = $aclProvider; 30 | } 31 | 32 | /** 33 | * Return a ACL for the object. 34 | * 35 | * @param mixed $object 36 | * 37 | * @return \Symfony\Component\Security\Acl\Domain\Acl 38 | */ 39 | protected function getObjectAcl($object) 40 | { 41 | $objectIdentity = ObjectIdentity::fromDomainObject($object); 42 | try { 43 | $acl = $this->aclProvider->createAcl($objectIdentity); 44 | } catch (AclAlreadyExistsException $e) { 45 | //find acl 46 | $acl = $this->aclProvider->findAcl($objectIdentity); 47 | } 48 | 49 | return $acl; 50 | } 51 | 52 | /** 53 | * Get the user security identity. 54 | * 55 | * @param UserInterface $user 56 | * 57 | * @return UserSecurityIdentity 58 | */ 59 | protected function getUserIdentity(UserInterface $user) 60 | { 61 | $identity = UserSecurityIdentity::fromAccount($user); 62 | 63 | return $identity; 64 | } 65 | 66 | /** 67 | * Add a user to the object and set the permission mask. 68 | * 69 | * Thus function persists the object but does not flush 70 | * 71 | * @param mixed $object 72 | * @param UserInterface $user 73 | * @param int $permissionMask 74 | */ 75 | protected function addUserAce($object, UserInterface $user, $permissionMask) 76 | { 77 | $securityIdentity = $this->getUserIdentity($user); 78 | $acl = $this->getObjectAcl($object); 79 | 80 | $aceIndex = $this->getAceIndex($acl, $securityIdentity); 81 | if (false !== $aceIndex) { 82 | $acl->updateObjectAce($aceIndex, $permissionMask); 83 | } else { 84 | //there is no access control entity for this user in this acl, 85 | //Create one 86 | $acl->insertObjectAce($securityIdentity, $permissionMask); 87 | } 88 | 89 | $this->aclProvider->updateAcl($acl); 90 | } 91 | 92 | /** 93 | * Delete the access control entity for this user on this object. 94 | * 95 | * @param mixed $object 96 | * @param UserInterface $user 97 | */ 98 | protected function removeUserAce($object, UserInterface $user) 99 | { 100 | $securityIdentity = $this->getUserIdentity($user); 101 | $acl = $this->getObjectAcl($object); 102 | 103 | $aceIndex = $this->getAceIndex($acl, $securityIdentity); 104 | if (false !== $aceIndex) { 105 | //remove 106 | $acl->deleteObjectAce($aceIndex); 107 | } 108 | 109 | $this->aclProvider->updateAcl($acl); 110 | } 111 | 112 | /** 113 | * Return the index of the Ace for the $securityIdentity in the $acl. 114 | * 115 | * @param AclInterface $acl 116 | * @param UserSecurityIdentity $securityIdentity 117 | * 118 | * @return int|bool false if not found 119 | */ 120 | protected function getAceIndex(AclInterface $acl, UserSecurityIdentity $securityIdentity) 121 | { 122 | //gets the aces 123 | $aces = $acl->getObjectAces(); 124 | 125 | //we have to check every Ace and find the one related to our 126 | foreach ($aces as $index => $ace) { 127 | if ($ace->getSecurityIdentity() == $securityIdentity) { 128 | return $index; 129 | } 130 | } 131 | 132 | return false; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Manager/ProjectManager.php: -------------------------------------------------------------------------------- 1 | em = $om; 56 | $this->permissionManager = $pm; 57 | $this->projectFactory = $pf; 58 | $this->dispatcher = $dispatcher; 59 | } 60 | 61 | /** 62 | * Remove private projects are revoke permission from other projects. 63 | * 64 | * @param ProjectMemberInterface $user 65 | */ 66 | public function removeUserFromAllProjects(ProjectMemberInterface $user) 67 | { 68 | $repo = $this->em->getRepository('HappyrUserProjectBundle:Project'); 69 | if (null !== $privateProject = $repo->findPrivateProject($user)) { 70 | $this->projectFactory->remove($privateProject); 71 | } 72 | 73 | $projects = $repo->findUserProjects($user); 74 | foreach ($projects as $p) { 75 | $this->permissionManager->removeUser($p, $user); 76 | } 77 | } 78 | 79 | /** 80 | * Add a user to a project. 81 | * 82 | * 83 | * @param Project $project 84 | * @param ProjectMemberInterface $user 85 | * @param string $mask 86 | * 87 | * @return bool 88 | * 89 | * @throws \InvalidArgumentException 90 | */ 91 | public function addUser(Project $project, ProjectMemberInterface $user, $mask = 'VIEW') 92 | { 93 | //if you try to add a user that already is a part of the project 94 | if ($project->getUsers()->contains($user)) { 95 | return true; 96 | } 97 | 98 | /** 99 | * Make sure that the project is a public project, or create a new public project. 100 | */ 101 | if (!$project->isPublic()) { 102 | throw new \InvalidArgumentException('You can not add a user to a private project'); 103 | } 104 | 105 | $this->permissionManager->addUser($project, $user, $mask); 106 | 107 | $this->em->persist($project); 108 | $this->em->flush(); 109 | 110 | // Dispatch event 111 | $this->dispatcher->dispatch(ProjectEvent::USER_ADDED, new ProjectEvent($project, $user)); 112 | } 113 | 114 | /** 115 | * Remove user. 116 | * 117 | * @param Project $project 118 | * @param ProjectMemberInterface $user 119 | */ 120 | public function removeUser(Project $project, ProjectMemberInterface $user) 121 | { 122 | $this->permissionManager->removeUser($project, $user); 123 | 124 | $this->em->persist($project); 125 | $this->em->flush(); 126 | 127 | // Dispatch event 128 | $this->dispatcher->dispatch(ProjectEvent::USER_REMOVED, new ProjectEvent($project, $user)); 129 | } 130 | 131 | /** 132 | * Add object to project. 133 | * 134 | * WARNING: This will remove the object from any previuos projects 135 | * 136 | * @param Project $project 137 | * @param ProjectObjectInterface $object 138 | * 139 | * @return bool 140 | */ 141 | public function addObject(Project $project, ProjectObjectInterface $object) 142 | { 143 | /* 144 | * Check if the object belongs to an other project 145 | */ 146 | if ($object->getProject() != null) { 147 | $objectProject = $object->getProject(); 148 | 149 | $this->removeObject($objectProject, $object); 150 | $this->em->persist($objectProject); 151 | } 152 | 153 | //add the object to this project 154 | $this->permissionManager->addObject($project, $object); 155 | 156 | $this->em->persist($project); 157 | $this->em->persist($object); 158 | $this->em->flush(); 159 | 160 | // Dispatch event 161 | $this->dispatcher->dispatch(ProjectObjectEvent::OBJECT_ADDED, new ProjectEvent($project, $object)); 162 | 163 | return true; 164 | } 165 | 166 | /** 167 | * Remove object from project. 168 | * 169 | * @param Project $project 170 | * @param ProjectObjectInterface $object 171 | */ 172 | public function removeObject(Project $project, ProjectObjectInterface $object) 173 | { 174 | $this->permissionManager->removeObject($project, $object); 175 | 176 | $this->em->persist($project); 177 | $this->em->persist($object); 178 | $this->em->flush(); 179 | 180 | $this->dispatcher->dispatch(ProjectObjectEvent::OBJECT_REMOVED, new ProjectEvent($project, $object)); 181 | } 182 | 183 | /** 184 | * Alias for PermissionManager. 185 | * 186 | * @param Project $project 187 | * @param ProjectMemberInterface $user 188 | * @param string $mask 189 | */ 190 | public function changeUserPermissions(Project $project, ProjectMemberInterface $user, $mask) 191 | { 192 | $this->permissionManager->changePermissions($project, $user, $mask); 193 | 194 | $this->em->persist($project); 195 | $this->em->flush(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Manager/PermissionManager.php: -------------------------------------------------------------------------------- 1 | addObject($object); 48 | $users = $project->getUsers(); 49 | 50 | foreach ($users as $user) { 51 | $bitMask = $this->getBitMask($project->getPermission($user)); 52 | $this->addUserAce($object, $user, $bitMask); 53 | } 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Remove an object from the project. 60 | * 61 | * Remember to persist the $object as well. Doctrine can't find the object since we cut the relation. 62 | * 63 | * @param Project $project 64 | * @param ProjectObjectInterface $object 65 | * 66 | * @return $this 67 | */ 68 | public function removeObject(Project $project, ProjectObjectInterface $object) 69 | { 70 | $project->removeObject($object); 71 | $users = $project->getUsers(); 72 | 73 | foreach ($users as $user) { 74 | $this->removeUserAce($object, $user); 75 | } 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Add a user to a project. This gives the user the proper permissions to all objects in the project. 82 | * 83 | * @param Project $project 84 | * @param ProjectMemberInterface $user 85 | * @param string $mask 86 | * 87 | * @return $this 88 | * 89 | * @throws \InvalidArgumentException 90 | */ 91 | public function addUser(Project $project, ProjectMemberInterface $user, $mask = 'VIEW') 92 | { 93 | $project->addUser($user); 94 | 95 | return $this->changePermissions($project, $user, $mask); 96 | } 97 | 98 | /** 99 | * Remove a user from the project. 100 | * 101 | * @param Project $project 102 | * @param ProjectMemberInterface $user 103 | * 104 | * @return $this 105 | */ 106 | public function removeUser(Project $project, ProjectMemberInterface $user) 107 | { 108 | $project->removeUser($user); 109 | 110 | return $this->revokePermissions($project, $user); 111 | } 112 | 113 | /** 114 | * Change a users privileges in a project. 115 | * 116 | * 117 | * @param Project $project 118 | * @param ProjectMemberInterface $user 119 | * @param string $mask 120 | * 121 | * @return $this; 122 | * 123 | * @throws \ErrorException 124 | */ 125 | public function changePermissions(Project $project, ProjectMemberInterface $user, $mask) 126 | { 127 | $mask = strtoupper($mask); 128 | $validMasks = self::$validMasks; 129 | $validMasks[] = 'REVOKE'; 130 | 131 | if (!in_array($mask, $validMasks)) { 132 | throw new \ErrorException( 133 | sprintf( 134 | "The string '%s' is not a valid mask. Valid masks are (".implode(',', $validMasks).').', 135 | $mask 136 | ) 137 | ); 138 | } 139 | 140 | if ($mask == 'REVOKE') { 141 | return $this->revokePermissions($project, $user); 142 | } 143 | 144 | /* 145 | * Save mask in the user permissions 146 | */ 147 | $bitMask = $this->getBitMask($mask); 148 | 149 | $project->setPermission($user, $mask); 150 | $this->addUserAce($project, $user, $bitMask); 151 | 152 | /* 153 | * Add some permissions on the $project->getObjects for the $user 154 | */ 155 | $objectes = $project->getObjects(); 156 | foreach ($objectes as $object) { 157 | $this->addUserAce($object, $user, $bitMask); 158 | } 159 | 160 | return $this; 161 | } 162 | 163 | /** 164 | * Remove permissions for the user. 165 | * 166 | * @param Project $project 167 | * @param ProjectMemberInterface $user 168 | * 169 | * @return $this 170 | */ 171 | private function revokePermissions(Project $project, ProjectMemberInterface $user) 172 | { 173 | $project->revokePermissions($user); 174 | 175 | //remove project ace 176 | $this->removeUserAce($project, $user); 177 | 178 | //remove ace for each object 179 | $objects = $project->getObjects(); 180 | foreach ($objects as $object) { 181 | $this->removeUserAce($object, $user); 182 | } 183 | 184 | return $this; 185 | } 186 | 187 | /** 188 | * Get the bit mask from a string mask. 189 | * 190 | * @param string $mask 191 | * 192 | * @return int 193 | */ 194 | protected function getBitMask($mask) 195 | { 196 | $builder = new MaskBuilder(); 197 | foreach (self::$validMasks as $m) { 198 | $builder->add($m); 199 | if ($m == $mask) { 200 | break; 201 | } 202 | } 203 | 204 | return $builder->get(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Entity/Project.php: -------------------------------------------------------------------------------- 1 | users = new ArrayCollection(); 74 | $this->objects = new ArrayCollection(); 75 | $this->permissions = array(); 76 | $this->createdAt = new \DateTime(); 77 | $this->updatedAt = new \DateTime(); 78 | } 79 | 80 | /** 81 | * Set permissions for user. 82 | * 83 | * @param ProjectMemberInterface $user 84 | * @param string $mask 85 | * 86 | * @return $this 87 | */ 88 | public function setPermission(ProjectMemberInterface $user, $mask) 89 | { 90 | $this->permissions[$user->getId()] = $mask; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * Get the permissions for this user on this project. 97 | * 98 | * @param ProjectMemberInterface $user 99 | * 100 | * @return string 101 | */ 102 | public function getPermission(ProjectMemberInterface $user) 103 | { 104 | if (isset($this->permissions[$user->getId()])) { 105 | return $this->permissions[$user->getId()]; 106 | } 107 | 108 | return 'NONE'; 109 | } 110 | 111 | /** 112 | * Revoke persmissions for a user. 113 | * 114 | * @param ProjectMemberInterface $user 115 | * 116 | * @return $this 117 | */ 118 | public function revokePermissions(ProjectMemberInterface $user) 119 | { 120 | if (isset($this->permissions[$user->getId()])) { 121 | unset($this->permissions[$user->getId()]); 122 | } 123 | 124 | return $this; 125 | } 126 | 127 | /** 128 | * Get id. 129 | * 130 | * @return int 131 | */ 132 | public function getId() 133 | { 134 | return $this->id; 135 | } 136 | 137 | /** 138 | * Add user. 139 | * 140 | * @param UserInterface $user 141 | * 142 | * @return Project 143 | */ 144 | public function addUser(UserInterface $user) 145 | { 146 | if (!$this->users->contains($user)) { 147 | $this->users->add($user); 148 | } 149 | 150 | return $this; 151 | } 152 | 153 | /** 154 | * Remove an user. 155 | * 156 | * @param UserInterface $user 157 | * 158 | * @return bool 159 | */ 160 | public function removeUser(UserInterface $user) 161 | { 162 | return $this->users->removeElement($user); 163 | } 164 | 165 | /** 166 | * Get users. 167 | * 168 | * @return \Doctrine\Common\Collections\ArrayCollection 169 | */ 170 | public function getUsers() 171 | { 172 | return $this->users; 173 | } 174 | 175 | /** 176 | * Set name. 177 | * 178 | * @param string $name 179 | * 180 | * @return Project 181 | */ 182 | public function setName($name) 183 | { 184 | $this->name = $name; 185 | 186 | return $this; 187 | } 188 | 189 | /** 190 | * Get name. 191 | * 192 | * @return string 193 | */ 194 | public function getName() 195 | { 196 | return $this->name; 197 | } 198 | 199 | /** 200 | * Set description. 201 | * 202 | * @param string $description 203 | * 204 | * @return Project 205 | */ 206 | public function setDescription($description) 207 | { 208 | $this->description = $description; 209 | 210 | return $this; 211 | } 212 | 213 | /** 214 | * Get description. 215 | * 216 | * @return string 217 | */ 218 | public function getDescription() 219 | { 220 | return $this->description; 221 | } 222 | 223 | /** 224 | * Add objects. 225 | * 226 | * @param ProjectObjectInterface $object 227 | * 228 | * @return $this 229 | */ 230 | public function addObject(ProjectObjectInterface $object) 231 | { 232 | if (!$this->objects->contains($object)) { 233 | $this->objects->add($object); 234 | } 235 | $object->setProject($this); 236 | 237 | return $this; 238 | } 239 | 240 | /** 241 | * Remove an object. 242 | * 243 | * @param ProjectObjectInterface $object 244 | * 245 | * @return bool 246 | */ 247 | public function removeObject(ProjectObjectInterface $object) 248 | { 249 | return $this->objects->removeElement($object); 250 | } 251 | 252 | /** 253 | * Get objects. 254 | * 255 | * @return \Doctrine\Common\Collections\ArrayCollection 256 | */ 257 | public function getObjects() 258 | { 259 | return $this->objects; 260 | } 261 | 262 | /** 263 | * @param bool $public 264 | * 265 | * @return $this 266 | */ 267 | public function setPublic($public) 268 | { 269 | $this->public = $public; 270 | 271 | return $this; 272 | } 273 | 274 | /** 275 | * @return bool 276 | */ 277 | public function isPublic() 278 | { 279 | return $this->public; 280 | } 281 | 282 | /** 283 | * Update timestamp. 284 | */ 285 | public function updateUpdatedAt() 286 | { 287 | $this->updatedAt = new \DateTime(); 288 | } 289 | 290 | /** 291 | * @return \Datetime 292 | */ 293 | public function getCreatedAt() 294 | { 295 | return $this->createdAt; 296 | } 297 | 298 | /** 299 | * @return \Datetime 300 | */ 301 | public function getUpdatedAt() 302 | { 303 | return $this->updatedAt; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /Controller/ProjectController.php: -------------------------------------------------------------------------------- 1 | getUser(); 28 | $repo = $this->getDoctrine()->getRepository('HappyrUserProjectBundle:Project'); 29 | $myProjects = $repo->findUserProjects($user); 30 | 31 | $projects = $repo->findNonUserProjects($user); 32 | 33 | return array( 34 | 'myProjects' => $myProjects, 35 | 'projects' => $projects, 36 | ); 37 | } 38 | 39 | /** 40 | * Finds and displays a Project entity. 41 | * 42 | * @param Project $project 43 | * 44 | * @Template() 45 | * 46 | * @return array 47 | */ 48 | public function showAction(Project $project) 49 | { 50 | $security = $this->get('happyr.user.project.security_manager'); 51 | $security->verifyUserIsGranted('VIEW', $project); 52 | $security->verifyProjectIsPublic($project); 53 | 54 | return array( 55 | 'project' => $project, 56 | ); 57 | } 58 | 59 | /** 60 | * Remove an user from the project. 61 | * 62 | * @param Project $project 63 | * 64 | * @return \Symfony\Component\HttpFoundation\RedirectResponse 65 | */ 66 | public function leaveAction(Project $project) 67 | { 68 | $security = $this->get('happyr.user.project.security_manager'); 69 | $security->verifyUserIsGranted('VIEW', $project); 70 | $security->verifyProjectIsPublic($project); 71 | 72 | $user = $this->getUser(); 73 | $this->get('happyr.user.project.permission_manager')->removeUser($project, $user); 74 | 75 | $em = $this->getDoctrine()->getManager(); 76 | $em->persist($project); 77 | $em->flush(); 78 | 79 | return $this->redirect($this->generateUrl('happyr_user_project_project_index')); 80 | } 81 | 82 | /** 83 | * Creates a new Project entity. 84 | * 85 | * @param Request $request 86 | * 87 | * @Template() 88 | * 89 | * @return array 90 | */ 91 | public function newAction(Request $request) 92 | { 93 | $factory = $this->get('happyr.user.project.project_factory'); 94 | $project = $factory->getNew(); 95 | 96 | $form = $this->createForm( 97 | new ProjectType(), 98 | $project, 99 | array( 100 | 'action' => $this->generateUrl('happyr_user_project_project_create'), 101 | ) 102 | ); 103 | $form->add('submit', 'submit', array('label' => 'form.create')); 104 | 105 | if ($request->isMethod('POST')) { 106 | $form->handleRequest($request); 107 | 108 | if ($form->isValid()) { 109 | //save project before adding users 110 | $factory->create($project); 111 | 112 | //add current user to the project 113 | $user = $this->getUser(); 114 | $permissionManager = $this->get('happyr.user.project.permission_manager'); 115 | $permissionManager->addUser($project, $user, 'MASTER'); 116 | 117 | $this->getDoctrine()->getManager()->flush(); 118 | 119 | $this->get('session')->getFlashbag()->add('success', 'happyr.user.project.project.flash.created'); 120 | 121 | return $this->redirect($this->generateUrl('happyr_user_project_project_show', array('id' => $project->getId()))); 122 | } 123 | } 124 | 125 | return array( 126 | 'project' => $project, 127 | 'form' => $form->createView(), 128 | ); 129 | } 130 | 131 | /** 132 | * Displays a form to edit an existing Project entity. 133 | * 134 | * @param Request $request 135 | * @param Project $project 136 | * 137 | * @Template() 138 | * 139 | * @return array 140 | */ 141 | public function editAction(Request $request, Project $project) 142 | { 143 | $security = $this->get('happyr.user.project.security_manager'); 144 | $security->verifyUserIsGranted('MASTER', $project); 145 | $security->verifyProjectIsPublic($project); 146 | 147 | $form = $this->createForm( 148 | new ProjectType(), 149 | $project, 150 | array( 151 | 'action' => $this->generateUrl('happyr_user_project_project_edit', array('id' => $project->getId())), 152 | ) 153 | ); 154 | $form->add('submit', 'submit', array('label' => 'form.update')); 155 | 156 | if ($request->isMethod('POST')) { 157 | $form->handleRequest($request); 158 | 159 | if ($form->isValid()) { 160 | $em = $this->getDoctrine()->getManager(); 161 | $em->persist($project); 162 | $em->flush(); 163 | 164 | $this->get('session')->getFlashbag()->add('success', 'happyr.user.project.project.flash.updated'); 165 | } 166 | } 167 | 168 | return array( 169 | 'project' => $project, 170 | 'form' => $form->createView(), 171 | ); 172 | } 173 | 174 | /** 175 | * Deletes a Project entity. 176 | * 177 | * @param Request $request 178 | * @param Project $project 179 | * 180 | * @Template() 181 | * 182 | * @return array 183 | */ 184 | public function deleteAction(Request $request, Project $project) 185 | { 186 | $security = $this->get('happyr.user.project.security_manager'); 187 | $security->verifyUserIsGranted('MASTER', $project); 188 | $security->verifyProjectIsPublic($project); 189 | 190 | $form = $this->createDeleteForm($project->getId()); 191 | 192 | if ($request->isMethod('POST')) { 193 | $form->handleRequest($request); 194 | 195 | if ($form->isValid()) { 196 | $this->get('happyr.user.project.project_factory')->remove($project); 197 | 198 | return $this->redirect($this->generateUrl('happyr_user_project_project_index')); 199 | } 200 | } 201 | 202 | return array( 203 | 'form' => $form->createView(), 204 | 'project' => $project, 205 | ); 206 | } 207 | 208 | /** 209 | * Creates a form to delete a Project entity by id. 210 | * 211 | * @param mixed $id The entity id 212 | * 213 | * @return \Symfony\Component\Form\Form The form 214 | */ 215 | private function createDeleteForm($id) 216 | { 217 | return $this->createFormBuilder() 218 | ->setAction($this->generateUrl('happyr_user_project_project_delete', array('id' => $id))) 219 | ->add('submit', 'submit', array('label' => 'form.remove')) 220 | ->getForm(); 221 | } 222 | 223 | /** 224 | * Remove an user from the project. 225 | * 226 | * @param Project $project 227 | * 228 | * @return \Symfony\Component\HttpFoundation\RedirectResponse 229 | */ 230 | public function joinRequestAction(Project $project) 231 | { 232 | if (!$project->isPublic()) { 233 | throw $this->createNotFoundException('Project not found'); 234 | } 235 | 236 | $user = $this->getUser(); 237 | $this->get('happyr.user.project.project_manager')->addJoinRequest($project, $user); 238 | 239 | return $this->redirect($this->generateUrl('happyr_user_project_project_index')); 240 | } 241 | } 242 | --------------------------------------------------------------------------------