Started change for entities to have concept of owners

This commit is contained in:
Dan Brown 2020-12-30 18:25:35 +00:00
parent c71f00b2ec
commit b493becadf
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
12 changed files with 151 additions and 57 deletions

View File

@ -1,6 +1,8 @@
<?php namespace BookStack\Actions;
use BookStack\Ownable;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property string text
@ -8,25 +10,25 @@ use BookStack\Ownable;
* @property int|null parent_id
* @property int local_id
*/
class Comment extends Ownable
class Comment extends Model
{
use HasCreatorAndUpdater;
protected $fillable = ['text', 'parent_id'];
protected $appends = ['created', 'updated'];
/**
* Get the entity that this comment belongs to
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function entity()
public function entity(): MorphTo
{
return $this->morphTo('entity');
}
/**
* Check if a comment has been updated since creation.
* @return bool
*/
public function isUpdated()
public function isUpdated(): bool
{
return $this->updated_at->timestamp > $this->created_at->timestamp;
}

View File

@ -5,7 +5,9 @@ use BookStack\Auth\Role;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\EntityProvider;
use BookStack\Ownable;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\Traits\HasOwner;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
@ -168,7 +170,7 @@ class PermissionService
});
// Chunk through all bookshelves
$this->entityProvider->bookshelf->newQuery()->withTrashed()->select(['id', 'restricted', 'created_by'])
$this->entityProvider->bookshelf->newQuery()->withTrashed()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
@ -181,10 +183,10 @@ class PermissionService
protected function bookFetchQuery()
{
return $this->entityProvider->book->withTrashed()->newQuery()
->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
$query->withTrashed()->select(['id', 'restricted', 'created_by', 'book_id']);
->select(['id', 'restricted', 'owned_by'])->with(['chapters' => function ($query) {
$query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id']);
}, 'pages' => function ($query) {
$query->withTrashed()->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
$query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']);
}]);
}
@ -286,7 +288,7 @@ class PermissionService
});
// Chunk through all bookshelves
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
@ -508,28 +510,24 @@ class PermissionService
'action' => $action,
'has_permission' => $permissionAll,
'has_permission_own' => $permissionOwn,
'created_by' => $entity->getRawAttribute('created_by')
'owned_by' => $entity->getRawAttribute('owned_by')
];
}
/**
* Checks if an entity has a restriction set upon it.
* @param Ownable $ownable
* @param $permission
* @return bool
* @param HasCreatorAndUpdater|HasOwner $ownable
*/
public function checkOwnableUserAccess(Ownable $ownable, $permission)
public function checkOwnableUserAccess(Model $ownable, string $permission): bool
{
$explodedPermission = explode('-', $permission);
$baseQuery = $ownable->where('id', '=', $ownable->id);
$baseQuery = $ownable->newQuery()->where('id', '=', $ownable->id);
$action = end($explodedPermission);
$this->currentAction = $action;
$nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
// Handle non entity specific jointPermissions
if (in_array($explodedPermission[0], $nonJointPermissions)) {
if (!($ownable instanceof Entity)) {
$allPermission = $this->currentUser() && $this->currentUser()->can($permission . '-all');
$ownPermission = $this->currentUser() && $this->currentUser()->can($permission . '-own');
$this->currentAction = 'view';
@ -566,7 +564,7 @@ class PermissionService
$query->where('has_permission', '=', 1)
->orWhere(function ($query2) use ($userId) {
$query2->where('has_permission_own', '=', 1)
->where('created_by', '=', $userId);
->where('owned_by', '=', $userId);
});
});
@ -615,7 +613,7 @@ class PermissionService
$query->where('has_permission', '=', true)
->orWhere(function ($query) {
$query->where('has_permission_own', '=', true)
->where('created_by', '=', $this->currentUser()->id);
->where('owned_by', '=', $this->currentUser()->id);
});
});
});
@ -639,7 +637,7 @@ class PermissionService
$query->where('has_permission', '=', true)
->orWhere(function (Builder $query) {
$query->where('has_permission_own', '=', true)
->where('created_by', '=', $this->currentUser()->id);
->where('owned_by', '=', $this->currentUser()->id);
});
});
});
@ -656,7 +654,7 @@ class PermissionService
$query->where('draft', '=', false)
->orWhere(function (Builder $query) {
$query->where('draft', '=', true)
->where('created_by', '=', $this->currentUser()->id);
->where('owned_by', '=', $this->currentUser()->id);
});
});
}
@ -676,7 +674,7 @@ class PermissionService
$query->where('draft', '=', false)
->orWhere(function ($query) {
$query->where('draft', '=', true)
->where('created_by', '=', $this->currentUser()->id);
->where('owned_by', '=', $this->currentUser()->id);
});
});
}
@ -710,7 +708,7 @@ class PermissionService
->where(function ($query) {
$query->where('has_permission', '=', true)->orWhere(function ($query) {
$query->where('has_permission_own', '=', true)
->where('created_by', '=', $this->currentUser()->id);
->where('owned_by', '=', $this->currentUser()->id);
});
});
});
@ -746,7 +744,7 @@ class PermissionService
->where(function ($query) {
$query->where('has_permission', '=', true)->orWhere(function ($query) {
$query->where('has_permission_own', '=', true)
->where('created_by', '=', $this->currentUser()->id);
->where('owned_by', '=', $this->currentUser()->id);
});
});
});

View File

@ -9,7 +9,9 @@ use BookStack\Auth\Permissions\JointPermission;
use BookStack\Entities\Tools\SearchIndex;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Facades\Permissions;
use BookStack\Ownable;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\Traits\HasOwner;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
@ -35,9 +37,11 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static Builder withLastView()
* @method static Builder withViewCount()
*/
abstract class Entity extends Ownable
abstract class Entity extends Model
{
use SoftDeletes;
use HasCreatorAndUpdater;
use HasOwner;
/**
* @var string - Name of property where the main text content is found

View File

@ -4,7 +4,8 @@ namespace BookStack\Http\Controllers;
use BookStack\Facades\Activity;
use BookStack\Interfaces\Loggable;
use BookStack\Ownable;
use BookStack\HasCreatorAndUpdater;
use BookStack\Model;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Exceptions\HttpResponseException;
@ -72,7 +73,7 @@ abstract class Controller extends BaseController
/**
* Check the current user's permissions against an ownable item otherwise throw an exception.
*/
protected function checkOwnablePermission(string $permission, Ownable $ownable): void
protected function checkOwnablePermission(string $permission, Model $ownable): void
{
if (!userCan($permission, $ownable)) {
$this->showPermissionError();

View File

@ -1,27 +1,26 @@
<?php namespace BookStack;
<?php namespace BookStack\Traits;
use BookStack\Auth\User;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int created_by
* @property int updated_by
*/
abstract class Ownable extends Model
trait HasCreatorAndUpdater
{
/**
* Relation for the user that created this entity.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function createdBy()
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
/**
* Relation for the user that updated this entity.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function updatedBy()
public function updatedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'updated_by');
}

19
app/Traits/HasOwner.php Normal file
View File

@ -0,0 +1,19 @@
<?php namespace BookStack\Traits;
use BookStack\Auth\User;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int owned_by
*/
trait HasOwner
{
/**
* Relation for the user that owns this entity.
*/
public function ownedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'owned_by');
}
}

View File

@ -1,7 +1,8 @@
<?php namespace BookStack\Uploads;
use BookStack\Entities\Models\Page;
use BookStack\Ownable;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
/**
* @property int id
@ -10,8 +11,10 @@ use BookStack\Ownable;
* @property string extension
* @property bool external
*/
class Attachment extends Ownable
class Attachment extends Model
{
use HasCreatorAndUpdater;
protected $fillable = ['name', 'order'];
/**

View File

@ -1,11 +1,13 @@
<?php namespace BookStack\Uploads;
use BookStack\Entities\Models\Page;
use BookStack\Ownable;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
use Images;
class Image extends Ownable
class Image extends Model
{
use HasCreatorAndUpdater;
protected $fillable = ['name'];
protected $hidden = [];

View File

@ -2,7 +2,7 @@
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\User;
use BookStack\Ownable;
use BookStack\Model;
use BookStack\Settings\SettingService;
/**
@ -56,7 +56,7 @@ function hasAppAccess(): bool
* Check if the current user has a permission. If an ownable element
* is passed in the jointPermissions are checked against that particular item.
*/
function userCan(string $permission, Ownable $ownable = null): bool
function userCan(string $permission, Model $ownable = null): bool
{
if ($ownable === null) {
return user() && user()->can($permission);

View File

@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class AddOwnedByFieldToEntities extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$tables = ['pages', 'books', 'chapters', 'bookshelves'];
foreach ($tables as $table) {
Schema::table($table, function (Blueprint $table) {
$table->integer('owned_by')->unsigned()->index();
});
DB::table($table)->update(['owned_by' => DB::raw('`created_by`')]);
}
Schema::table('joint_permissions', function (Blueprint $table) {
$table->renameColumn('created_by', 'owned_by');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$tables = ['pages', 'books', 'chapters', 'bookshelves'];
foreach ($tables as $table) {
Schema::table($table, function (Blueprint $table) {
$table->dropColumn('owned_by');
});
}
Schema::table('joint_permissions', function (Blueprint $table) {
$table->renameColumn('owned_by', 'created_by');
});
}
}

View File

@ -22,6 +22,7 @@ return [
'meta_created_name' => 'Created :timeLength by :user',
'meta_updated' => 'Updated :timeLength',
'meta_updated_name' => 'Updated :timeLength by :user',
'meta_owned_name' => 'Owned by :user',
'entity_select' => 'Entity Select',
'images' => 'Images',
'my_recent_drafts' => 'My Recent Drafts',

View File

@ -1,34 +1,50 @@
<div class="entity-meta">
@if($entity->isA('revision'))
@icon('history'){{ trans('entities.pages_revision') }}
{{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
<br>
<div>
@icon('history'){{ trans('entities.pages_revision') }}
{{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
</div>
@endif
@if ($entity->isA('page'))
@if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
@icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }} <br>
<div>
@if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
@icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
@if (userCan('page-update', $entity))</a>@endif
</div>
@endif
@if ($entity->ownedBy && $entity->ownedBy->id !== $entity->createdBy->id)
<div>
@icon('user'){!! trans('entities.meta_owned_name', [
'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
]) !!}
</div>
@endif
@if ($entity->createdBy)
@icon('star'){!! trans('entities.meta_created_name', [
<div>
@icon('star'){!! trans('entities.meta_created_name', [
'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".htmlentities($entity->createdBy->name). "</a>"
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
]) !!}
</div>
@else
@icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
<div>
@icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
</div>
@endif
<br>
@if ($entity->updatedBy)
@icon('edit'){!! trans('entities.meta_updated_name', [
<div>
@icon('edit'){!! trans('entities.meta_updated_name', [
'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".htmlentities($entity->updatedBy->name). "</a>"
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
]) !!}
</div>
@elseif (!$entity->isA('revision'))
@icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
<div>
@icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
</div>
@endif
</div>