mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-02 05:45:13 +08:00
parent
3928cbac18
commit
2fbf5527c7
@ -6,11 +6,17 @@ use BookStack\Activity\Models\Favouritable;
|
|||||||
use BookStack\App\Model;
|
use BookStack\App\Model;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Entities\Queries\TopFavourites;
|
use BookStack\Entities\Queries\TopFavourites;
|
||||||
|
use BookStack\Entities\Tools\MixedEntityRequestHelper;
|
||||||
use BookStack\Http\Controller;
|
use BookStack\Http\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class FavouriteController extends Controller
|
class FavouriteController extends Controller
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected MixedEntityRequestHelper $entityHelper,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a listing of all favourite items for the current user.
|
* Show a listing of all favourite items for the current user.
|
||||||
*/
|
*/
|
||||||
@ -36,13 +42,14 @@ class FavouriteController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function add(Request $request)
|
public function add(Request $request)
|
||||||
{
|
{
|
||||||
$favouritable = $this->getValidatedModelFromRequest($request);
|
$modelInfo = $this->validate($request, $this->entityHelper->validationRules());
|
||||||
$favouritable->favourites()->firstOrCreate([
|
$entity = $this->entityHelper->getVisibleEntityFromRequestData($modelInfo);
|
||||||
|
$entity->favourites()->firstOrCreate([
|
||||||
'user_id' => user()->id,
|
'user_id' => user()->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->showSuccessNotification(trans('activities.favourite_add_notification', [
|
$this->showSuccessNotification(trans('activities.favourite_add_notification', [
|
||||||
'name' => $favouritable->name,
|
'name' => $entity->name,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
@ -53,48 +60,16 @@ class FavouriteController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function remove(Request $request)
|
public function remove(Request $request)
|
||||||
{
|
{
|
||||||
$favouritable = $this->getValidatedModelFromRequest($request);
|
$modelInfo = $this->validate($request, $this->entityHelper->validationRules());
|
||||||
$favouritable->favourites()->where([
|
$entity = $this->entityHelper->getVisibleEntityFromRequestData($modelInfo);
|
||||||
|
$entity->favourites()->where([
|
||||||
'user_id' => user()->id,
|
'user_id' => user()->id,
|
||||||
])->delete();
|
])->delete();
|
||||||
|
|
||||||
$this->showSuccessNotification(trans('activities.favourite_remove_notification', [
|
$this->showSuccessNotification(trans('activities.favourite_remove_notification', [
|
||||||
'name' => $favouritable->name,
|
'name' => $entity->name,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function getValidatedModelFromRequest(Request $request): Entity
|
|
||||||
{
|
|
||||||
$modelInfo = $this->validate($request, [
|
|
||||||
'type' => ['required', 'string'],
|
|
||||||
'id' => ['required', 'integer'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!class_exists($modelInfo['type'])) {
|
|
||||||
throw new \Exception('Model not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Model $model */
|
|
||||||
$model = new $modelInfo['type']();
|
|
||||||
if (!$model instanceof Favouritable) {
|
|
||||||
throw new \Exception('Model not favouritable');
|
|
||||||
}
|
|
||||||
|
|
||||||
$modelInstance = $model->newQuery()
|
|
||||||
->where('id', '=', $modelInfo['id'])
|
|
||||||
->first(['id', 'name', 'owned_by']);
|
|
||||||
|
|
||||||
$inaccessibleEntity = ($modelInstance instanceof Entity && !userCan('view', $modelInstance));
|
|
||||||
if (is_null($modelInstance) || $inaccessibleEntity) {
|
|
||||||
throw new \Exception('Model instance not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $modelInstance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,25 +3,23 @@
|
|||||||
namespace BookStack\Activity\Controllers;
|
namespace BookStack\Activity\Controllers;
|
||||||
|
|
||||||
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
||||||
use BookStack\App\Model;
|
use BookStack\Entities\Tools\MixedEntityRequestHelper;
|
||||||
use BookStack\Entities\Models\Entity;
|
|
||||||
use BookStack\Http\Controller;
|
use BookStack\Http\Controller;
|
||||||
use Exception;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
|
|
||||||
class WatchController extends Controller
|
class WatchController extends Controller
|
||||||
{
|
{
|
||||||
public function update(Request $request)
|
public function update(Request $request, MixedEntityRequestHelper $entityHelper)
|
||||||
{
|
{
|
||||||
$this->checkPermission('receive-notifications');
|
$this->checkPermission('receive-notifications');
|
||||||
$this->preventGuestAccess();
|
$this->preventGuestAccess();
|
||||||
|
|
||||||
$requestData = $this->validate($request, [
|
$requestData = $this->validate($request, [
|
||||||
'level' => ['required', 'string'],
|
'level' => ['required', 'string'],
|
||||||
|
...$entityHelper->validationRules()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$watchable = $this->getValidatedModelFromRequest($request);
|
$watchable = $entityHelper->getVisibleEntityFromRequestData($requestData);
|
||||||
$watchOptions = new UserEntityWatchOptions(user(), $watchable);
|
$watchOptions = new UserEntityWatchOptions(user(), $watchable);
|
||||||
$watchOptions->updateLevelByName($requestData['level']);
|
$watchOptions->updateLevelByName($requestData['level']);
|
||||||
|
|
||||||
@ -29,37 +27,4 @@ class WatchController extends Controller
|
|||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws ValidationException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
protected function getValidatedModelFromRequest(Request $request): Entity
|
|
||||||
{
|
|
||||||
$modelInfo = $this->validate($request, [
|
|
||||||
'type' => ['required', 'string'],
|
|
||||||
'id' => ['required', 'integer'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!class_exists($modelInfo['type'])) {
|
|
||||||
throw new Exception('Model not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Model $model */
|
|
||||||
$model = new $modelInfo['type']();
|
|
||||||
if (!$model instanceof Entity) {
|
|
||||||
throw new Exception('Model not an entity');
|
|
||||||
}
|
|
||||||
|
|
||||||
$modelInstance = $model->newQuery()
|
|
||||||
->where('id', '=', $modelInfo['id'])
|
|
||||||
->first(['id', 'name', 'owned_by']);
|
|
||||||
|
|
||||||
$inaccessibleEntity = ($modelInstance instanceof Entity && !userCan('view', $modelInstance));
|
|
||||||
if (is_null($modelInstance) || $inaccessibleEntity) {
|
|
||||||
throw new Exception('Model instance not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $modelInstance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
39
app/Entities/Tools/MixedEntityRequestHelper.php
Normal file
39
app/Entities/Tools/MixedEntityRequestHelper.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Entities\Tools;
|
||||||
|
|
||||||
|
use BookStack\Entities\EntityProvider;
|
||||||
|
use BookStack\Entities\Models\Entity;
|
||||||
|
|
||||||
|
class MixedEntityRequestHelper
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected EntityProvider $entities,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query out an entity, visible to the current user, for the given
|
||||||
|
* entity request details (this provided in a request validated by
|
||||||
|
* this classes' validationRules method).
|
||||||
|
* @param array{type: string, id: string} $requestData
|
||||||
|
*/
|
||||||
|
public function getVisibleEntityFromRequestData(array $requestData): Entity
|
||||||
|
{
|
||||||
|
$entityType = $this->entities->get($requestData['type']);
|
||||||
|
|
||||||
|
return $entityType->newQuery()->scopes(['visible'])->findOrFail($requestData['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules for an abstract entity request.
|
||||||
|
* @return array{type: string[], id: string[]}
|
||||||
|
*/
|
||||||
|
public function validationRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => ['required', 'string'],
|
||||||
|
'id' => ['required', 'integer'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
@endphp
|
@endphp
|
||||||
<form action="{{ url('/favourites/' . ($isFavourite ? 'remove' : 'add')) }}" method="POST">
|
<form action="{{ url('/favourites/' . ($isFavourite ? 'remove' : 'add')) }}" method="POST">
|
||||||
{{ csrf_field() }}
|
{{ csrf_field() }}
|
||||||
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
<input type="hidden" name="type" value="{{ $entity->getMorphClass() }}">
|
||||||
<input type="hidden" name="id" value="{{ $entity->id }}">
|
<input type="hidden" name="id" value="{{ $entity->id }}">
|
||||||
<button type="submit" data-shortcut="favourite" class="icon-list-item text-link">
|
<button type="submit" data-shortcut="favourite" class="icon-list-item text-link">
|
||||||
<span>@icon($isFavourite ? 'star' : 'star-outline')</span>
|
<span>@icon($isFavourite ? 'star' : 'star-outline')</span>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<form action="{{ url('/watching/update') }}" method="POST">
|
<form action="{{ url('/watching/update') }}" method="POST">
|
||||||
{{ csrf_field() }}
|
{{ csrf_field() }}
|
||||||
{{ method_field('PUT') }}
|
{{ method_field('PUT') }}
|
||||||
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
<input type="hidden" name="type" value="{{ $entity->getMorphClass() }}">
|
||||||
<input type="hidden" name="id" value="{{ $entity->id }}">
|
<input type="hidden" name="id" value="{{ $entity->id }}">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
name="level"
|
name="level"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<form action="{{ url('/watching/update') }}" method="POST">
|
<form action="{{ url('/watching/update') }}" method="POST">
|
||||||
{{ method_field('PUT') }}
|
{{ method_field('PUT') }}
|
||||||
{{ csrf_field() }}
|
{{ csrf_field() }}
|
||||||
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
<input type="hidden" name="type" value="{{ $entity->getMorphClass() }}">
|
||||||
<input type="hidden" name="id" value="{{ $entity->id }}">
|
<input type="hidden" name="id" value="{{ $entity->id }}">
|
||||||
|
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu xl-limited anchor-left pb-none">
|
<ul refs="dropdown@menu" class="dropdown-menu xl-limited anchor-left pb-none">
|
||||||
|
@ -66,7 +66,7 @@ class WatchTest extends TestCase
|
|||||||
|
|
||||||
$this->actingAs($editor)->get($book->getUrl());
|
$this->actingAs($editor)->get($book->getUrl());
|
||||||
$resp = $this->put('/watching/update', [
|
$resp = $this->put('/watching/update', [
|
||||||
'type' => get_class($book),
|
'type' => $book->getMorphClass(),
|
||||||
'id' => $book->id,
|
'id' => $book->id,
|
||||||
'level' => 'comments'
|
'level' => 'comments'
|
||||||
]);
|
]);
|
||||||
@ -81,7 +81,7 @@ class WatchTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$resp = $this->put('/watching/update', [
|
$resp = $this->put('/watching/update', [
|
||||||
'type' => get_class($book),
|
'type' => $book->getMorphClass(),
|
||||||
'id' => $book->id,
|
'id' => $book->id,
|
||||||
'level' => 'default'
|
'level' => 'default'
|
||||||
]);
|
]);
|
||||||
@ -101,7 +101,7 @@ class WatchTest extends TestCase
|
|||||||
$book = $this->entities->book();
|
$book = $this->entities->book();
|
||||||
|
|
||||||
$resp = $this->put('/watching/update', [
|
$resp = $this->put('/watching/update', [
|
||||||
'type' => get_class($book),
|
'type' => $book->getMorphClass(),
|
||||||
'id' => $book->id,
|
'id' => $book->id,
|
||||||
'level' => 'comments'
|
'level' => 'comments'
|
||||||
]);
|
]);
|
||||||
|
@ -14,10 +14,10 @@ class FavouriteTest extends TestCase
|
|||||||
|
|
||||||
$resp = $this->actingAs($editor)->get($page->getUrl());
|
$resp = $this->actingAs($editor)->get($page->getUrl());
|
||||||
$this->withHtml($resp)->assertElementContains('button', 'Favourite');
|
$this->withHtml($resp)->assertElementContains('button', 'Favourite');
|
||||||
$this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/add"]');
|
$this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/add"] input[name="type"][value="page"]');
|
||||||
|
|
||||||
$resp = $this->post('/favourites/add', [
|
$resp = $this->post('/favourites/add', [
|
||||||
'type' => get_class($page),
|
'type' => $page->getMorphClass(),
|
||||||
'id' => $page->id,
|
'id' => $page->id,
|
||||||
]);
|
]);
|
||||||
$resp->assertRedirect($page->getUrl());
|
$resp->assertRedirect($page->getUrl());
|
||||||
@ -45,7 +45,7 @@ class FavouriteTest extends TestCase
|
|||||||
$this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/remove"]');
|
$this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/remove"]');
|
||||||
|
|
||||||
$resp = $this->post('/favourites/remove', [
|
$resp = $this->post('/favourites/remove', [
|
||||||
'type' => get_class($page),
|
'type' => $page->getMorphClass(),
|
||||||
'id' => $page->id,
|
'id' => $page->id,
|
||||||
]);
|
]);
|
||||||
$resp->assertRedirect($page->getUrl());
|
$resp->assertRedirect($page->getUrl());
|
||||||
@ -67,7 +67,7 @@ class FavouriteTest extends TestCase
|
|||||||
|
|
||||||
$this->actingAs($user)->get($book->getUrl());
|
$this->actingAs($user)->get($book->getUrl());
|
||||||
$resp = $this->post('/favourites/add', [
|
$resp = $this->post('/favourites/add', [
|
||||||
'type' => get_class($book),
|
'type' => $book->getMorphClass(),
|
||||||
'id' => $book->id,
|
'id' => $book->id,
|
||||||
]);
|
]);
|
||||||
$resp->assertRedirect($book->getUrl());
|
$resp->assertRedirect($book->getUrl());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user