diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 247a09c5..00000000 --- a/.styleci.yml +++ /dev/null @@ -1 +0,0 @@ -preset: psr2 diff --git a/composer.json b/composer.json index 884b8f8f..5838c8a6 100644 --- a/composer.json +++ b/composer.json @@ -14,22 +14,29 @@ "name": "Austin Stierler", "email": "austin.stierler@gmail.com", "role": "Developer/Maintainer" - } + }, + { + "name": "Tarek Adam", + "email": "tarek.adam@gmail.com", + "role": "Contributor" + } ], "support": { "issues": "https://github.com/VentureCraft/revisionable/issues", "source": "https://github.com/VentureCraft/revisionable" }, "require": { - "php": ">=5.4.0", - "illuminate/support": "~4.0|~5.0|~5.1" }, "autoload": { - "classmap": [ - "src/migrations" - ], - "psr-0": { - "Venturecraft\\Revisionable": "src/" + "psr-4": { + "Venturecraft\\Revisionable\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "Venturecraft\\Revisionable\\ServiceProvider" + ] } } } diff --git a/src/config/revisionable.php b/config/main.php similarity index 60% rename from src/config/revisionable.php rename to config/main.php index 4f6666b8..2ee9fd81 100644 --- a/src/config/revisionable.php +++ b/config/main.php @@ -6,5 +6,7 @@ | Revision Model |-------------------------------------------------------------------------- */ - 'model' => Venturecraft\Revisionable\Revision::class, + 'route-prefix' => 'revisions', + 'middleware' => ['web', 'auth'], + 'revisionable-model-binding' => [] ]; diff --git a/src/migrations/2013_04_09_062329_create_revisions_table.php b/migrations/2013_04_09_062329_create_revisions_table.php similarity index 100% rename from src/migrations/2013_04_09_062329_create_revisions_table.php rename to migrations/2013_04_09_062329_create_revisions_table.php diff --git a/migrations/2018_05_01_114345_revisionable_add_accepted_at_timestamp.php b/migrations/2018_05_01_114345_revisionable_add_accepted_at_timestamp.php new file mode 100644 index 00000000..38c56c8b --- /dev/null +++ b/migrations/2018_05_01_114345_revisionable_add_accepted_at_timestamp.php @@ -0,0 +1,33 @@ +timestamp('accepted_at')->after('new_value'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('revisions', function (Blueprint $table) { + // + }); + } +} diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php new file mode 100644 index 00000000..3b524522 --- /dev/null +++ b/resources/lang/en/validation.php @@ -0,0 +1,6 @@ + 'Invalid model.', +]; diff --git a/resources/views/history.blade.php b/resources/views/history.blade.php new file mode 100644 index 00000000..98ff5692 --- /dev/null +++ b/resources/views/history.blade.php @@ -0,0 +1,14 @@ +@foreach($model->revisionHistory as $history) + @if($history->key == 'created_at' and !$history->old_value) +
{{ (!empty($history->userResponsible()))? $history->userResponsible()->first_name : 'system' }} created this resource at {{ $history->newValue() }}
+ @else +
{{ $history->userResponsible()->first_name }} changed {{ $history->fieldName() }} from + {{ $history->oldValue() }} + @if(is_null($history->oldValue())) + null + @endif + + to {{ $history->newValue() }} +
+ @endif +@endforeach diff --git a/routes/main.php b/routes/main.php new file mode 100644 index 00000000..91441328 --- /dev/null +++ b/routes/main.php @@ -0,0 +1,13 @@ + 'Venturecraft\Revisionable', + 'as' => Venturecraft\Revisionable\ServiceProvider::SHORT_NAME . '::', + 'prefix' => config(Venturecraft\Revisionable\ServiceProvider::SHORT_NAME . '.route-prefix'), + 'middleware' => config(Venturecraft\Revisionable\ServiceProvider::SHORT_NAME . '.middleware')]; + +Route::group($config, function (){ + Route::get('{revisionable}/history', [ + 'as' => 'model-history', + 'uses' => 'Controller@modelHistory' + ]); +}); \ No newline at end of file diff --git a/src/Controller.php b/src/Controller.php new file mode 100644 index 00000000..c29c784e --- /dev/null +++ b/src/Controller.php @@ -0,0 +1,14 @@ +withModel($revisionable); + } + +} diff --git a/src/Venturecraft/Revisionable/FieldFormatter.php b/src/FieldFormatter.php similarity index 100% rename from src/Venturecraft/Revisionable/FieldFormatter.php rename to src/FieldFormatter.php diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Revision.php similarity index 91% rename from src/Venturecraft/Revisionable/Revision.php rename to src/Revision.php index 498c743b..d0909b0e 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Revision.php @@ -2,8 +2,10 @@ namespace Venturecraft\Revisionable; -use Illuminate\Database\Eloquent\Model as Eloquent; -use Illuminate\Support\Facades\Log; +use Carbon\Carbon; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; + /** * Revision. @@ -13,13 +15,16 @@ * * (c) Venture Craft */ -class Revision extends Eloquent +class Revision extends Model { /** * @var string */ public $table = 'revisions'; + protected $dates = ['accepted_at']; + + /** * @var array */ @@ -31,6 +36,10 @@ class Revision extends Eloquent public function __construct(array $attributes = array()) { parent::__construct($attributes); + + if(empty($this->attributes['accepted_at'])){ + $this->attributes['accepted_at'] = Carbon::now(); + } } /** @@ -96,7 +105,7 @@ private function formatFieldName($key) */ public function oldValue() { - return $this->getValue('old'); + $this->getValue('old'); } @@ -163,7 +172,7 @@ private function getValue($which = 'new') // Check if model use RevisionableTrait if(method_exists($item, 'identifiableName')) { // see if there's an available mutator - $mutator = 'get' . studly_case($this->key) . 'Attribute'; + $mutator = 'get' . Str::studly($this->key) . 'Attribute'; if (method_exists($item, $mutator)) { return $this->format($item->$mutator($this->key), $item->identifiableName()); } @@ -179,7 +188,7 @@ private function getValue($which = 'new') // if there was an issue // or, if it's a normal value - $mutator = 'get' . studly_case($this->key) . 'Attribute'; + $mutator = 'get' . Str::studly($this->key) . 'Attribute'; if (method_exists($main_model, $mutator)) { return $this->format($this->key, $main_model->$mutator($this->$which_value)); } @@ -233,10 +242,10 @@ public function userResponsible() ) { return $class::findUserById($this->user_id); } else { - $user_model = app('config')->get('auth.model'); + $user_model = config('auth.model'); if (empty($user_model)) { - $user_model = app('config')->get('auth.providers.users.model'); + $user_model = config('auth.providers.users.model'); if (empty($user_model)) { return false; } @@ -289,4 +298,14 @@ public function format($key, $value) return $value; } } + + public function scopeOnlyPending($q) + { + $q->whereNull('accepted_at'); + } + + public function scopeOnlyAccepted($q) + { + $q->whereNotNull('accepted_at'); + } } diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Revisionable.php similarity index 96% rename from src/Venturecraft/Revisionable/Revisionable.php rename to src/Revisionable.php index 82348834..b09fb07c 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Revisionable.php @@ -1,6 +1,7 @@ morphMany(get_class(static::newModel()), 'revisionable'); + return $this->morphMany(Revision::class, 'revisionable'); } /** @@ -153,7 +156,7 @@ public function postSave() 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => $key, - 'old_value' => array_get($this->originalData, $key), + 'old_value' => Arr::get($this->originalData, $key), 'new_value' => $this->updatedData[$key], 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), @@ -162,7 +165,7 @@ public function postSave() } if (count($revisions) > 0) { - $revision = static::newModel(); + $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); } } @@ -195,7 +198,7 @@ public function postCreate() 'updated_at' => new \DateTime(), ); - $revision = static::newModel(); + $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); } } @@ -218,7 +221,7 @@ public function postDelete() 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); - $revision = static::newModel(); + $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); } } diff --git a/src/RevisionableResolver.php b/src/RevisionableResolver.php new file mode 100644 index 00000000..da383877 --- /dev/null +++ b/src/RevisionableResolver.php @@ -0,0 +1,40 @@ +route = $route; + $this->binding_lookup = config(\Venturecraft\Revisionable\ServiceProvider::SHORT_NAME . '.revisionable-model-binding'); + } + + public function bind($revisionable){ + list($alias, $id)= explode('-', $revisionable); + if(empty($this->binding_lookup[$alias])){ + abort(404, 'No binding instruction found.'); + } + + $class = $this->binding_lookup[$alias]; + return $class::findOrFail($id); + } + +} diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/RevisionableTrait.php similarity index 86% rename from src/Venturecraft/Revisionable/RevisionableTrait.php rename to src/RevisionableTrait.php index b668bc58..7590353b 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/RevisionableTrait.php @@ -7,6 +7,8 @@ * */ +use Illuminate\Support\Arr; + /** * Class RevisionableTrait * @package Venturecraft\Revisionable @@ -85,12 +87,28 @@ public static function bootRevisionableTrait() }); } + public function getRevisionableRouteParamAttribute(){ + return strtolower(str_replace('\\', '-',static::class) .'-'. $this->getKey()); + } + + /** * @return mixed */ public function revisionHistory() { - return $this->morphMany(get_class(Revisionable::newModel()), 'revisionable'); + return $this->morphMany(Revision::class, 'revisionable'); + } + + /** + * Restrict the result to include only records which has pending revision + * history which are not accepted yet. + */ + public function scopeHasPendingRevisionHistory($query) + { + $query->whereHas('revisionHistory', function($q){ + $q->whereNull('accepted_at'); + }); } /** @@ -102,7 +120,7 @@ public function revisionHistory() */ public static function classRevisionHistory($limit = 100, $order = 'desc') { - $model = Revisionable::newModel(); + $model = new Revision; return $model->where('revisionable_type', get_called_class()) ->orderBy('updated_at', $order)->limit($limit)->get(); } @@ -118,7 +136,9 @@ public function preSave() // if there's no revisionEnabled. Or if there is, if it's true $this->originalData = $this->original; + $this->updatedData = $this->attributes; + $this->updating = $this->exists; // we can only safely compare basic items, // so for now we drop any object based items, like DateTime @@ -144,7 +164,17 @@ public function preSave() unset($this->attributes['keepRevisionOf']); $this->dirtyData = $this->getDirty(); - $this->updating = $this->exists; + + $changes_to_record = $this->changedRevisionableFields(); + foreach ($changes_to_record as $key => $value) { + if($this->updating && !empty($this->autoAccept) && $this->autoAccept == false && !empty($this->keepRevisionOf) && in_array($key, $this->keepRevisionOf)){ + if(isset($this->originalData[$key])){ + \Log::debug('Changing value for key '.$key); + $this->attributes[$key] = $this->originalData[$key]; + } + } + } + } } @@ -180,24 +210,31 @@ public function postSave() 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => $key, - 'old_value' => array_get($this->originalData, $key), + 'old_value' => Arr::get($this->originalData, $key), 'new_value' => $this->updatedData[$key], 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), + 'accepted_at' => new \DateTime() ); } if (count($revisions) > 0) { if($LimitReached && $RevisionCleanup){ - $toDelete = $this->revisionHistory()->orderBy('id','asc')->limit(count($revisions))->get(); + $columns = collect($revisions)->pluck('key'); + $toDelete = $this->revisionHistory() + ->whereIn('key', $columns) + ->orderBy('id','asc') + ->limit(count($revisions)) + ->get(); + foreach($toDelete as $delete){ $delete->delete(); } } - $revision = Revisionable::newModel(); + $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); - \Event::fire('revisionable.saved', array('model' => $this, 'revisions' => $revisions)); + \Event::dispatch('revisionable.saved', array('model' => $this, 'revisions' => $revisions)); } } } @@ -227,11 +264,13 @@ public function postCreate() 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), + 'accepted_at' => new \DateTime() + ); - $revision = Revisionable::newModel(); + $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); - \Event::fire('revisionable.created', array('model' => $this, 'revisions' => $revisions)); + \Event::dispatch('revisionable.created', array('model' => $this, 'revisions' => $revisions)); } } @@ -255,9 +294,9 @@ public function postDelete() 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); - $revision = Revisionable::newModel(); + $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); - \Event::fire('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); + \Event::dispatch('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 00000000..0b7c5046 --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,51 @@ +loadMigrationsFrom(__DIR__ . '/../migrations'); + $this->publishes([__DIR__ . '/../config/main.php' => config_path(SELF::SHORT_NAME . '.php')]); + $this->mergeConfigFrom( + __DIR__ . '/../config/main.php', SELF::SHORT_NAME + ); + + $this->loadViewsFrom(__DIR__ . '/../resources/views', self::SHORT_NAME); + $this->publishes([__DIR__ . '/../resources/views' => resource_path('views/vendor/' . self::SHORT_NAME)], 'views'); + + $this->loadRoutesFrom(__DIR__ . '/../routes/main.php'); + + $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', self::SHORT_NAME); + $this->publishes([__DIR__ . '/../resources/lang' => resource_path('lang/vendor/' . self::SHORT_NAME)], 'lang'); + + Route::bind('revisionable', RevisionableResolver::class); + + } + + /** + * Register the application services. + * + * @return void + */ + public function register(){ + + } + +} diff --git a/src/Venturecraft/Revisionable/RevisionableServiceProvider.php b/src/Venturecraft/Revisionable/RevisionableServiceProvider.php deleted file mode 100644 index fb9fec26..00000000 --- a/src/Venturecraft/Revisionable/RevisionableServiceProvider.php +++ /dev/null @@ -1,42 +0,0 @@ -publishes([ - __DIR__ . '/../../config/revisionable.php' => config_path('revisionable.php'), - ], 'config'); - - $this->publishes([ - __DIR__ . '/../../migrations/' => database_path('migrations'), - ], 'migrations'); - } - - /** - * Register the application services. - * - * @return void - */ - public function register() - { - } - - /** - * Get the services provided by the provider. - * - * @return string[] - */ - public function provides() - { - } -}