From bf8e7f3393d48e6300c4d8775daeb40d55ea2017 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 16 May 2021 00:29:56 +0100 Subject: [PATCH] Started addition of favourite system --- app/Actions/Favourite.php | 17 +++++ app/Actions/View.php | 4 +- app/Entities/Models/Entity.php | 22 +++++- app/Http/Controllers/FavouriteController.php | 76 +++++++++++++++++++ app/Interfaces/Favouritable.php | 11 +++ ...1_05_15_173110_create_favourites_table.php | 36 +++++++++ resources/icons/star-circle.svg | 1 - resources/icons/star-outline.svg | 1 + resources/lang/en/activities.php | 4 + resources/lang/en/common.php | 2 + resources/views/pages/show.blade.php | 3 + .../entity-favourite-action.blade.php | 12 +++ routes/web.php | 5 ++ 13 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 app/Actions/Favourite.php create mode 100644 app/Http/Controllers/FavouriteController.php create mode 100644 app/Interfaces/Favouritable.php create mode 100644 database/migrations/2021_05_15_173110_create_favourites_table.php create mode 100644 resources/icons/star-outline.svg create mode 100644 resources/views/partials/entity-favourite-action.blade.php diff --git a/app/Actions/Favourite.php b/app/Actions/Favourite.php new file mode 100644 index 000000000..107a76578 --- /dev/null +++ b/app/Actions/Favourite.php @@ -0,0 +1,17 @@ +morphTo(); + } +} diff --git a/app/Actions/View.php b/app/Actions/View.php index e9841293b..c5ec6a38d 100644 --- a/app/Actions/View.php +++ b/app/Actions/View.php @@ -1,6 +1,7 @@ morphTo(); } diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index d4b477304..be63cec2b 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -2,6 +2,7 @@ use BookStack\Actions\Activity; use BookStack\Actions\Comment; +use BookStack\Actions\Favourite; use BookStack\Actions\Tag; use BookStack\Actions\View; use BookStack\Auth\Permissions\EntityPermission; @@ -9,6 +10,7 @@ use BookStack\Auth\Permissions\JointPermission; use BookStack\Entities\Tools\SearchIndex; use BookStack\Entities\Tools\SlugGenerator; use BookStack\Facades\Permissions; +use BookStack\Interfaces\Favouritable; use BookStack\Interfaces\Sluggable; use BookStack\Model; use BookStack\Traits\HasCreatorAndUpdater; @@ -38,7 +40,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @method static Builder withLastView() * @method static Builder withViewCount() */ -abstract class Entity extends Model implements Sluggable +abstract class Entity extends Model implements Sluggable, Favouritable { use SoftDeletes; use HasCreatorAndUpdater; @@ -297,4 +299,22 @@ abstract class Entity extends Model implements Sluggable $this->slug = app(SlugGenerator::class)->generate($this); return $this->slug; } + + /** + * @inheritdoc + */ + public function favourites(): MorphMany + { + return $this->morphMany(Favourite::class, 'favouritable'); + } + + /** + * Check if the entity is a favourite of the current user. + */ + public function isFavourite(): bool + { + return $this->favourites() + ->where('user_id', '=', user()->id) + ->exists(); + } } diff --git a/app/Http/Controllers/FavouriteController.php b/app/Http/Controllers/FavouriteController.php new file mode 100644 index 000000000..8a26eac8e --- /dev/null +++ b/app/Http/Controllers/FavouriteController.php @@ -0,0 +1,76 @@ +getValidatedModelFromRequest($request); + $favouritable->favourites()->firstOrCreate([ + 'user_id' => user()->id, + ]); + + $this->showSuccessNotification(trans('activities.favourite_add_notification', [ + 'name' => $favouritable->name, + ])); + return redirect()->back(); + } + + /** + * Remove an item as a favourite. + */ + public function remove(Request $request) + { + $favouritable = $this->getValidatedModelFromRequest($request); + $favouritable->favourites()->where([ + 'user_id' => user()->id, + ])->delete(); + + $this->showSuccessNotification(trans('activities.favourite_remove_notification', [ + 'name' => $favouritable->name, + ])); + return redirect()->back(); + } + + /** + * @throws \Illuminate\Validation\ValidationException + * @throws \Exception + */ + protected function getValidatedModelFromRequest(Request $request): Favouritable + { + $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']); + + $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/Interfaces/Favouritable.php b/app/Interfaces/Favouritable.php new file mode 100644 index 000000000..dd335feed --- /dev/null +++ b/app/Interfaces/Favouritable.php @@ -0,0 +1,11 @@ +increments('id'); + $table->integer('user_id')->index(); + $table->integer('favouritable_id'); + $table->string('favouritable_type', 100); + $table->timestamps(); + + $table->index(['favouritable_id', 'favouritable_type'], 'favouritable_index'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('favourites'); + } +} diff --git a/resources/icons/star-circle.svg b/resources/icons/star-circle.svg index 0e3f16879..a7667e48f 100644 --- a/resources/icons/star-circle.svg +++ b/resources/icons/star-circle.svg @@ -1,4 +1,3 @@ - \ No newline at end of file diff --git a/resources/icons/star-outline.svg b/resources/icons/star-outline.svg new file mode 100644 index 000000000..4e83ab42b --- /dev/null +++ b/resources/icons/star-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/lang/en/activities.php b/resources/lang/en/activities.php index fe937b061..5917de2cf 100644 --- a/resources/lang/en/activities.php +++ b/resources/lang/en/activities.php @@ -43,6 +43,10 @@ return [ 'bookshelf_delete' => 'deleted bookshelf', 'bookshelf_delete_notification' => 'Bookshelf Successfully Deleted', + // Favourites + 'favourite_add_notification' => '":name" has been added to your favourites', + 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Other 'commented_on' => 'commented on', 'permissions_update' => 'updated permissions', diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index 855c1c807..e198878ad 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -40,6 +40,8 @@ return [ 'remove' => 'Remove', 'add' => 'Add', 'fullscreen' => 'Fullscreen', + 'favourite' => 'Favourite', + 'unfavourite' => 'Unfavourite', // Sort Options 'sort_options' => 'Sort Options', diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 13125464a..73a107cf7 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -151,6 +151,9 @@
{{--Export--}} + @if(signedInUser()) + @include('partials.entity-favourite-action', ['entity' => $page, 'alreadyFavourite' => $page->isFavourite()]) + @endif @include('partials.entity-export-menu', ['entity' => $page]) diff --git a/resources/views/partials/entity-favourite-action.blade.php b/resources/views/partials/entity-favourite-action.blade.php new file mode 100644 index 000000000..49ba6aa5d --- /dev/null +++ b/resources/views/partials/entity-favourite-action.blade.php @@ -0,0 +1,12 @@ +@php + $isFavourite = $entity->isFavourite(); +@endphp +
+ {{ csrf_field() }} + + + +
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 730c795f8..69823b4ba 100644 --- a/routes/web.php +++ b/routes/web.php @@ -152,9 +152,14 @@ Route::group(['middleware' => 'auth'], function () { // User Search Route::get('/search/users/select', 'UserSearchController@forSelect'); + // Template System Route::get('/templates', 'PageTemplateController@list'); Route::get('/templates/{templateId}', 'PageTemplateController@get'); + // Favourites + Route::post('/favourites/add', 'FavouriteController@add'); + Route::post('/favourites/remove', 'FavouriteController@remove'); + // Other Pages Route::get('/', 'HomeController@index'); Route::get('/home', 'HomeController@index');