From ea55b7f141a70e2d6edb209f6c48610a9c005e4e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 21 Nov 2015 17:22:14 +0000 Subject: [PATCH] Added view count tracking with personalised lists --- app/Console/Commands/ResetViews.php | 41 ++++++++++ app/Console/Kernel.php | 1 + app/Entity.php | 21 ++++- app/Http/Controllers/BookController.php | 5 +- app/Http/Controllers/ChapterController.php | 2 + app/Http/Controllers/HomeController.php | 15 ++-- app/Http/Controllers/PageController.php | 2 + app/Providers/CustomFacadeProvider.php | 5 ++ app/Repos/BookRepo.php | 55 ++++++++++++- app/Services/ActivityService.php | 2 +- app/Services/Facades/Views.php | 13 ++++ app/Services/ViewService.php | 77 +++++++++++++++++++ app/View.php | 20 +++++ config/app.php | 1 + .../2015_11_21_145609_create_views_table.php | 34 ++++++++ resources/views/books/index.blade.php | 12 ++- resources/views/home.blade.php | 18 ++--- .../views/partials/entity-list.blade.php | 17 ++++ 18 files changed, 311 insertions(+), 30 deletions(-) create mode 100644 app/Console/Commands/ResetViews.php create mode 100644 app/Services/Facades/Views.php create mode 100644 app/Services/ViewService.php create mode 100644 app/View.php create mode 100644 database/migrations/2015_11_21_145609_create_views_table.php create mode 100644 resources/views/partials/entity-list.blade.php diff --git a/app/Console/Commands/ResetViews.php b/app/Console/Commands/ResetViews.php new file mode 100644 index 000000000..3a3903ff8 --- /dev/null +++ b/app/Console/Commands/ResetViews.php @@ -0,0 +1,41 @@ +morphMany('BookStack\Activity', 'entity')->orderBy('created_at', 'desc'); } + /** + * Get View objects for this entity. + * @return mixed + */ + public function views() + { + return $this->morphMany('BookStack\View', 'viewable'); + } + + /** + * Get just the views for the current user. + * @return mixed + */ + public function userViews() + { + return $this->views()->where('user_id', '=', auth()->user()->id); + } + /** * Allows checking of the exact class, Used to check entity type. * Cleaner method for is_a. diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index eb9bf7aba..ff172f3b2 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -11,6 +11,7 @@ use BookStack\Http\Requests; use BookStack\Repos\BookRepo; use BookStack\Repos\ChapterRepo; use BookStack\Repos\PageRepo; +use Views; class BookController extends Controller { @@ -41,7 +42,8 @@ class BookController extends Controller public function index() { $books = $this->bookRepo->getAllPaginated(10); - return view('books/index', ['books' => $books]); + $recents = $this->bookRepo->getRecentlyViewed(10, 0); + return view('books/index', ['books' => $books, 'recents' => $recents]); } /** @@ -86,6 +88,7 @@ class BookController extends Controller public function show($slug) { $book = $this->bookRepo->getBySlug($slug); + Views::add($book); return view('books/show', ['book' => $book, 'current' => $book]); } diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index f7b4f6dab..a8076cdf2 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -10,6 +10,7 @@ use BookStack\Http\Requests; use BookStack\Http\Controllers\Controller; use BookStack\Repos\BookRepo; use BookStack\Repos\ChapterRepo; +use Views; class ChapterController extends Controller { @@ -79,6 +80,7 @@ class ChapterController extends Controller { $book = $this->bookRepo->getBySlug($bookSlug); $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); + Views::add($chapter); return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index d0eb16399..3b04a1f41 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,13 +2,12 @@ namespace BookStack\Http\Controllers; +use Activity; use Illuminate\Http\Request; use BookStack\Http\Requests; -use BookStack\Http\Controllers\Controller; use BookStack\Repos\BookRepo; -use BookStack\Services\ActivityService; -use BookStack\Services\Facades\Activity; +use Views; class HomeController extends Controller { @@ -18,12 +17,10 @@ class HomeController extends Controller /** * HomeController constructor. - * @param ActivityService $activityService * @param BookRepo $bookRepo */ - public function __construct(ActivityService $activityService, BookRepo $bookRepo) + public function __construct(BookRepo $bookRepo) { - $this->activityService = $activityService; $this->bookRepo = $bookRepo; parent::__construct(); } @@ -36,9 +33,9 @@ class HomeController extends Controller */ public function index() { - $books = $this->bookRepo->getAll(10); - $activity = $this->activityService->latest(); - return view('home', ['books' => $books, 'activity' => $activity]); + $activity = Activity::latest(); + $recentlyViewed = Views::getUserRecentlyViewed(10, 0); + return view('home', ['activity' => $activity, 'recents' => $recentlyViewed]); } } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 16a44e4c5..006d84f6e 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -10,6 +10,7 @@ use BookStack\Http\Requests; use BookStack\Repos\BookRepo; use BookStack\Repos\ChapterRepo; use BookStack\Repos\PageRepo; +use Views; class PageController extends Controller { @@ -86,6 +87,7 @@ class PageController extends Controller { $book = $this->bookRepo->getBySlug($bookSlug); $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + Views::add($page); return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page]); } diff --git a/app/Providers/CustomFacadeProvider.php b/app/Providers/CustomFacadeProvider.php index e99b236c4..bd4b2b515 100644 --- a/app/Providers/CustomFacadeProvider.php +++ b/app/Providers/CustomFacadeProvider.php @@ -2,6 +2,7 @@ namespace BookStack\Providers; +use BookStack\Services\ViewService; use Illuminate\Support\ServiceProvider; use BookStack\Services\ActivityService; use BookStack\Services\SettingService; @@ -29,6 +30,10 @@ class CustomFacadeProvider extends ServiceProvider return new ActivityService($this->app->make('BookStack\Activity')); }); + $this->app->bind('views', function() { + return new ViewService($this->app->make('BookStack\View')); + }); + $this->app->bind('setting', function() { return new SettingService( $this->app->make('BookStack\Setting'), diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index 87309501e..55dbb86dc 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -1,7 +1,9 @@ pageRepo = $pageRepo; } + /** + * Get the book that has the given id. + * @param $id + * @return mixed + */ public function getById($id) { return $this->book->findOrFail($id); } + /** + * Get all books, Limited by count. + * @param int $count + * @return mixed + */ public function getAll($count = 10) { return $this->book->orderBy('name', 'asc')->take($count)->get(); } /** - * Getas + * Get all books paginated. * @param int $count * @return mixed */ @@ -40,6 +52,16 @@ class BookRepo return $this->book->orderBy('name', 'asc')->paginate($count); } + public function getRecentlyViewed($count = 10, $page = 0) + { + return Views::getUserRecentlyViewed($count, $page, $this->book); + } + + /** + * Get a book by slug + * @param $slug + * @return mixed + */ public function getBySlug($slug) { return $this->book->where('slug', '=', $slug)->first(); @@ -65,11 +87,20 @@ class BookRepo return $this->book->fill($input); } + /** + * Count the amount of books that have a specific slug. + * @param $slug + * @return mixed + */ public function countBySlug($slug) { return $this->book->where('slug', '=', $slug)->count(); } + /** + * Destroy a book identified by the given slug. + * @param $bookSlug + */ public function destroyBySlug($bookSlug) { $book = $this->getBySlug($bookSlug); @@ -84,12 +115,22 @@ class BookRepo $book->delete(); } + /** + * Get the next child element priority. + * @param Book $book + * @return int + */ public function getNewPriority($book) { $lastElem = $book->children()->pop(); return $lastElem ? $lastElem->priority + 1 : 0; } + /** + * @param string $slug + * @param bool|false $currentId + * @return bool + */ public function doesSlugExist($slug, $currentId = false) { $query = $this->book->where('slug', '=', $slug); @@ -99,6 +140,13 @@ class BookRepo return $query->count() > 0; } + /** + * Provides a suitable slug for the given book name. + * Ensures the returned slug is unique in the system. + * @param string $name + * @param bool|false $currentId + * @return string + */ public function findSuitableSlug($name, $currentId = false) { $originalSlug = Str::slug($name); @@ -111,6 +159,11 @@ class BookRepo return $slug; } + /** + * Get books by search term. + * @param $term + * @return mixed + */ public function getBySearch($term) { $terms = explode(' ', preg_quote(trim($term))); diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php index 5bfee81ab..4da928fad 100644 --- a/app/Services/ActivityService.php +++ b/app/Services/ActivityService.php @@ -17,7 +17,7 @@ class ActivityService public function __construct(Activity $activity) { $this->activity = $activity; - $this->user = Auth::user(); + $this->user = auth()->user(); } /** diff --git a/app/Services/Facades/Views.php b/app/Services/Facades/Views.php new file mode 100644 index 000000000..484ee8145 --- /dev/null +++ b/app/Services/Facades/Views.php @@ -0,0 +1,13 @@ +view = $view; + $this->user = auth()->user(); + } + + /** + * Add a view to the given entity. + * @param Entity $entity + * @return int + */ + public function add(Entity $entity) + { + $view = $entity->views()->where('user_id', '=', $this->user->id)->first(); + // Add view if model exists + if ($view) { + $view->increment('views'); + return $view->views; + } + + // Otherwise create new view count + $entity->views()->save($this->view->create([ + 'user_id' => $this->user->id, + 'views' => 1 + ])); + + return 1; + } + + /** + * Get all recently viewed entities for the current user. + * @param int $count + * @param int $page + * @param Entity|bool $filterModel + * @return mixed + */ + public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false) + { + $skipCount = $count * $page; + $query = $this->view->where('user_id', '=', auth()->user()->id); + + if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); + + $views = $query->with('viewable')->orderBy('updated_at', 'desc')->skip($skipCount)->take($count)->get(); + $viewedEntities = $views->map(function ($item) { + return $item->viewable()->getResults(); + }); + return $viewedEntities; + } + + + /** + * Reset all view counts by deleting all views. + */ + public function resetAll() + { + $this->view->truncate(); + } + + +} \ No newline at end of file diff --git a/app/View.php b/app/View.php new file mode 100644 index 000000000..50dd06012 --- /dev/null +++ b/app/View.php @@ -0,0 +1,20 @@ +morphTo(); + } +} diff --git a/config/app.php b/config/app.php index 44b3542a6..196a5c19b 100644 --- a/config/app.php +++ b/config/app.php @@ -214,6 +214,7 @@ return [ 'Activity' => BookStack\Services\Facades\Activity::class, 'Setting' => BookStack\Services\Facades\Setting::class, + 'Views' => BookStack\Services\Facades\Views::class, ], diff --git a/database/migrations/2015_11_21_145609_create_views_table.php b/database/migrations/2015_11_21_145609_create_views_table.php new file mode 100644 index 000000000..2baef7317 --- /dev/null +++ b/database/migrations/2015_11_21_145609_create_views_table.php @@ -0,0 +1,34 @@ +increments('id'); + $table->integer('user_id'); + $table->integer('viewable_id'); + $table->string('viewable_type'); + $table->integer('views'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('views'); + } +} diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index 8163f5105..344835132 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -5,8 +5,8 @@
-
-
+
+
@if($currentUser->can('book-create')) Add new book @@ -20,7 +20,7 @@
-
+

Books

@if(count($books) > 0) @foreach($books as $book) @@ -33,7 +33,11 @@ Create one now @endif
-
+
+
 
+

Recently Viewed

+ @include('partials/entity-list', ['entities' => $recents]) +
diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 2a97c4839..0ffc8b3bf 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -4,26 +4,18 @@
+
-

Books

- @if(count($books) > 0) - @foreach($books as $book) - @include('books/list-item', ['book' => $book]) -
- @endforeach - @if(count($books) === 10) - View all books » - @endif - @else -

No books have been created.

- Create one now - @endif +

My Recently Viewed

+ @include('partials/entity-list', ['entities' => $recents])
+
 

Recent Activity

@include('partials/activity-list', ['activity' => $activity])
+
diff --git a/resources/views/partials/entity-list.blade.php b/resources/views/partials/entity-list.blade.php new file mode 100644 index 000000000..a4b2bdfb6 --- /dev/null +++ b/resources/views/partials/entity-list.blade.php @@ -0,0 +1,17 @@ + +@if(count($entities) > 0) + @foreach($entities as $entity) + @if($entity->isA('page')) + @include('pages/list-item', ['page' => $entity]) + @elseif($entity->isA('book')) + @include('books/list-item', ['book' => $entity]) + @elseif($entity->isA('chapter')) + @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true]) + @endif +
+ @endforeach +@else +

+ No items available :( +

+@endif \ No newline at end of file