Added activity history to to all entities. Fixes #12

This commit is contained in:
Dan Brown 2015-08-16 18:59:23 +01:00
parent 41eb2fb633
commit 5d9d096028
19 changed files with 673 additions and 251 deletions

3
.gitignore vendored
View File

@ -9,4 +9,5 @@ Homestead.yaml
/public/js /public/js
/public/uploads /public/uploads
/public/bower /public/bower
/storage/images /storage/images
_ide_helper.php

38
app/Activity.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace Oxbow;
use Illuminate\Database\Eloquent\Model;
/**
* @property string key
* @property \User user
* @property \Entity entity
* @property string extra
*/
class Activity extends Model
{
public function entity()
{
if($this->entity_id) {
return $this->morphTo('entity')->first();
} else {
return false;
}
}
public function user()
{
return $this->belongsTo('Oxbow\User');
}
/**
* Returns text from the language files, Looks up by using the
* activity key.
*/
public function getText()
{
return trans('activities.' . $this->key);
}
}

View File

@ -37,4 +37,15 @@ class Book extends Entity
return $pages->sortBy('priority'); return $pages->sortBy('priority');
} }
/**
* Gets only the most recent activity for this book
* @param int $limit
* @param int $page
* @return mixed
*/
public function recentActivity($limit = 20, $page=0)
{
return $this->hasMany('Oxbow\Activity')->orderBy('created_at', 'desc')->skip($limit*$page)->take($limit)->get();
}
} }

View File

@ -34,4 +34,25 @@ class Entity extends Model
{ {
return [get_class($this), $this->id] === [get_class($entity), $entity->id]; return [get_class($this), $this->id] === [get_class($entity), $entity->id];
} }
/**
* Gets the activity for this entity.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function activity()
{
return $this->morphMany('Oxbow\Activity', 'entity')->orderBy('created_at', 'desc');
}
/**
* Gets only the most recent activity
* @param int $limit
* @param int $page
* @return mixed
*/
public function recentActivity($limit = 20, $page=0)
{
return $this->activity()->skip($limit*$page)->take($limit)->get();
}
} }

View File

@ -2,6 +2,7 @@
namespace Oxbow\Http\Controllers; namespace Oxbow\Http\Controllers;
use Activity;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -65,6 +66,7 @@ class BookController extends Controller
$book->created_by = Auth::user()->id; $book->created_by = Auth::user()->id;
$book->updated_by = Auth::user()->id; $book->updated_by = Auth::user()->id;
$book->save(); $book->save();
Activity::add($book, 'book_create', $book->id);
return redirect('/books'); return redirect('/books');
} }
@ -110,6 +112,7 @@ class BookController extends Controller
$book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id); $book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id);
$book->updated_by = Auth::user()->id; $book->updated_by = Auth::user()->id;
$book->save(); $book->save();
Activity::add($book, 'book_update', $book->id);
return redirect($book->getUrl()); return redirect($book->getUrl());
} }
@ -132,7 +135,9 @@ class BookController extends Controller
*/ */
public function destroy($bookSlug) public function destroy($bookSlug)
{ {
$bookName = $this->bookRepo->getBySlug($bookSlug)->name;
$this->bookRepo->destroyBySlug($bookSlug); $this->bookRepo->destroyBySlug($bookSlug);
Activity::addMessage('book_delete', 0, $bookName);
return redirect('/books'); return redirect('/books');
} }
} }

View File

@ -2,6 +2,7 @@
namespace Oxbow\Http\Controllers; namespace Oxbow\Http\Controllers;
use Activity;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -60,6 +61,7 @@ class ChapterController extends Controller
$chapter->created_by = Auth::user()->id; $chapter->created_by = Auth::user()->id;
$chapter->updated_by = Auth::user()->id; $chapter->updated_by = Auth::user()->id;
$book->chapters()->save($chapter); $book->chapters()->save($chapter);
Activity::add($chapter, 'chapter_create', $book->id);
return redirect($book->getUrl()); return redirect($book->getUrl());
} }
@ -107,6 +109,7 @@ class ChapterController extends Controller
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id); $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
$chapter->updated_by = Auth::user()->id; $chapter->updated_by = Auth::user()->id;
$chapter->save(); $chapter->save();
Activity::add($chapter, 'chapter_update', $book->id);
return redirect($chapter->getUrl()); return redirect($chapter->getUrl());
} }
@ -134,6 +137,7 @@ class ChapterController extends Controller
{ {
$book = $this->bookRepo->getBySlug($bookSlug); $book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$chapterName = $chapter->name;
if(count($chapter->pages) > 0) { if(count($chapter->pages) > 0) {
foreach($chapter->pages as $page) { foreach($chapter->pages as $page) {
$page->chapter_id = 0; $page->chapter_id = 0;
@ -141,6 +145,7 @@ class ChapterController extends Controller
} }
} }
$chapter->delete(); $chapter->delete();
Activity::addMessage('chapter_delete', $book->id, $chapterName);
return redirect($book->getUrl()); return redirect($book->getUrl());
} }
} }

View File

@ -2,10 +2,10 @@
namespace Oxbow\Http\Controllers; namespace Oxbow\Http\Controllers;
use Activity;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Oxbow\Http\Requests; use Oxbow\Http\Requests;
use Oxbow\Repos\BookRepo; use Oxbow\Repos\BookRepo;
use Oxbow\Repos\ChapterRepo; use Oxbow\Repos\ChapterRepo;
@ -76,6 +76,7 @@ class PageController extends Controller
$page->updated_by = Auth::user()->id; $page->updated_by = Auth::user()->id;
$page->save(); $page->save();
$this->pageRepo->saveRevision($page); $this->pageRepo->saveRevision($page);
Activity::add($page, 'page_create', $book->id);
return redirect($page->getUrl()); return redirect($page->getUrl());
} }
@ -120,6 +121,7 @@ class PageController extends Controller
$book = $this->bookRepo->getBySlug($bookSlug); $book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id); $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$this->pageRepo->updatePage($page, $book->id, $request->all()); $this->pageRepo->updatePage($page, $book->id, $request->all());
Activity::add($page, 'page_update', $book->id);
return redirect($page->getUrl()); return redirect($page->getUrl());
} }
@ -187,6 +189,7 @@ class PageController extends Controller
} }
$model->save(); $model->save();
} }
Activity::add($book, 'book_sort', $book->id);
return redirect($book->getUrl()); return redirect($book->getUrl());
} }
@ -215,6 +218,7 @@ class PageController extends Controller
{ {
$book = $this->bookRepo->getBySlug($bookSlug); $book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id); $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
Activity::addMessage('page_delete', $book->id, $page->name);
$page->delete(); $page->delete();
return redirect($book->getUrl()); return redirect($book->getUrl());
} }
@ -254,6 +258,7 @@ class PageController extends Controller
$page = $this->pageRepo->getBySlug($pageSlug, $book->id); $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$revision = $this->pageRepo->getRevisionById($revisionId); $revision = $this->pageRepo->getRevisionById($revisionId);
$page = $this->pageRepo->updatePage($page, $book->id, $revision->toArray()); $page = $this->pageRepo->updatePage($page, $book->id, $revision->toArray());
Activity::add($page, 'page_restore', $book->id);
return redirect($page->getUrl()); return redirect($page->getUrl());
} }
} }

View File

@ -0,0 +1,31 @@
<?php
namespace Oxbow\Providers;
use Illuminate\Support\ServiceProvider;
use Oxbow\Services\ActivityService;
class CustomFacadeProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->app->bind('activity', function() {
return new ActivityService($this->app->make('Oxbow\Activity'));
});
}
}

View File

@ -0,0 +1,57 @@
<?php namespace Oxbow\Services;
use Illuminate\Support\Facades\Auth;
use Oxbow\Activity;
use Oxbow\Entity;
class ActivityService
{
protected $activity;
protected $user;
/**
* ActivityService constructor.
* @param $activity
*/
public function __construct(Activity $activity)
{
$this->activity = $activity;
$this->user = Auth::user();
}
/**
* Add activity data to database.
* @para Entity $entity
* @param $activityKey
* @param int $bookId
*/
public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false)
{
$this->activity->user_id = $this->user->id;
$this->activity->book_id = $bookId;
$this->activity->key = strtolower($activityKey);
if($extra !== false) {
$this->activity->extra = $extra;
}
$entity->activity()->save($this->activity);
}
/**
* Adds a activity history with a message & without binding to a entitiy.
* @param $activityKey
* @param int $bookId
* @param bool|false $extra
*/
public function addMessage($activityKey, $bookId = 0, $extra = false)
{
$this->activity->user_id = $this->user->id;
$this->activity->book_id = $bookId;
$this->activity->key = strtolower($activityKey);
if($extra !== false) {
$this->activity->extra = $extra;
}
$this->activity->save();
}
}

View File

@ -0,0 +1,14 @@
<?php namespace Oxbow\Services\Facades;
use Illuminate\Support\Facades\Facade;
class Activity extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'activity'; }
}

View File

@ -7,7 +7,8 @@
"require": { "require": {
"php": ">=5.5.9", "php": ">=5.5.9",
"laravel/framework": "5.1.*", "laravel/framework": "5.1.*",
"intervention/image": "^2.3" "intervention/image": "^2.3",
"barryvdh/laravel-ide-helper": "^2.1"
}, },
"require-dev": { "require-dev": {
"fzaninotto/faker": "~1.4", "fzaninotto/faker": "~1.4",

542
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -141,6 +141,8 @@ return [
* Third Party * Third Party
*/ */
Intervention\Image\ImageServiceProvider::class, Intervention\Image\ImageServiceProvider::class,
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
/* /*
* Application Service Providers... * Application Service Providers...
@ -148,6 +150,7 @@ return [
Oxbow\Providers\AppServiceProvider::class, Oxbow\Providers\AppServiceProvider::class,
Oxbow\Providers\EventServiceProvider::class, Oxbow\Providers\EventServiceProvider::class,
Oxbow\Providers\RouteServiceProvider::class, Oxbow\Providers\RouteServiceProvider::class,
Oxbow\Providers\CustomFacadeProvider::class,
], ],
@ -203,6 +206,12 @@ return [
'ImageTool' => Intervention\Image\Facades\Image::class, 'ImageTool' => Intervention\Image\Facades\Image::class,
/**
* Custom
*/
'Activity' => Oxbow\Services\Facades\Activity::class,
], ],
]; ];

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateActivitiesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('activities', function (Blueprint $table) {
$table->increments('id');
$table->string('key');
$table->text('extra');
$table->integer('book_id')->indexed();
$table->integer('user_id');
$table->integer('entity_id');
$table->string('entity_type');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('activities');
}
}

View File

@ -423,4 +423,10 @@ body.dragging, body.dragging * {
background-color: transparent; background-color: transparent;
color: #EEE; color: #EEE;
} }
}
.activity-list-item {
padding: $-s 0;
color: #888;
border-bottom: 1px solid #EEE;
} }

View File

@ -0,0 +1,26 @@
<?php
return [
/**
* Activity text strings.
* Is used for all the text within activity logs.
*/
// Pages
'page_create' => 'created page',
'page_update' => 'updated page',
'page_delete' => 'deleted page',
'page_restore' => 'restored page',
// Chapters
'chapter_create' => 'created chapter',
'chapter_update' => 'updated chapter',
'chapter_delete' => 'deleted chapter',
// Books
'book_create' => 'created book',
'book_update' => 'updated book',
'book_delete' => 'deleted book',
];

View File

@ -15,49 +15,63 @@
</div> </div>
</div> </div>
<div class="page-content"> <div class="row">
<h1>{{$book->name}}</h1> <div class="col-md-6 col-md-offset-1">
<p class="text-muted">{{$book->description}}</p>
<div class="page-list"> <div class="page-content">
<hr> <h1>{{$book->name}}</h1>
@foreach($book->children() as $childElement) <p class="text-muted">{{$book->description}}</p>
<div class="book-child">
<h3> <div class="page-list">
<a href="{{ $childElement->getUrl() }}"> <hr>
@if(is_a($childElement, 'Oxbow\Chapter')) @foreach($book->children() as $childElement)
<i class="zmdi zmdi-collection-bookmark chapter-toggle"></i> <div class="book-child">
@else <h3>
<i class="zmdi zmdi-file-text"></i> <a href="{{ $childElement->getUrl() }}">
@if(is_a($childElement, 'Oxbow\Chapter'))
<i class="zmdi zmdi-collection-bookmark chapter-toggle"></i>
@else
<i class="zmdi zmdi-file-text"></i>
@endif
{{ $childElement->name }}
</a>
</h3>
<p class="text-muted">
{{$childElement->getExcerpt()}}
</p>
@if(is_a($childElement, 'Oxbow\Chapter') && count($childElement->pages) > 0)
<div class="inset-list">
@foreach($childElement->pages as $page)
<h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i> {{$page->name}}</a></h4>
@endforeach
</div>
@endif @endif
{{ $childElement->name }}
</a>
</h3>
<p class="text-muted">
{{$childElement->getExcerpt()}}
</p>
@if(is_a($childElement, 'Oxbow\Chapter') && count($childElement->pages) > 0)
<div class="inset-list">
@foreach($childElement->pages as $page)
<h4><a href="{{$page->getUrl()}}"><i class="zmdi zmdi-file-text"></i> {{$page->name}}</a></h4>
@endforeach
</div> </div>
@endif <hr>
@endforeach
</div> </div>
<hr>
@endforeach <p class="text-muted small">
Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif
<br>
Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif
</p>
</div>
</div> </div>
<p class="text-muted small"> <div class="col-md-3 col-md-offset-1">
Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by {{$book->createdBy->name}} @endif <div class="margin-top large"><br></div>
<br> <h3>Recent Activity</h3>
Last Updated {{$book->updated_at->diffForHumans()}} @if($book->createdBy) by {{$book->updatedBy->name}} @endif @include('partials/activity-list', ['entity' => $book])
</p> </div>
</div> </div>
<script> <script>
$(function() { $(function() {

View File

@ -0,0 +1,16 @@
{{--Requires an Activity item with the name $activity passed in--}}
@if($activity->user) {{$activity->user->name}} @endif
{{ $activity->getText() }}
@if($activity->entity())
<a href="{{ $activity->entity()->getUrl() }}">{{ $activity->entity()->name }}</a>
@endif
@if($activity->extra) "{{$activity->extra}}" @endif
<br>
<span class="text-muted"><small><i class="zmdi zmdi-time"></i>{{ $activity->created_at->diffForHumans() }}</small></span>

View File

@ -0,0 +1,12 @@
{{--Requires an entity to be passed with the name $entity--}}
@if(count($entity->recentActivity()) > 0)
<div class="activity-list">
@foreach($entity->recentActivity() as $activity)
<div class="activity-list-item">
@include('partials/activity-item')
</div>
@endforeach
</div>
@endif