Dan Brown

Added activity history to to all entities. Fixes #12

...@@ -9,4 +9,5 @@ Homestead.yaml ...@@ -9,4 +9,5 @@ Homestead.yaml
9 /public/js 9 /public/js
10 /public/uploads 10 /public/uploads
11 /public/bower 11 /public/bower
12 -/storage/images
...\ No newline at end of file ...\ No newline at end of file
12 +/storage/images
13 +_ide_helper.php
...\ No newline at end of file ...\ No newline at end of file
......
1 +<?php
2 +
3 +namespace Oxbow;
4 +
5 +use Illuminate\Database\Eloquent\Model;
6 +
7 +/**
8 + * @property string key
9 + * @property \User user
10 + * @property \Entity entity
11 + * @property string extra
12 + */
13 +class Activity extends Model
14 +{
15 + public function entity()
16 + {
17 + if($this->entity_id) {
18 + return $this->morphTo('entity')->first();
19 + } else {
20 + return false;
21 + }
22 + }
23 +
24 + public function user()
25 + {
26 + return $this->belongsTo('Oxbow\User');
27 + }
28 +
29 + /**
30 + * Returns text from the language files, Looks up by using the
31 + * activity key.
32 + */
33 + public function getText()
34 + {
35 + return trans('activities.' . $this->key);
36 + }
37 +
38 +}
...@@ -37,4 +37,15 @@ class Book extends Entity ...@@ -37,4 +37,15 @@ class Book extends Entity
37 return $pages->sortBy('priority'); 37 return $pages->sortBy('priority');
38 } 38 }
39 39
40 + /**
41 + * Gets only the most recent activity for this book
42 + * @param int $limit
43 + * @param int $page
44 + * @return mixed
45 + */
46 + public function recentActivity($limit = 20, $page=0)
47 + {
48 + return $this->hasMany('Oxbow\Activity')->orderBy('created_at', 'desc')->skip($limit*$page)->take($limit)->get();
49 + }
50 +
40 } 51 }
......
...@@ -34,4 +34,25 @@ class Entity extends Model ...@@ -34,4 +34,25 @@ class Entity extends Model
34 { 34 {
35 return [get_class($this), $this->id] === [get_class($entity), $entity->id]; 35 return [get_class($this), $this->id] === [get_class($entity), $entity->id];
36 } 36 }
37 +
38 + /**
39 + * Gets the activity for this entity.
40 + * @return \Illuminate\Database\Eloquent\Relations\MorphMany
41 + */
42 + public function activity()
43 + {
44 + return $this->morphMany('Oxbow\Activity', 'entity')->orderBy('created_at', 'desc');
45 + }
46 +
47 + /**
48 + * Gets only the most recent activity
49 + * @param int $limit
50 + * @param int $page
51 + * @return mixed
52 + */
53 + public function recentActivity($limit = 20, $page=0)
54 + {
55 + return $this->activity()->skip($limit*$page)->take($limit)->get();
56 + }
57 +
37 } 58 }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 namespace Oxbow\Http\Controllers; 3 namespace Oxbow\Http\Controllers;
4 4
5 +use Activity;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 7
7 use Illuminate\Support\Facades\Auth; 8 use Illuminate\Support\Facades\Auth;
...@@ -65,6 +66,7 @@ class BookController extends Controller ...@@ -65,6 +66,7 @@ class BookController extends Controller
65 $book->created_by = Auth::user()->id; 66 $book->created_by = Auth::user()->id;
66 $book->updated_by = Auth::user()->id; 67 $book->updated_by = Auth::user()->id;
67 $book->save(); 68 $book->save();
69 + Activity::add($book, 'book_create', $book->id);
68 return redirect('/books'); 70 return redirect('/books');
69 } 71 }
70 72
...@@ -110,6 +112,7 @@ class BookController extends Controller ...@@ -110,6 +112,7 @@ class BookController extends Controller
110 $book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id); 112 $book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id);
111 $book->updated_by = Auth::user()->id; 113 $book->updated_by = Auth::user()->id;
112 $book->save(); 114 $book->save();
115 + Activity::add($book, 'book_update', $book->id);
113 return redirect($book->getUrl()); 116 return redirect($book->getUrl());
114 } 117 }
115 118
...@@ -132,7 +135,9 @@ class BookController extends Controller ...@@ -132,7 +135,9 @@ class BookController extends Controller
132 */ 135 */
133 public function destroy($bookSlug) 136 public function destroy($bookSlug)
134 { 137 {
138 + $bookName = $this->bookRepo->getBySlug($bookSlug)->name;
135 $this->bookRepo->destroyBySlug($bookSlug); 139 $this->bookRepo->destroyBySlug($bookSlug);
140 + Activity::addMessage('book_delete', 0, $bookName);
136 return redirect('/books'); 141 return redirect('/books');
137 } 142 }
138 } 143 }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 namespace Oxbow\Http\Controllers; 3 namespace Oxbow\Http\Controllers;
4 4
5 +use Activity;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 7
7 use Illuminate\Support\Facades\Auth; 8 use Illuminate\Support\Facades\Auth;
...@@ -60,6 +61,7 @@ class ChapterController extends Controller ...@@ -60,6 +61,7 @@ class ChapterController extends Controller
60 $chapter->created_by = Auth::user()->id; 61 $chapter->created_by = Auth::user()->id;
61 $chapter->updated_by = Auth::user()->id; 62 $chapter->updated_by = Auth::user()->id;
62 $book->chapters()->save($chapter); 63 $book->chapters()->save($chapter);
64 + Activity::add($chapter, 'chapter_create', $book->id);
63 return redirect($book->getUrl()); 65 return redirect($book->getUrl());
64 } 66 }
65 67
...@@ -107,6 +109,7 @@ class ChapterController extends Controller ...@@ -107,6 +109,7 @@ class ChapterController extends Controller
107 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id); 109 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
108 $chapter->updated_by = Auth::user()->id; 110 $chapter->updated_by = Auth::user()->id;
109 $chapter->save(); 111 $chapter->save();
112 + Activity::add($chapter, 'chapter_update', $book->id);
110 return redirect($chapter->getUrl()); 113 return redirect($chapter->getUrl());
111 } 114 }
112 115
...@@ -134,6 +137,7 @@ class ChapterController extends Controller ...@@ -134,6 +137,7 @@ class ChapterController extends Controller
134 { 137 {
135 $book = $this->bookRepo->getBySlug($bookSlug); 138 $book = $this->bookRepo->getBySlug($bookSlug);
136 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 139 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
140 + $chapterName = $chapter->name;
137 if(count($chapter->pages) > 0) { 141 if(count($chapter->pages) > 0) {
138 foreach($chapter->pages as $page) { 142 foreach($chapter->pages as $page) {
139 $page->chapter_id = 0; 143 $page->chapter_id = 0;
...@@ -141,6 +145,7 @@ class ChapterController extends Controller ...@@ -141,6 +145,7 @@ class ChapterController extends Controller
141 } 145 }
142 } 146 }
143 $chapter->delete(); 147 $chapter->delete();
148 + Activity::addMessage('chapter_delete', $book->id, $chapterName);
144 return redirect($book->getUrl()); 149 return redirect($book->getUrl());
145 } 150 }
146 } 151 }
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
2 2
3 namespace Oxbow\Http\Controllers; 3 namespace Oxbow\Http\Controllers;
4 4
5 +use Activity;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 7
7 use Illuminate\Support\Facades\Auth; 8 use Illuminate\Support\Facades\Auth;
8 -use Illuminate\Support\Str;
9 use Oxbow\Http\Requests; 9 use Oxbow\Http\Requests;
10 use Oxbow\Repos\BookRepo; 10 use Oxbow\Repos\BookRepo;
11 use Oxbow\Repos\ChapterRepo; 11 use Oxbow\Repos\ChapterRepo;
...@@ -76,6 +76,7 @@ class PageController extends Controller ...@@ -76,6 +76,7 @@ class PageController extends Controller
76 $page->updated_by = Auth::user()->id; 76 $page->updated_by = Auth::user()->id;
77 $page->save(); 77 $page->save();
78 $this->pageRepo->saveRevision($page); 78 $this->pageRepo->saveRevision($page);
79 + Activity::add($page, 'page_create', $book->id);
79 return redirect($page->getUrl()); 80 return redirect($page->getUrl());
80 } 81 }
81 82
...@@ -120,6 +121,7 @@ class PageController extends Controller ...@@ -120,6 +121,7 @@ class PageController extends Controller
120 $book = $this->bookRepo->getBySlug($bookSlug); 121 $book = $this->bookRepo->getBySlug($bookSlug);
121 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 122 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
122 $this->pageRepo->updatePage($page, $book->id, $request->all()); 123 $this->pageRepo->updatePage($page, $book->id, $request->all());
124 + Activity::add($page, 'page_update', $book->id);
123 return redirect($page->getUrl()); 125 return redirect($page->getUrl());
124 } 126 }
125 127
...@@ -187,6 +189,7 @@ class PageController extends Controller ...@@ -187,6 +189,7 @@ class PageController extends Controller
187 } 189 }
188 $model->save(); 190 $model->save();
189 } 191 }
192 + Activity::add($book, 'book_sort', $book->id);
190 return redirect($book->getUrl()); 193 return redirect($book->getUrl());
191 } 194 }
192 195
...@@ -215,6 +218,7 @@ class PageController extends Controller ...@@ -215,6 +218,7 @@ class PageController extends Controller
215 { 218 {
216 $book = $this->bookRepo->getBySlug($bookSlug); 219 $book = $this->bookRepo->getBySlug($bookSlug);
217 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 220 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
221 + Activity::addMessage('page_delete', $book->id, $page->name);
218 $page->delete(); 222 $page->delete();
219 return redirect($book->getUrl()); 223 return redirect($book->getUrl());
220 } 224 }
...@@ -254,6 +258,7 @@ class PageController extends Controller ...@@ -254,6 +258,7 @@ class PageController extends Controller
254 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 258 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
255 $revision = $this->pageRepo->getRevisionById($revisionId); 259 $revision = $this->pageRepo->getRevisionById($revisionId);
256 $page = $this->pageRepo->updatePage($page, $book->id, $revision->toArray()); 260 $page = $this->pageRepo->updatePage($page, $book->id, $revision->toArray());
261 + Activity::add($page, 'page_restore', $book->id);
257 return redirect($page->getUrl()); 262 return redirect($page->getUrl());
258 } 263 }
259 } 264 }
......
1 +<?php
2 +
3 +namespace Oxbow\Providers;
4 +
5 +use Illuminate\Support\ServiceProvider;
6 +use Oxbow\Services\ActivityService;
7 +
8 +class CustomFacadeProvider extends ServiceProvider
9 +{
10 + /**
11 + * Bootstrap the application services.
12 + *
13 + * @return void
14 + */
15 + public function boot()
16 + {
17 + //
18 + }
19 +
20 + /**
21 + * Register the application services.
22 + *
23 + * @return void
24 + */
25 + public function register()
26 + {
27 + $this->app->bind('activity', function() {
28 + return new ActivityService($this->app->make('Oxbow\Activity'));
29 + });
30 + }
31 +}
1 +<?php namespace Oxbow\Services;
2 +
3 +use Illuminate\Support\Facades\Auth;
4 +use Oxbow\Activity;
5 +use Oxbow\Entity;
6 +
7 +class ActivityService
8 +{
9 + protected $activity;
10 + protected $user;
11 +
12 + /**
13 + * ActivityService constructor.
14 + * @param $activity
15 + */
16 + public function __construct(Activity $activity)
17 + {
18 + $this->activity = $activity;
19 + $this->user = Auth::user();
20 + }
21 +
22 +
23 + /**
24 + * Add activity data to database.
25 + * @para Entity $entity
26 + * @param $activityKey
27 + * @param int $bookId
28 + */
29 + public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false)
30 + {
31 + $this->activity->user_id = $this->user->id;
32 + $this->activity->book_id = $bookId;
33 + $this->activity->key = strtolower($activityKey);
34 + if($extra !== false) {
35 + $this->activity->extra = $extra;
36 + }
37 + $entity->activity()->save($this->activity);
38 + }
39 +
40 + /**
41 + * Adds a activity history with a message & without binding to a entitiy.
42 + * @param $activityKey
43 + * @param int $bookId
44 + * @param bool|false $extra
45 + */
46 + public function addMessage($activityKey, $bookId = 0, $extra = false)
47 + {
48 + $this->activity->user_id = $this->user->id;
49 + $this->activity->book_id = $bookId;
50 + $this->activity->key = strtolower($activityKey);
51 + if($extra !== false) {
52 + $this->activity->extra = $extra;
53 + }
54 + $this->activity->save();
55 + }
56 +
57 +}
...\ No newline at end of file ...\ No newline at end of file
1 +<?php namespace Oxbow\Services\Facades;
2 +
3 +
4 +use Illuminate\Support\Facades\Facade;
5 +
6 +class Activity extends Facade
7 +{
8 + /**
9 + * Get the registered name of the component.
10 + *
11 + * @return string
12 + */
13 + protected static function getFacadeAccessor() { return 'activity'; }
14 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
7 "require": { 7 "require": {
8 "php": ">=5.5.9", 8 "php": ">=5.5.9",
9 "laravel/framework": "5.1.*", 9 "laravel/framework": "5.1.*",
10 - "intervention/image": "^2.3" 10 + "intervention/image": "^2.3",
11 + "barryvdh/laravel-ide-helper": "^2.1"
11 }, 12 },
12 "require-dev": { 13 "require-dev": {
13 "fzaninotto/faker": "~1.4", 14 "fzaninotto/faker": "~1.4",
......
...@@ -141,6 +141,8 @@ return [ ...@@ -141,6 +141,8 @@ return [
141 * Third Party 141 * Third Party
142 */ 142 */
143 Intervention\Image\ImageServiceProvider::class, 143 Intervention\Image\ImageServiceProvider::class,
144 + Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
145 +
144 146
145 /* 147 /*
146 * Application Service Providers... 148 * Application Service Providers...
...@@ -148,6 +150,7 @@ return [ ...@@ -148,6 +150,7 @@ return [
148 Oxbow\Providers\AppServiceProvider::class, 150 Oxbow\Providers\AppServiceProvider::class,
149 Oxbow\Providers\EventServiceProvider::class, 151 Oxbow\Providers\EventServiceProvider::class,
150 Oxbow\Providers\RouteServiceProvider::class, 152 Oxbow\Providers\RouteServiceProvider::class,
153 + Oxbow\Providers\CustomFacadeProvider::class,
151 154
152 ], 155 ],
153 156
...@@ -203,6 +206,12 @@ return [ ...@@ -203,6 +206,12 @@ return [
203 206
204 'ImageTool' => Intervention\Image\Facades\Image::class, 207 'ImageTool' => Intervention\Image\Facades\Image::class,
205 208
209 + /**
210 + * Custom
211 + */
212 +
213 + 'Activity' => Oxbow\Services\Facades\Activity::class,
214 +
206 ], 215 ],
207 216
208 ]; 217 ];
......
1 +<?php
2 +
3 +use Illuminate\Database\Schema\Blueprint;
4 +use Illuminate\Database\Migrations\Migration;
5 +
6 +class CreateActivitiesTable extends Migration
7 +{
8 + /**
9 + * Run the migrations.
10 + *
11 + * @return void
12 + */
13 + public function up()
14 + {
15 + Schema::create('activities', function (Blueprint $table) {
16 + $table->increments('id');
17 + $table->string('key');
18 + $table->text('extra');
19 + $table->integer('book_id')->indexed();
20 + $table->integer('user_id');
21 + $table->integer('entity_id');
22 + $table->string('entity_type');
23 + $table->timestamps();
24 + });
25 + }
26 +
27 + /**
28 + * Reverse the migrations.
29 + *
30 + * @return void
31 + */
32 + public function down()
33 + {
34 + Schema::drop('activities');
35 + }
36 +}
...@@ -423,4 +423,10 @@ body.dragging, body.dragging * { ...@@ -423,4 +423,10 @@ body.dragging, body.dragging * {
423 background-color: transparent; 423 background-color: transparent;
424 color: #EEE; 424 color: #EEE;
425 } 425 }
426 +}
427 +
428 +.activity-list-item {
429 + padding: $-s 0;
430 + color: #888;
431 + border-bottom: 1px solid #EEE;
426 } 432 }
...\ No newline at end of file ...\ No newline at end of file
......
1 +<?php
2 +
3 +return [
4 +
5 + /**
6 + * Activity text strings.
7 + * Is used for all the text within activity logs.
8 + */
9 +
10 + // Pages
11 + 'page_create' => 'created page',
12 + 'page_update' => 'updated page',
13 + 'page_delete' => 'deleted page',
14 + 'page_restore' => 'restored page',
15 +
16 + // Chapters
17 + 'chapter_create' => 'created chapter',
18 + 'chapter_update' => 'updated chapter',
19 + 'chapter_delete' => 'deleted chapter',
20 +
21 + // Books
22 + 'book_create' => 'created book',
23 + 'book_update' => 'updated book',
24 + 'book_delete' => 'deleted book',
25 +
26 +];
...\ No newline at end of file ...\ No newline at end of file
...@@ -15,49 +15,63 @@ ...@@ -15,49 +15,63 @@
15 </div> 15 </div>
16 </div> 16 </div>
17 17
18 - <div class="page-content"> 18 + <div class="row">
19 - <h1>{{$book->name}}</h1> 19 + <div class="col-md-6 col-md-offset-1">
20 - <p class="text-muted">{{$book->description}}</p> 20 +
21 - 21 + <div class="page-content">
22 - <div class="page-list"> 22 + <h1>{{$book->name}}</h1>
23 - <hr> 23 + <p class="text-muted">{{$book->description}}</p>
24 - @foreach($book->children() as $childElement) 24 +
25 - <div class="book-child"> 25 + <div class="page-list">
26 - <h3> 26 + <hr>
27 - <a href="{{ $childElement->getUrl() }}"> 27 + @foreach($book->children() as $childElement)
28 - @if(is_a($childElement, 'Oxbow\Chapter')) 28 + <div class="book-child">
29 - <i class="zmdi zmdi-collection-bookmark chapter-toggle"></i> 29 + <h3>
30 - @else 30 + <a href="{{ $childElement->getUrl() }}">
31 - <i class="zmdi zmdi-file-text"></i> 31 + @if(is_a($childElement, 'Oxbow\Chapter'))
32 + <i class="zmdi zmdi-collection-bookmark chapter-toggle"></i>
33 + @else
34 + <i class="zmdi zmdi-file-text"></i>
35 + @endif
36 + {{ $childElement->name }}
37 + </a>
38 + </h3>
39 + <p class="text-muted">
40 + {{$childElement->getExcerpt()}}
41 + </p>
42 +
43 + @if(is_a($childElement, 'Oxbow\Chapter') && count($childElement->pages) > 0)
44 + <div class="inset-list">
45 + @foreach($childElement->pages as $page)
46 + <h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i> {{$page->name}}</a></h4>
47 + @endforeach
48 + </div>
32 @endif 49 @endif
33 - {{ $childElement->name }}
34 - </a>
35 - </h3>
36 - <p class="text-muted">
37 - {{$childElement->getExcerpt()}}
38 - </p>
39 -
40 - @if(is_a($childElement, 'Oxbow\Chapter') && count($childElement->pages) > 0)
41 - <div class="inset-list">
42 - @foreach($childElement->pages as $page)
43 - <h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i> {{$page->name}}</a></h4>
44 - @endforeach
45 </div> 50 </div>
46 - @endif 51 + <hr>
52 + @endforeach
47 </div> 53 </div>
48 - <hr>
49 - @endforeach
50 - </div>
51 54
52 - <p class="text-muted small"> 55 + <p class="text-muted small">
53 - Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif 56 + Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
54 - <br> 57 + <br>
55 - Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif 58 + Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
56 - </p> 59 + </p>
60 +
61 + </div>
57 62
63 + </div>
64 +
65 + <div class="col-md-3 col-md-offset-1">
66 + <div class="margin-top large"><br></div>
67 + <h3>Recent Activity</h3>
68 + @include('partials/activity-list', ['entity' => $book])
69 + </div>
58 </div> 70 </div>
59 71
60 72
73 +
74 +
61 <script> 75 <script>
62 $(function() { 76 $(function() {
63 77
......
1 +
2 +{{--Requires an Activity item with the name $activity passed in--}}
3 +
4 +@if($activity->user) {{$activity->user->name}} @endif
5 +
6 +{{ $activity->getText() }}
7 +
8 +@if($activity->entity())
9 + <a href="{{ $activity->entity()->getUrl() }}">{{ $activity->entity()->name }}</a>
10 +@endif
11 +
12 +@if($activity->extra) "{{$activity->extra}}" @endif
13 +
14 +<br>
15 +
16 +<span class="text-muted"><small><i class="zmdi zmdi-time"></i>{{ $activity->created_at->diffForHumans() }}</small></span>
...\ No newline at end of file ...\ No newline at end of file
1 +
2 +{{--Requires an entity to be passed with the name $entity--}}
3 +
4 +@if(count($entity->recentActivity()) > 0)
5 + <div class="activity-list">
6 + @foreach($entity->recentActivity() as $activity)
7 + <div class="activity-list-item">
8 + @include('partials/activity-item')
9 + </div>
10 + @endforeach
11 + </div>
12 +@endif
...\ No newline at end of file ...\ No newline at end of file