From 2fbf5527c70d5d3eadb2767ca5301ad05f7f28c8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 10 Sep 2023 15:18:31 +0100 Subject: [PATCH] Simplified and aligned handling of mixed entity endpoints Fixes #4444 --- .../Controllers/FavouriteController.php | 53 +++++-------------- app/Activity/Controllers/WatchController.php | 43 ++------------- .../Tools/MixedEntityRequestHelper.php | 39 ++++++++++++++ .../views/entities/favourite-action.blade.php | 2 +- .../views/entities/watch-action.blade.php | 2 +- .../views/entities/watch-controls.blade.php | 2 +- tests/Activity/WatchTest.php | 6 +-- tests/FavouriteTest.php | 8 +-- 8 files changed, 67 insertions(+), 88 deletions(-) create mode 100644 app/Entities/Tools/MixedEntityRequestHelper.php diff --git a/app/Activity/Controllers/FavouriteController.php b/app/Activity/Controllers/FavouriteController.php index 1b88ffd64..d2534ddfe 100644 --- a/app/Activity/Controllers/FavouriteController.php +++ b/app/Activity/Controllers/FavouriteController.php @@ -6,11 +6,17 @@ use BookStack\Activity\Models\Favouritable; use BookStack\App\Model; use BookStack\Entities\Models\Entity; use BookStack\Entities\Queries\TopFavourites; +use BookStack\Entities\Tools\MixedEntityRequestHelper; use BookStack\Http\Controller; use Illuminate\Http\Request; class FavouriteController extends Controller { + public function __construct( + protected MixedEntityRequestHelper $entityHelper, + ) { + } + /** * Show a listing of all favourite items for the current user. */ @@ -36,13 +42,14 @@ class FavouriteController extends Controller */ public function add(Request $request) { - $favouritable = $this->getValidatedModelFromRequest($request); - $favouritable->favourites()->firstOrCreate([ + $modelInfo = $this->validate($request, $this->entityHelper->validationRules()); + $entity = $this->entityHelper->getVisibleEntityFromRequestData($modelInfo); + $entity->favourites()->firstOrCreate([ 'user_id' => user()->id, ]); $this->showSuccessNotification(trans('activities.favourite_add_notification', [ - 'name' => $favouritable->name, + 'name' => $entity->name, ])); return redirect()->back(); @@ -53,48 +60,16 @@ class FavouriteController extends Controller */ public function remove(Request $request) { - $favouritable = $this->getValidatedModelFromRequest($request); - $favouritable->favourites()->where([ + $modelInfo = $this->validate($request, $this->entityHelper->validationRules()); + $entity = $this->entityHelper->getVisibleEntityFromRequestData($modelInfo); + $entity->favourites()->where([ 'user_id' => user()->id, ])->delete(); $this->showSuccessNotification(trans('activities.favourite_remove_notification', [ - 'name' => $favouritable->name, + 'name' => $entity->name, ])); 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; - } } diff --git a/app/Activity/Controllers/WatchController.php b/app/Activity/Controllers/WatchController.php index 3d7e18116..d63918fb3 100644 --- a/app/Activity/Controllers/WatchController.php +++ b/app/Activity/Controllers/WatchController.php @@ -3,25 +3,23 @@ namespace BookStack\Activity\Controllers; use BookStack\Activity\Tools\UserEntityWatchOptions; -use BookStack\App\Model; -use BookStack\Entities\Models\Entity; +use BookStack\Entities\Tools\MixedEntityRequestHelper; use BookStack\Http\Controller; -use Exception; use Illuminate\Http\Request; -use Illuminate\Validation\ValidationException; class WatchController extends Controller { - public function update(Request $request) + public function update(Request $request, MixedEntityRequestHelper $entityHelper) { $this->checkPermission('receive-notifications'); $this->preventGuestAccess(); $requestData = $this->validate($request, [ 'level' => ['required', 'string'], + ...$entityHelper->validationRules() ]); - $watchable = $this->getValidatedModelFromRequest($request); + $watchable = $entityHelper->getVisibleEntityFromRequestData($requestData); $watchOptions = new UserEntityWatchOptions(user(), $watchable); $watchOptions->updateLevelByName($requestData['level']); @@ -29,37 +27,4 @@ class WatchController extends Controller 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; - } } diff --git a/app/Entities/Tools/MixedEntityRequestHelper.php b/app/Entities/Tools/MixedEntityRequestHelper.php new file mode 100644 index 000000000..8319f6aa0 --- /dev/null +++ b/app/Entities/Tools/MixedEntityRequestHelper.php @@ -0,0 +1,39 @@ +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'], + ]; + } +} diff --git a/resources/views/entities/favourite-action.blade.php b/resources/views/entities/favourite-action.blade.php index 35189044b..e596fbdce 100644 --- a/resources/views/entities/favourite-action.blade.php +++ b/resources/views/entities/favourite-action.blade.php @@ -3,7 +3,7 @@ @endphp
{{ csrf_field() }} - +