Skip to content

Commit

Permalink
Feature: allow to create a more restricted project visibility
Browse files Browse the repository at this point in the history
Fixes #123 #121 #116

Project can be set to internal only. This means users with role of "User"
can only see issue created by them. This is to allow manager and developer to work
on internal issues that should not be visible to users/clients of the project.
  • Loading branch information
satrun77 committed Jul 21, 2016
1 parent 7962410 commit be68983
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 20 deletions.
6 changes: 5 additions & 1 deletion app/Form/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ public function fields()
'private' => [
'type' => 'select',
'label' => 'visibility',
'options' => [ProjectModel::PRIVATE_YES => trans('tinyissue.private'), ProjectModel::PRIVATE_NO => trans('tinyissue.public')],
'options' => [
ProjectModel::INTERNAL_YES => trans('tinyissue.internal'),
ProjectModel::PRIVATE_YES => trans('tinyissue.private'),
ProjectModel::PRIVATE_NO => trans('tinyissue.public'),
],
],
'default_assignee' => [
'type' => 'hidden',
Expand Down
19 changes: 13 additions & 6 deletions app/Http/Controllers/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Tinyissue\Http\Controllers;

use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\Request;
use Tinyissue\Form\FilterIssue as FilterForm;
use Tinyissue\Form\Note as NoteForm;
Expand Down Expand Up @@ -39,15 +40,20 @@ public function getIndex(Project $project)
{
$activities = $project->activities()
->with('activity', 'issue', 'user', 'assignTo', 'comment', 'note')
->orderBy('created_at', 'DESC')
->take(10)
->get();
->orderBy('users_activity.created_at', 'DESC')
->take(10);

// Internal project and logged user can see created only
if ($project->isPrivateInternal() && $this->auth->user()->isUser()) {
$activities->join('projects_issues', 'projects_issues.id', '=', 'item_id');
$activities->where('created_by', '=', $this->auth->user()->id);
}

return view('project.index', [
'tabs' => $this->projectMainViewTabs($project, 'index'),
'project' => $project,
'active' => 'activity',
'activities' => $activities,
'activities' => $activities->get(),
'sidebar' => 'project',
]);
}
Expand All @@ -64,8 +70,9 @@ public function getIndex(Project $project)
*/
public function getIssues(FilterForm $filterForm, Request $request, Project $project, $status = Issue::STATUS_OPEN)
{
$active = $status == Issue::STATUS_OPEN ? 'open_issue' : 'closed_issue';
$issues = $project->listIssues($status, $request->all());
$request['created_by'] = auth()->user()->id;
$active = $status == Issue::STATUS_OPEN ? 'open_issue' : 'closed_issue';
$issues = $project->listIssues($status, $request->all());

return view('project.index', [
'tabs' => $this->projectMainViewTabs($project, 'issues', $issues, $status),
Expand Down
11 changes: 9 additions & 2 deletions app/Http/Middleware/Permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,22 @@ public function handle(Request $request, Closure $next)
$user = $this->auth->user();
/** @var ProjectModel|null $project */
$project = $request->route()->getParameter('project');
$issue = $request->route()->getParameter('issue');

// Check if user has the permission
// & if the user can access the current context (e.g. is one of the project users)
if (app('tinyissue.settings')->isPublicProjectsEnabled()
&& in_array($permission, $this->publicAccess)
&& $project instanceof ProjectModel && !$project->isPrivate()) {
// Ignore we are ok to view issues in public project
} elseif (!$this->auth->guest()
&& (!$user->permission($permission) || !$user->permissionInContext($request->route()))) {
} elseif (
!$this->auth->guest()
&& (
!$user->permission($permission) ||
!$user->permissionInContext($request->route()) ||
($project instanceof ProjectModel && $project->isPrivateInternal() && $user->isUser() && $issue && !$issue->isCreatedBy($user))
)
) {
abort(401);
}

Expand Down
70 changes: 69 additions & 1 deletion app/Model/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* @property string $name
* @property int $status
* @property int $default_assignee
* @property int $private
* @property Project\Issue[] $issues
* @property int $openIssuesCount
* @property int $closedIssuesCount
Expand All @@ -37,6 +38,13 @@ class Project extends Model
Traits\Project\CrudTrait,
Traits\Project\QueryTrait;

/**
* Project private & user role can see their own issues only.
*
* @var int
*/
const INTERNAL_YES = 2;

/**
* Project not public to view and create issue.
*
Expand Down Expand Up @@ -93,6 +101,28 @@ class Project extends Model
*/
protected $fillable = ['name', 'default_assignee', 'status', 'private'];

/**
* List of HTML classes for each status.
*
* @var array
*/
protected $attrClassNames = [
self::PRIVATE_NO => 'note',
self::PRIVATE_YES => 'info',
self::INTERNAL_YES => 'primary',
];

/**
* List of statuses names.
*
* @var array
*/
protected $statusesNames = [
self::PRIVATE_NO => 'public',
self::PRIVATE_YES => 'private',
self::INTERNAL_YES => 'internal',
];

/**
* Generate a URL for the active project.
*
Expand Down Expand Up @@ -209,6 +239,44 @@ public function isMember($userId)
*/
public function isPrivate()
{
return $this->private === true;
return $this->private === self::PRIVATE_YES;
}

/**
* Whether or not the project is private internal.
*
* @return bool
*/
public function isPrivateInternal()
{
return $this->private === self::INTERNAL_YES;
}

/**
* Returns project status as string name.
*
* @return string
*/
public function getStatusAsName()
{
if (array_key_exists((int) $this->private, $this->statusesNames)) {
return $this->statusesNames[(int) $this->private];
}

return '';
}

/**
* Returns the class name to be used for project status.
*
* @return string
*/
public function getStatusClass()
{
if (array_key_exists((int) $this->private, $this->attrClassNames)) {
return $this->attrClassNames[(int) $this->private];
}

return '';
}
}
12 changes: 12 additions & 0 deletions app/Model/Project/Issue.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,16 @@ public function canUserViewQuote(Model\User $user = null)

return false;
}

/**
* Whether or not a user is the creator of the issue.
*
* @param Model\User $user
*
* @return bool
*/
public function isCreatedBy(Model\User $user)
{
return $this->created_by === $user->id;
}
}
4 changes: 3 additions & 1 deletion app/Model/Traits/Project/CountTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ trait CountTrait
*/
public function countPrivateProjects()
{
return $this->where('private', '=', Project::PRIVATE_YES)->count();
return $this
->where('private', '=', Project::PRIVATE_YES)
->orWhere('private', '=', Project::INTERNAL_YES)->count();
}

/**
Expand Down
16 changes: 16 additions & 0 deletions app/Model/Traits/Project/FilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,20 @@ public function filterTitleOrBody(Relations\HasMany $query, $keyword)
});
}
}

/**
* Filter by created by.
*
* @param Relations\HasMany $query
* @param int $userId
* @param bool $enabled
*
* @return void
*/
public function filterCreatedBy(Relations\HasMany $query, $userId, $enabled = false)
{
if (true === $enabled && $userId > 0) {
$query->where('created_by', '=', $userId);
}
}
}
1 change: 1 addition & 0 deletions app/Model/Traits/Project/QueryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public function listIssues($status = Project\Issue::STATUS_OPEN, array $filter =
$this->filterTitleOrBody($query, array_get($filter, 'keyword'));
$this->filterTags($query, array_get($filter, 'tag_status'));
$this->filterTags($query, array_get($filter, 'tag_type'));
$this->filterCreatedBy($query, array_get($filter, 'created_by'), $this->isPrivateInternal());

// Sort
if ($sortBy == 'updated') {
Expand Down
16 changes: 15 additions & 1 deletion app/Model/Traits/User/QueryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,21 @@ public function projectsWidthActivities($status = Project::STATUS_OPEN)
->with([
'activities' => function (Relations\Relation $query) {
$query->with('activity', 'issue', 'user', 'assignTo', 'comment', 'note');
$query->orderBy('created_at', 'DESC');
$query->orderBy('users_activity.created_at', 'DESC');

// For logged users with role User, show issues that are created by them in internal projects
// of issue create by any for other project statuses
if (auth()->user()->isUser()) {
$query->join('projects_issues', 'projects_issues.id', '=', 'item_id');
$query->join('projects', 'projects.id', '=', 'parent_id');
$query->where(function (Eloquent\Builder $query) {
$query->where(function (Eloquent\Builder $query) {
$query->where('created_by', '=', auth()->user()->id);
$query->where('private', '=', Project::INTERNAL_YES);
});
$query->orWhere('private', '<>', Project::INTERNAL_YES);
});
}
},
]);
}
Expand Down
2 changes: 1 addition & 1 deletion app/Model/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,6 @@ public function isBlocked()
*/
public function isUser()
{
return $this->role->role === Role::ROLE_USER;
return $this->exists && $this->role->role === Role::ROLE_USER;
}
}
1 change: 1 addition & 0 deletions resources/lang/en/tinyissue.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,5 @@
'your_created_issues_description' => 'Issues that are created by you',
'issue_created_by_you' => 'Issue Created By You',
'readonly_issue_message' => 'The issue is in read only status.',
'internal' => 'Internal',
];
10 changes: 3 additions & 7 deletions resources/views/projects/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,9 @@

<span>{{ $project->openIssuesCount }} @lang('tinyissue.open_issue' . ($project->openIssuesCount <= 1? '' : 's'))</span>
@if(!Auth::guest())
<span class="pull-right label @if($project->private) label-info @else label-note @endif">
@if($project->private)
@lang('tinyissue.private')
@else
@lang('tinyissue.public')
@endif
</span>
<span class="pull-right label label-{{ $project->getStatusClass() }}">
@lang('tinyissue.' . $project->getStatusAsName())
</span>
@endif
</li>
@endforeach
Expand Down

0 comments on commit be68983

Please sign in to comment.