From eb84ace275c3a7ba18bc1e7ace830ad5cb818ccf Mon Sep 17 00:00:00 2001 From: Roshan Gautam Date: Sat, 16 Jan 2016 22:15:56 +0000 Subject: [PATCH 01/74] Change function name from getUserId to getSystemUserId to avoid clash with the same function name in Sentinel User Model --- src/Venturecraft/Revisionable/RevisionableTrait.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 560fcba2..36e49434 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -181,7 +181,7 @@ public function postSave() 'key' => $key, 'old_value' => array_get($this->originalData, $key), 'new_value' => $this->updatedData[$key], - 'user_id' => $this->getUserId(), + 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); @@ -222,7 +222,7 @@ public function postCreate() 'key' => 'created_at', 'old_value' => null, 'new_value' => $this->created_at, - 'user_id' => $this->getUserId(), + 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); @@ -250,7 +250,7 @@ public function postDelete() 'key' => 'deleted_at', 'old_value' => null, 'new_value' => $this->deleted_at, - 'user_id' => $this->getUserId(), + 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); @@ -263,7 +263,7 @@ public function postDelete() * Attempt to find the user id of the currently logged in user * Supports Cartalyst Sentry/Sentinel based authentication, as well as stock Auth **/ - public function getUserId() + public function getSystemUserId() { try { if (class_exists($class = '\SleepingOwl\AdminAuth\Facades\AdminAuth') From cabe24e129d427f8d77fcd4af5763464976d9446 Mon Sep 17 00:00:00 2001 From: Juan Villegas Date: Tue, 9 Feb 2016 16:37:40 -0300 Subject: [PATCH 02/74] Update readme.md typo --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 07bbab14..db88c85e 100644 --- a/readme.md +++ b/readme.md @@ -128,7 +128,7 @@ class Article extends Eloquent { protected $historyLimit = 500; //Stop tracking revisions after 500 changes have been made. } ``` -In order to maintain a limit on history, but instead of stopping tracking revisions if you want to remove old revisions, you can accommodate that feature by setting `$revisionCleanup`. +In order to maintain a limit on history, but instead of stopping tracking revisions you want to remove old revisions, you can accommodate that feature by setting `$revisionCleanup`. ```php namespace MyApp\Models; From 80060d5bda626896e2599b5c34e7d68b880b345f Mon Sep 17 00:00:00 2001 From: Jake Bathman Date: Thu, 5 May 2016 14:41:35 -0500 Subject: [PATCH 03/74] Fixes #222, where the array addition operator overwrites exempted field names --- src/Venturecraft/Revisionable/RevisionableTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 560fcba2..92ce981d 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -132,11 +132,11 @@ public function preSave() // the below is ugly, for sure, but it's required so we can save the standard model // then use the keep / dontkeep values for later, in the isRevisionable method $this->dontKeep = isset($this->dontKeepRevisionOf) ? - $this->dontKeepRevisionOf + $this->dontKeep + array_merge($this->dontKeepRevisionOf, $this->dontKeep) : $this->dontKeep; $this->doKeep = isset($this->keepRevisionOf) ? - $this->keepRevisionOf + $this->doKeep + array_merge($this->keepRevisionOf, $this->doKeep) : $this->doKeep; unset($this->attributes['dontKeepRevisionOf']); From 1309fb046a3d2ab541c0b6c2bbe767ad59a70efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Tue, 17 May 2016 15:53:22 +0200 Subject: [PATCH 04/74] Added revisionable.* events --- readme.md | 21 ++++++++++++++++++- .../Revisionable/RevisionableTrait.php | 5 +++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index db88c85e..ba3dc646 100644 --- a/readme.md +++ b/readme.md @@ -128,7 +128,7 @@ class Article extends Eloquent { protected $historyLimit = 500; //Stop tracking revisions after 500 changes have been made. } ``` -In order to maintain a limit on history, but instead of stopping tracking revisions you want to remove old revisions, you can accommodate that feature by setting `$revisionCleanup`. +In order to maintain a limit on history, but instead of stopping tracking revisions if you want to remove old revisions, you can accommodate that feature by setting `$revisionCleanup`. ```php namespace MyApp\Models; @@ -181,6 +181,25 @@ protected $dontKeepRevisionOf = array( > The `$keepRevisionOf` setting takes precendence over `$dontKeepRevisionOf` +### Events + +Every time a model revision is created an event is fired. You can listen for `revisionable.created`, +`revisionable.saved` or `revisionable.deleted`. + +```php +// app/Providers/EventServiceProviders.php +public function boot(DispatcherContract $events) +{ + parent::boot($events); + + $events->listen('revisionable.*', function($revisions) { + // Do something with the revisions. + dd($revisions); + }); +} + +``` + ## Format output diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 560fcba2..c181dee8 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -196,6 +196,7 @@ public function postSave() } $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); + \Event::fire('revisionable.saved', array('revisions' => $revisions)); } } } @@ -229,10 +230,9 @@ public function postCreate() $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); - + \Event::fire('revisionable.created', array('revisions' => $revisions)); } - } /** @@ -256,6 +256,7 @@ public function postDelete() ); $revision = new \Venturecraft\Revisionable\Revision; \DB::table($revision->getTable())->insert($revisions); + \Event::fire('revisionable.deleted', array('revisions' => $revisions)); } } From c14e2b9387507270b00b6fa1e1856cf0249779e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Wed, 18 May 2016 19:03:08 +0200 Subject: [PATCH 05/74] Pass the modified model along with the event --- src/Venturecraft/Revisionable/RevisionableTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index c181dee8..dcf4a596 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -196,7 +196,7 @@ public function postSave() } $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); - \Event::fire('revisionable.saved', array('revisions' => $revisions)); + \Event::fire('revisionable.saved', array('model' => $this, 'revisions' => $revisions)); } } } @@ -230,7 +230,7 @@ public function postCreate() $revision = new Revision; \DB::table($revision->getTable())->insert($revisions); - \Event::fire('revisionable.created', array('revisions' => $revisions)); + \Event::fire('revisionable.created', array('model' => $this, 'revisions' => $revisions)); } } @@ -256,7 +256,7 @@ public function postDelete() ); $revision = new \Venturecraft\Revisionable\Revision; \DB::table($revision->getTable())->insert($revisions); - \Event::fire('revisionable.deleted', array('revisions' => $revisions)); + \Event::fire('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); } } From 920d0b7d2fa25b944a1167b47bdabf1f34a878e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Thu, 19 May 2016 20:35:31 +0200 Subject: [PATCH 06/74] Fixed readme --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index ba3dc646..21ffe5f3 100644 --- a/readme.md +++ b/readme.md @@ -192,9 +192,9 @@ public function boot(DispatcherContract $events) { parent::boot($events); - $events->listen('revisionable.*', function($revisions) { - // Do something with the revisions. - dd($revisions); + $events->listen('revisionable.*', function($model, $revisions) { + // Do something with the revisions or the changed model. + dd($model, $revisions); }); } From fb68b3d3145f419d707a3ca7ec55aa547a4f5e5e Mon Sep 17 00:00:00 2001 From: Austin Stierler Date: Tue, 14 Jun 2016 20:46:22 -0700 Subject: [PATCH 07/74] Update README + contributing docs --- CONTRIBUTING.md | 4 +++- readme.md | 22 +++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3df92b6d..c3f3ca82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,8 @@ # Contribution Guidelines Contributions are encouraged and welcome; to keep things organised, all bugs and requests should be -opened in the github issues tab for the main project, at [venturecraft/revisionable/issues](https://github.com/venturecraft/revisionable/issues) +opened in the GitHub "Issues" tab for the main project, at [venturecraft/revisionable/issues](https://github.com/venturecraft/revisionable/issues) + +Keep in mind, we are currently maintaining backward-compatibility with Laravel 4.x - please make sure youa Please submit all pull requests to the [revisionable/develop](https://github.com/VentureCraft/revisionable/tree/develop) branch, so they can be tested before being merged into the master branch. diff --git a/readme.md b/readme.md index 21ffe5f3..0d7d3aaa 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,10 @@ Revisionable - - - - - - +[![Laravel 4.x](https://img.shields.io/badge/Laravel-4.x-brightgreen.svg?style=flat-square)](http://laravel.com) +[![Laravel 5.2](https://img.shields.io/badge/Laravel-5.x-brightgreen.svg?style=flat-square)](http://laravel.com) +[![Latest Version](https://img.shields.io/github/release/venturecraft/revisionable.svg?style=flat-square)](https://packagist.org/packages/venturecraft/revisionable) +[![Downloads](https://img.shields.io/packagist/dt/venturecraft/revisionable.svg?style=flat-square)](https://packagist.org/packages/venturecraft/revisionable) +[![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) Wouldn't it be nice to have a revision history for any model in your project, without having to do any work for it. By simply extending revisionable from your model, you can instantly have just that, and be able to display a history similar to this: @@ -41,8 +40,13 @@ Run composer update to download the package php composer.phar update ``` -Finally, you'll also need to run migration on the package +Finally, you'll also need to run migration on the package (Laravel 5.x) +``` +php artisan migrate --path=vendor/venturecraft/revisionable/src/migrations +``` + +For Laravel 4.x users: ``` php artisan migrate --package=venturecraft/revisionable ``` @@ -359,7 +363,7 @@ $object->disableRevisionField(array('title', 'content')); // Disables title and ## Contributing Contributions are encouraged and welcome; to keep things organised, all bugs and requests should be -opened in the github issues tab for the main project, at [venturecraft/revisionable/issues](https://github.com/venturecraft/revisionable/issues) +opened in the GitHub issues tab for the main project, at [venturecraft/revisionable/issues](https://github.com/venturecraft/revisionable/issues) All pull requests should be made to the develop branch, so they can be tested before being merged into the master branch. @@ -369,6 +373,6 @@ All pull requests should be made to the develop branch, so they can be tested be If you're having troubles with using this package, odds on someone else has already had the same problem. Two places you can look for common answers to your problems are: * [StackOverflow revisionable tag](http://stackoverflow.com/questions/tagged/revisionable?sort=newest&pageSize=50) -* [Github Issues](https://github.com/VentureCraft/revisionable/issues?page=1&state=closed) +* [GitHub Issues](https://github.com/VentureCraft/revisionable/issues?page=1&state=closed) > If you do prefer posting your questions to the public on StackOverflow, please use the 'revisionable' tag. From 448b6c5e106ac8121cc6179a62f4c66a1ff87b46 Mon Sep 17 00:00:00 2001 From: Austin Stierler Date: Tue, 14 Jun 2016 22:46:20 -0700 Subject: [PATCH 08/74] Rename getUserId to getSystemUserId in non-trait for consistency --- src/Venturecraft/Revisionable/Revisionable.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index d78de8d0..434b7510 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -146,7 +146,7 @@ public function postSave() 'key' => $key, 'old_value' => array_get($this->originalData, $key), 'new_value' => $this->updatedData[$key], - 'user_id' => $this->getUserId(), + 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); @@ -181,7 +181,7 @@ public function postCreate() 'key' => 'created_at', 'old_value' => null, 'new_value' => $this->created_at, - 'user_id' => $this->getUserId(), + 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); @@ -206,7 +206,7 @@ public function postDelete() 'key' => 'deleted_at', 'old_value' => null, 'new_value' => $this->deleted_at, - 'user_id' => $this->getUserId(), + 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); @@ -219,7 +219,7 @@ public function postDelete() * Attempt to find the user id of the currently logged in user * Supports Cartalyst Sentry/Sentinel based authentication, as well as stock Auth **/ - private function getUserId() + private function getSystemUserId() { try { if (class_exists($class = '\Cartalyst\Sentry\Facades\Laravel\Sentry') From 2bdc5fcdd6c8e054ec41f1cbd5a0e9e2e69fd40e Mon Sep 17 00:00:00 2001 From: Austin Stierler Date: Wed, 15 Jun 2016 15:41:03 -0700 Subject: [PATCH 09/74] #223 non-trait compatibility --- src/Venturecraft/Revisionable/Revisionable.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 434b7510..5bc7e24d 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -107,11 +107,11 @@ public function preSave() // the below is ugly, for sure, but it's required so we can save the standard model // then use the keep / dontkeep values for later, in the isRevisionable method $this->dontKeep = isset($this->dontKeepRevisionOf) ? - $this->dontKeepRevisionOf + $this->dontKeep + array_merge($this->dontKeepRevisionOf, $this->dontKeep) : $this->dontKeep; $this->doKeep = isset($this->keepRevisionOf) ? - $this->keepRevisionOf + $this->doKeep + array_merge($this->keepRevisionOf, $this->doKeep) : $this->doKeep; unset($this->attributes['dontKeepRevisionOf']); From 0fa9e7a7bb03d9972c85030ed2e018ef762dc1ce Mon Sep 17 00:00:00 2001 From: Austin Stierler Date: Sat, 18 Jun 2016 17:52:59 -0700 Subject: [PATCH 10/74] Use model defined timestamp names (#220 + correct softDeletes handling) --- src/Venturecraft/Revisionable/Revisionable.php | 10 +++++----- src/Venturecraft/Revisionable/RevisionableTrait.php | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 5bc7e24d..b5a4e991 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -178,9 +178,9 @@ public function postCreate() $revisions[] = array( 'revisionable_type' => get_class($this), 'revisionable_id' => $this->getKey(), - 'key' => 'created_at', + 'key' => self::CREATED_AT, 'old_value' => null, - 'new_value' => $this->created_at, + 'new_value' => $this->{self::CREATED_AT}, 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), @@ -199,13 +199,13 @@ public function postDelete() { if ((!isset($this->revisionEnabled) || $this->revisionEnabled) && $this->isSoftDelete() - && $this->isRevisionable('deleted_at')) { + && $this->isRevisionable($this->getDeletedAtColumn())) { $revisions[] = array( 'revisionable_type' => get_class($this), 'revisionable_id' => $this->getKey(), - 'key' => 'deleted_at', + 'key' => $this->getDeletedAtColumn(), 'old_value' => null, - 'new_value' => $this->deleted_at, + 'new_value' => $this->{$this->getDeletedAtColumn()}, 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 461659a5..59196436 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -220,9 +220,9 @@ public function postCreate() $revisions[] = array( 'revisionable_type' => get_class($this), 'revisionable_id' => $this->getKey(), - 'key' => 'created_at', + 'key' => self::CREATED_AT, 'old_value' => null, - 'new_value' => $this->created_at, + 'new_value' => $this->{self::CREATED_AT}, 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), @@ -242,14 +242,14 @@ public function postDelete() { if ((!isset($this->revisionEnabled) || $this->revisionEnabled) && $this->isSoftDelete() - && $this->isRevisionable('deleted_at') + && $this->isRevisionable($this->getDeletedAtColumn()) ) { $revisions[] = array( 'revisionable_type' => get_class($this), 'revisionable_id' => $this->getKey(), - 'key' => 'deleted_at', + 'key' => $this->getDeletedAtColumn(), 'old_value' => null, - 'new_value' => $this->deleted_at, + 'new_value' => $this->{$this->getDeletedAtColumn()}, 'user_id' => $this->getSystemUserId(), 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), From fa83b245bb0f13a13dc85862210baf114555ff4f Mon Sep 17 00:00:00 2001 From: Matthew Mawdsley Date: Mon, 11 Jul 2016 10:33:46 +0100 Subject: [PATCH 11/74] Fixed issue where keys containing _id but not at the end of the string were considered relationships. --- src/Venturecraft/Revisionable/Revision.php | 36 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Venturecraft/Revisionable/Revision.php index 98d429ef..acbc20cc 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Venturecraft/Revisionable/Revision.php @@ -133,8 +133,8 @@ private function getValue($which = 'new') $main_model = new $main_model; try { - if (strpos($this->key, '_id')) { - $related_model = str_replace('_id', '', $this->key); + if ($this->isRelated()) { + $related_model = $this->getRelatedModel(); // Now we can find out the namespace of of related model if (!method_exists($main_model, $related_model)) { @@ -187,6 +187,38 @@ private function getValue($which = 'new') return $this->format($this->key, $this->$which_value); } + /** + * Return true if the key is for a related model. + * + * @return bool + */ + private function isRelated() + { + $isRelated = false; + $idSuffix = '_id'; + $pos = strrpos($this->key, $idSuffix); + + if ($pos !== false + && strlen($this->key) - strlen($idSuffix) === $pos + ) { + $isRelated = true; + } + + return $isRelated; + } + + /** + * Return the name of the related model. + * + * @return string + */ + private function getRelatedModel() + { + $idSuffix = '_id'; + + return substr($this->key, 0, strlen($this->key) - strlen($idSuffix)); + } + /** * User Responsible. * From 803c61d488d7132487a8f46f5b7de62a0e7bf4ce Mon Sep 17 00:00:00 2001 From: Austin Stierler Date: Mon, 1 Aug 2016 19:00:27 -0700 Subject: [PATCH 12/74] use getMorphClass() for revisionable_type --- src/Venturecraft/Revisionable/Revisionable.php | 6 +++--- src/Venturecraft/Revisionable/RevisionableTrait.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index b5a4e991..7099ef50 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -141,7 +141,7 @@ public function postSave() foreach ($changes_to_record as $key => $change) { $revisions[] = array( - 'revisionable_type' => get_class($this), + 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => $key, 'old_value' => array_get($this->originalData, $key), @@ -176,7 +176,7 @@ public function postCreate() if ((!isset($this->revisionEnabled) || $this->revisionEnabled)) { $revisions[] = array( - 'revisionable_type' => get_class($this), + 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => self::CREATED_AT, 'old_value' => null, @@ -201,7 +201,7 @@ public function postDelete() && $this->isSoftDelete() && $this->isRevisionable($this->getDeletedAtColumn())) { $revisions[] = array( - 'revisionable_type' => get_class($this), + 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => $this->getDeletedAtColumn(), 'old_value' => null, diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 59196436..f298dcf7 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -176,7 +176,7 @@ public function postSave() foreach ($changes_to_record as $key => $change) { $revisions[] = array( - 'revisionable_type' => get_class($this), + 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => $key, 'old_value' => array_get($this->originalData, $key), @@ -218,7 +218,7 @@ public function postCreate() if ((!isset($this->revisionEnabled) || $this->revisionEnabled)) { $revisions[] = array( - 'revisionable_type' => get_class($this), + 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => self::CREATED_AT, 'old_value' => null, @@ -245,7 +245,7 @@ public function postDelete() && $this->isRevisionable($this->getDeletedAtColumn()) ) { $revisions[] = array( - 'revisionable_type' => get_class($this), + 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => $this->getDeletedAtColumn(), 'old_value' => null, From 35cdf14543723dc7ad5533c3eeb9d5304612f6ae Mon Sep 17 00:00:00 2001 From: Rias Date: Thu, 27 Oct 2016 16:38:39 +0200 Subject: [PATCH 13/74] Use array_key_exists instead of isset This fixes an issue where revisions are being added when a field is "null" and is being updated to "null" which is the same value. --- src/Venturecraft/Revisionable/RevisionableTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f298dcf7..f1041568 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -295,7 +295,7 @@ private function changedRevisionableFields() // check that the field is revisionable, and double check // that it's actually new data in case dirty is, well, clean if ($this->isRevisionable($key) && !is_array($value)) { - if (!isset($this->originalData[$key]) || $this->originalData[$key] != $this->updatedData[$key]) { + if (!array_key_exists($key, $this->originalData) || $this->originalData[$key] != $this->updatedData[$key]) { $changes_to_record[$key] = $value; } } else { From ed0847c96f5cdd9c5810e5eaa053deec7a4cb651 Mon Sep 17 00:00:00 2001 From: Adrian Chen Date: Wed, 21 Dec 2016 14:17:18 +0800 Subject: [PATCH 14/74] add custom model support --- readme.md | 17 +++++++- .../Revisionable/Revisionable.php | 20 ++++++--- .../RevisionableServiceProvider.php | 42 +++++++++++++++++++ .../Revisionable/RevisionableTrait.php | 13 +++--- src/config/revisionable.php | 10 +++++ 5 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 src/Venturecraft/Revisionable/RevisionableServiceProvider.php create mode 100644 src/config/revisionable.php diff --git a/readme.md b/readme.md index 0d7d3aaa..5bcb2793 100644 --- a/readme.md +++ b/readme.md @@ -40,10 +40,25 @@ Run composer update to download the package php composer.phar update ``` +Open config/app.php and register the required service provider + +``` +'providers' => [ + Venturecraft\Revisionable\RevisionableServiceProvider::class, +] +``` + +Publish the configuration and migrations + +``` +# Laravel 5.x +php artisan vendor:publish --provider="Venturecraft\Revisionable\RevisionableServiceProvider" +``` + Finally, you'll also need to run migration on the package (Laravel 5.x) ``` -php artisan migrate --path=vendor/venturecraft/revisionable/src/migrations +php artisan migrate ``` For Laravel 4.x users: diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 7099ef50..877404f6 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -64,7 +64,7 @@ public static function boot() $model->postSave(); }); - static::created(function($model){ + static::created(function ($model) { $model->postCreate(); }); @@ -73,13 +73,22 @@ public static function boot() $model->postDelete(); }); } + /** + * Instance the revision model + * @return \Illuminate\Database\Eloquent\Model + */ + public static function newModel() + { + $model = \Config::get('revisionable.model'); + return new $model; + } /** * @return mixed */ public function revisionHistory() { - return $this->morphMany('\Venturecraft\Revisionable\Revision', 'revisionable'); + return $this->morphMany(get_class(static::newModel()), 'revisionable'); } /** @@ -153,7 +162,7 @@ public function postSave() } if (count($revisions) > 0) { - $revision = new Revision; + $revision = static::newModel(); \DB::table($revision->getTable())->insert($revisions); } } @@ -186,9 +195,8 @@ public function postCreate() 'updated_at' => new \DateTime(), ); - $revision = new Revision; + $revision = static::newModel(); \DB::table($revision->getTable())->insert($revisions); - } } @@ -210,7 +218,7 @@ public function postDelete() 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); - $revision = new \Venturecraft\Revisionable\Revision; + $revision = static::newModel(); \DB::table($revision->getTable())->insert($revisions); } } diff --git a/src/Venturecraft/Revisionable/RevisionableServiceProvider.php b/src/Venturecraft/Revisionable/RevisionableServiceProvider.php new file mode 100644 index 00000000..fb9fec26 --- /dev/null +++ b/src/Venturecraft/Revisionable/RevisionableServiceProvider.php @@ -0,0 +1,42 @@ +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() + { + } +} diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f298dcf7..722e4c71 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -75,7 +75,7 @@ public static function bootRevisionableTrait() $model->postSave(); }); - static::created(function($model){ + static::created(function ($model) { $model->postCreate(); }); @@ -90,7 +90,7 @@ public static function bootRevisionableTrait() */ public function revisionHistory() { - return $this->morphMany('\Venturecraft\Revisionable\Revision', 'revisionable'); + return $this->morphMany(get_class(Revisionable::newModel()), 'revisionable'); } /** @@ -102,7 +102,8 @@ public function revisionHistory() */ public static function classRevisionHistory($limit = 100, $order = 'desc') { - return \Venturecraft\Revisionable\Revision::where('revisionable_type', get_called_class()) + $model = Revisionable::newModel(); + return $model->where('revisionable_type', get_called_class()) ->orderBy('updated_at', $order)->limit($limit)->get(); } @@ -194,7 +195,7 @@ public function postSave() $delete->delete(); } } - $revision = new Revision; + $revision = Revisionable::newModel(); \DB::table($revision->getTable())->insert($revisions); \Event::fire('revisionable.saved', array('model' => $this, 'revisions' => $revisions)); } @@ -228,7 +229,7 @@ public function postCreate() 'updated_at' => new \DateTime(), ); - $revision = new Revision; + $revision = Revisionable::newModel(); \DB::table($revision->getTable())->insert($revisions); \Event::fire('revisionable.created', array('model' => $this, 'revisions' => $revisions)); } @@ -254,7 +255,7 @@ public function postDelete() 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); - $revision = new \Venturecraft\Revisionable\Revision; + $revision = Revisionable::newModel(); \DB::table($revision->getTable())->insert($revisions); \Event::fire('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); } diff --git a/src/config/revisionable.php b/src/config/revisionable.php new file mode 100644 index 00000000..4f6666b8 --- /dev/null +++ b/src/config/revisionable.php @@ -0,0 +1,10 @@ + Venturecraft\Revisionable\Revision::class, +]; From 6aec665b26d733b9c016a4d30396502a7298938d Mon Sep 17 00:00:00 2001 From: Adrian Chen Date: Wed, 21 Dec 2016 14:23:41 +0800 Subject: [PATCH 15/74] add default model --- src/Venturecraft/Revisionable/Revisionable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 877404f6..82348834 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -79,7 +79,7 @@ public static function boot() */ public static function newModel() { - $model = \Config::get('revisionable.model'); + $model = \Config::get('revisionable.model', 'Venturecraft\Revisionable\Revision'); return new $model; } From 8bc161fc4b0150e1bdeeb0933a4b9d10b7a1c7a9 Mon Sep 17 00:00:00 2001 From: Adrian Chen Date: Wed, 21 Dec 2016 14:42:37 +0800 Subject: [PATCH 16/74] change Installation section for Laravel 5.x --- readme.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 5bcb2793..9345839f 100644 --- a/readme.md +++ b/readme.md @@ -40,7 +40,7 @@ Run composer update to download the package php composer.phar update ``` -Open config/app.php and register the required service provider +Open config/app.php and register the required service provider (Laravel 5.x) ``` 'providers' => [ @@ -48,10 +48,9 @@ Open config/app.php and register the required service provider ] ``` -Publish the configuration and migrations +Publish the configuration and migrations (Laravel 5.x) ``` -# Laravel 5.x php artisan vendor:publish --provider="Venturecraft\Revisionable\RevisionableServiceProvider" ``` From 63c763ff1d4d6fcd52c54a5a1b7952ba120b6d46 Mon Sep 17 00:00:00 2001 From: Scott Cariss Date: Fri, 27 Jan 2017 20:08:52 +0000 Subject: [PATCH 17/74] Get actual class name for morph --- src/Venturecraft/Revisionable/Revision.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Venturecraft/Revisionable/Revision.php index acbc20cc..f33f1eb4 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Venturecraft/Revisionable/Revision.php @@ -75,7 +75,7 @@ public function fieldName() */ private function formatFieldName($key) { - $related_model = $this->revisionable_type; + $related_model = $this->getActualClassNameForMorph($this->revisionable_type); $related_model = new $related_model; $revisionFormattedFieldNames = $related_model->getRevisionFormattedFieldNames(); @@ -278,7 +278,7 @@ public function historyOf() */ public function format($key, $value) { - $related_model = $this->revisionable_type; + $related_model = $this->getActualClassNameForMorph($this->revisionable_type); $related_model = new $related_model; $revisionFormattedFields = $related_model->getRevisionFormattedFields(); From 05aedc612d26162ec3882a26c3b5de29145b94f7 Mon Sep 17 00:00:00 2001 From: Sebastien HEYD Date: Thu, 20 Apr 2017 14:40:40 +0200 Subject: [PATCH 18/74] Closes VentureCraft/revisionable#186 Simply check if model use RevisionableTrait or not. Avoid to log info that is not useful --- src/Venturecraft/Revisionable/Revision.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Venturecraft/Revisionable/Revision.php index acbc20cc..1d0dcf1e 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Venturecraft/Revisionable/Revision.php @@ -160,14 +160,16 @@ private function getValue($which = 'new') return $this->format($this->key, $item->getRevisionUnknownString()); } + // Check if model use RevisionableTrait + if(method_exists($item, 'identifiableName')) { + // see if there's an available mutator + $mutator = 'get' . studly_case($this->key) . 'Attribute'; + if (method_exists($item, $mutator)) { + return $this->format($item->$mutator($this->key), $item->identifiableName()); + } - // see if there's an available mutator - $mutator = 'get' . studly_case($this->key) . 'Attribute'; - if (method_exists($item, $mutator)) { - return $this->format($item->$mutator($this->key), $item->identifiableName()); + return $this->format($this->key, $item->identifiableName()); } - - return $this->format($this->key, $item->identifiableName()); } } catch (\Exception $e) { // Just a fail-safe, in the case the data setup isn't as expected From 9b0c17791c3ebbc0b8e7362f42bbd9960352e8d2 Mon Sep 17 00:00:00 2001 From: Vladimir Pyatnitskiy Date: Mon, 1 May 2017 06:03:00 +0300 Subject: [PATCH 19/74] Don't log if nothing is wrong --- src/Venturecraft/Revisionable/Revision.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Venturecraft/Revisionable/Revision.php index acbc20cc..c76ffb1b 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Venturecraft/Revisionable/Revision.php @@ -172,7 +172,6 @@ private function getValue($which = 'new') } catch (\Exception $e) { // Just a fail-safe, in the case the data setup isn't as expected // Nothing to do here. - Log::info('Revisionable: ' . $e); } // if there was an issue From a698de58ee2f69420c9574c355648b56b43e74dd Mon Sep 17 00:00:00 2001 From: Vladimir Pyatnitskiy Date: Wed, 17 May 2017 01:06:17 +0300 Subject: [PATCH 20/74] Don't convert model to string unnecessarily Using model instance in string context causes its serialization (since in Eloquent, __toString() calls toJson(), which in turn calls toArray()). This fails if the model is not expected to be serialized when empty, or until saved. It's enough to mention model class name for the exception message to be clear (and the exception's thrown away anyway). --- src/Venturecraft/Revisionable/Revision.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Venturecraft/Revisionable/Revision.php index acbc20cc..5b8472f8 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Venturecraft/Revisionable/Revision.php @@ -140,7 +140,7 @@ private function getValue($which = 'new') if (!method_exists($main_model, $related_model)) { $related_model = camel_case($related_model); // for cases like published_status_id if (!method_exists($main_model, $related_model)) { - throw new \Exception('Relation ' . $related_model . ' does not exist for ' . $main_model); + throw new \Exception('Relation ' . $related_model . ' does not exist for ' . get_class($main_model)); } } $related_class = $main_model->$related_model()->getRelated(); From 717b3206c07a18338a2acb5d6c3768507d712da8 Mon Sep 17 00:00:00 2001 From: Austin Stierler Date: Sun, 21 May 2017 20:31:56 -0700 Subject: [PATCH 21/74] Cleanup examples, readme (fix non-https links), bump PHP requirements, remove some wording about supporting Laravel 4.x going forward (want to start to phase out) --- CONTRIBUTING.md | 2 -- composer.json | 64 +++++++++++++++++++----------------- readme.md | 86 ++++++++++++++++++++++--------------------------- 3 files changed, 74 insertions(+), 78 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3f3ca82..b7cbd52f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,4 @@ Contributions are encouraged and welcome; to keep things organised, all bugs and requests should be opened in the GitHub "Issues" tab for the main project, at [venturecraft/revisionable/issues](https://github.com/venturecraft/revisionable/issues) -Keep in mind, we are currently maintaining backward-compatibility with Laravel 4.x - please make sure youa - Please submit all pull requests to the [revisionable/develop](https://github.com/VentureCraft/revisionable/tree/develop) branch, so they can be tested before being merged into the master branch. diff --git a/composer.json b/composer.json index 52677d68..884b8f8f 100644 --- a/composer.json +++ b/composer.json @@ -1,29 +1,35 @@ -{ - "name": "venturecraft/revisionable", - "license": "MIT", - "description": "Keep a revision history for your models without thinking, created as a package for use with Laravel", - "keywords": ["model", "laravel", "ardent", "revision", "history"], - "homepage": "http://github.com/venturecraft/revisionable", - "authors": [ - { - "name": "Chris Duell", - "email": "me@chrisduell.com" - } - ], - "support": { - "issues": "https://github.com/VentureCraft/revisionable/issues", - "source": "https://github.com/VentureCraft/revisionable" - }, - "require": { - "php": ">=5.3.0", - "illuminate/support": "~4.0|~5.0|~5.1" - }, - "autoload": { - "classmap": [ - "src/migrations" - ], - "psr-0": { - "Venturecraft\\Revisionable": "src/" - } - } -} +{ + "name": "venturecraft/revisionable", + "license": "MIT", + "description": "Keep a revision history for your models without thinking, created as a package for use with Laravel", + "keywords": ["model", "laravel", "ardent", "revision", "history", "audit", "eloquent"], + "homepage": "https://github.com/venturecraft/revisionable", + "authors": [ + { + "name": "Chris Duell", + "email": "me@chrisduell.com", + "role": "Original Developer" + }, + { + "name": "Austin Stierler", + "email": "austin.stierler@gmail.com", + "role": "Developer/Maintainer" + } + ], + "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/" + } + } +} diff --git a/readme.md b/readme.md index 9345839f..01a057d0 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,10 @@ -Revisionable +Revisionable for Laravel -[![Laravel 4.x](https://img.shields.io/badge/Laravel-4.x-brightgreen.svg?style=flat-square)](http://laravel.com) -[![Laravel 5.2](https://img.shields.io/badge/Laravel-5.x-brightgreen.svg?style=flat-square)](http://laravel.com) +[![Laravel 4.x](https://img.shields.io/badge/Laravel-4.x-yellow.svg?style=flat-square)](https://laravel.com/) +[![Laravel 5.2](https://img.shields.io/badge/Laravel-5.x-brightgreen.svg?style=flat-square)](https://laravel.com/) [![Latest Version](https://img.shields.io/github/release/venturecraft/revisionable.svg?style=flat-square)](https://packagist.org/packages/venturecraft/revisionable) [![Downloads](https://img.shields.io/packagist/dt/venturecraft/revisionable.svg?style=flat-square)](https://packagist.org/packages/venturecraft/revisionable) -[![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) +[![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) Wouldn't it be nice to have a revision history for any model in your project, without having to do any work for it. By simply extending revisionable from your model, you can instantly have just that, and be able to display a history similar to this: @@ -22,11 +22,11 @@ Revisionable has support for Auth powered by * [**Sentry by Cartalyst**](https://cartalyst.com/manual/sentry). * [**Sentinel by Cartalyst**](https://cartalyst.com/manual/sentinel). -Revisionable can also now be used [as a trait](#the-new-trait-based-implementation), so your models can continue to extend Eloquent, or any other class that extends Eloquent (like [Ardent](https://github.com/laravelbook/ardent)). +*(Recommended)* Revisionable can also now be used [as a Trait](#the-new-trait-based-implementation), so your models can continue to extend Eloquent, or any other class that extends Eloquent (like [Ardent](https://github.com/laravelbook/ardent)). ## Installation -Revisionable is installable via [composer](http://getcomposer.org/doc/00-intro.md), the details are on [packagist, here.](https://packagist.org/packages/venturecraft/revisionable) +Revisionable is installable via [composer](https://getcomposer.org/doc/00-intro.md), the details are on [packagist, here.](https://packagist.org/packages/venturecraft/revisionable) Add the following to the `require` section of your projects composer.json file: @@ -82,27 +82,23 @@ php artisan migrate --package=venturecraft/revisionable ## Implementation -### The new, trait based implementation +### The new, Trait based implementation (recommended) +> Traits require PHP >= 5.4 For any model that you want to keep a revision history for, include the revisionable namespace and use the `RevisionableTrait` in your model, e.g., If you are using another bootable trait the be sure to override the boot method in your model; ```php -namespace MyApp\Models; +namespace App; -class Article extends Eloquent { - use \Venturecraft\Revisionable\RevisionableTrait; +use \Venturecraft\Revisionable\RevisionableTrait; - public static function boot() - { - parent::boot(); - } +class Article extends \Illuminate\Database\Eloquent\Model { + use RevisionableTrait; } ``` -> Being a trait, revisionable can now be used with the standard Eloquent model, or any class that extends Eloquent, like [Ardent](https://github.com/laravelbook/ardent) for example. - -> Traits require PHP >= 5.4 +> Being a trait, revisionable can now be used with the standard Eloquent model, or any class that extends Eloquent, such as [Ardent](https://github.com/laravelbook/ardent). ### Legacy class based implementation @@ -113,23 +109,23 @@ For any model that you want to keep a revision history for, include the revision ```php use Venturecraft\Revisionable\Revisionable; -namespace MyApp\Models; +namespace App; class Article extends Revisionable { } ``` -Note that it also works with namespaced models. +> Note: This also works with namespaced models. ### Implementation notes If needed, you can disable the revisioning by setting `$revisionEnabled` to false in your model. This can be handy if you want to temporarily disable revisioning, or if you want to create your own base model that extends revisionable, which all of your models extend, but you want to turn revisionable off for certain models. ```php -namespace MyApp\Models; +namespace App; -class Article extends Eloquent { - use Venturecraft\Revisionable\RevisionableTrait; +use \Venturecraft\Revisionable\RevisionableTrait; +class Article extends \Illuminate\Database\Eloquent\Model { protected $revisionEnabled = false; } ``` @@ -137,11 +133,11 @@ class Article extends Eloquent { You can also disable revisioning after X many revisions have been made by setting `$historyLimit` to the number of revisions you want to keep before stopping revisions. ```php -namespace MyApp\Models; +namespace App; -class Article extends Eloquent { - use Venturecraft\Revisionable\RevisionableTrait; +use \Venturecraft\Revisionable\RevisionableTrait; +class Article extends \Illuminate\Database\Eloquent\Model { protected $revisionEnabled = true; protected $historyLimit = 500; //Stop tracking revisions after 500 changes have been made. } @@ -149,11 +145,11 @@ class Article extends Eloquent { In order to maintain a limit on history, but instead of stopping tracking revisions if you want to remove old revisions, you can accommodate that feature by setting `$revisionCleanup`. ```php -namespace MyApp\Models; +namespace App; -class Article extends Eloquent { - use Venturecraft\Revisionable\RevisionableTrait; +use \Venturecraft\Revisionable\RevisionableTrait; +class Article extends \Illuminate\Database\Eloquent\Model { protected $revisionEnabled = true; protected $revisionCleanup = true; //Remove old revisions (works only when used with $historyLimit) protected $historyLimit = 500; //Maintain a maximum of 500 changes at any point of time, while cleaning up old revisions. @@ -184,20 +180,16 @@ protected $revisionCreationsEnabled = true; No doubt, there'll be cases where you don't want to store a revision history only for certain fields of the model, this is supported in two different ways. In your model you can either specifiy which fields you explicitly want to track and all other fields are ignored: ```php -protected $keepRevisionOf = array( - 'title' -); +protected $keepRevisionOf = ['title']; ``` Or, you can specify which fields you explicitly don't want to track. All other fields will be tracked. ```php -protected $dontKeepRevisionOf = array( - 'category_id' -); +protected $dontKeepRevisionOf = ['category_id']; ``` -> The `$keepRevisionOf` setting takes precendence over `$dontKeepRevisionOf` +> The `$keepRevisionOf` setting takes precedence over `$dontKeepRevisionOf` ### Events @@ -222,28 +214,28 @@ public function boot(DispatcherContract $events) ## Format output > You can continue (and are encouraged to) use `eloquent accessors` in your model to set the -output of your values, see the [laravel docs for more information on accessors](http://laravel.com/docs/eloquent-mutators#accessors-and-mutators) +output of your values, see the [Laravel docs for more information on accessors](https://laravel.com/docs/eloquent-mutators#accessors-and-mutators) > The below documentation is therefor deprecated In cases where you want to have control over the format of the output of the values, for example a boolean field, you can set them in the `$revisionFormattedFields` array in your model. e.g., ```php -protected $revisionFormattedFields = array( - 'title' => 'string:%s', - 'public' => 'boolean:No|Yes', - 'modified' => 'datetime:m/d/Y g:i A', +protected $revisionFormattedFields = [ + 'title' => 'string:%s', + 'public' => 'boolean:No|Yes', + 'modified' => 'datetime:m/d/Y g:i A', 'deleted_at' => 'isEmpty:Active|Deleted' -); +]; ``` You can also override the field name output using the `$revisionFormattedFieldNames` array in your model, e.g., ```php -protected $revisionFormattedFieldNames = array( - 'title' => 'Title', +protected $revisionFormattedFieldNames = [ + 'title' => 'Title', 'small_name' => 'Nickname', 'deleted_at' => 'Deleted At' -); +]; ``` This comes into play when you output the revision field name using `$revision->fieldName()` @@ -370,7 +362,7 @@ $object->disableRevisionField('title'); // Disables title or: ```php -$object->disableRevisionField(array('title', 'content')); // Disables title and content +$object->disableRevisionField(['title', 'content']); // Disables title and content ``` @@ -386,7 +378,7 @@ All pull requests should be made to the develop branch, so they can be tested be If you're having troubles with using this package, odds on someone else has already had the same problem. Two places you can look for common answers to your problems are: -* [StackOverflow revisionable tag](http://stackoverflow.com/questions/tagged/revisionable?sort=newest&pageSize=50) -* [GitHub Issues](https://github.com/VentureCraft/revisionable/issues?page=1&state=closed) +* [StackOverflow revisionable tag](https://stackoverflow.com/questions/tagged/revisionable?sort=newest&pageSize=50) +* [GitHub Issues](https://github.com/VentureCraft/revisionable/issues) > If you do prefer posting your questions to the public on StackOverflow, please use the 'revisionable' tag. From 8dc4ed7c3479d8acc4a933e4f435d7b44b9d6cde Mon Sep 17 00:00:00 2001 From: Austin Stierler Date: Sun, 21 May 2017 20:40:10 -0700 Subject: [PATCH 22/74] minor readme changes --- readme.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/readme.md b/readme.md index 01a057d0..e4989d3b 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ [![Downloads](https://img.shields.io/packagist/dt/venturecraft/revisionable.svg?style=flat-square)](https://packagist.org/packages/venturecraft/revisionable) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) -Wouldn't it be nice to have a revision history for any model in your project, without having to do any work for it. By simply extending revisionable from your model, you can instantly have just that, and be able to display a history similar to this: +Wouldn't it be nice to have a revision history for any model in your project, without having to do any work for it. By simply adding the `RevisionableTrait` Trait to your model, you can instantly have just that, and be able to display a history similar to this: * Chris changed title from 'Something' to 'Something else' * Chris changed category from 'News' to 'Breaking news' @@ -85,8 +85,7 @@ php artisan migrate --package=venturecraft/revisionable ### The new, Trait based implementation (recommended) > Traits require PHP >= 5.4 -For any model that you want to keep a revision history for, include the revisionable namespace and use the `RevisionableTrait` in your model, e.g., -If you are using another bootable trait the be sure to override the boot method in your model; +For any model that you want to keep a revision history for, include the `VentureCraft\Revisionable` namespace and use the `RevisionableTrait` in your model, e.g., ```php namespace App; @@ -98,13 +97,13 @@ class Article extends \Illuminate\Database\Eloquent\Model { } ``` -> Being a trait, revisionable can now be used with the standard Eloquent model, or any class that extends Eloquent, such as [Ardent](https://github.com/laravelbook/ardent). +> Being a trait, Revisionable can now be used with the standard Eloquent model, or any class that extends Eloquent, such as [Ardent](https://github.com/laravelbook/ardent). ### Legacy class based implementation > The new trait based approach is backwards compatible with existing installations of Revisionable. You can still use the below installation instructions, which essentially is extending a wrapper for the trait. -For any model that you want to keep a revision history for, include the revisionable namespace and extend revisionable instead of eloquent, e.g., +For any model that you want to keep a revision history for, include the `VentureCraft\Revisionable` namespace and use the `RevisionableTrait` in your model, e.g., ```php use Venturecraft\Revisionable\Revisionable; @@ -118,7 +117,7 @@ class Article extends Revisionable { } ### Implementation notes -If needed, you can disable the revisioning by setting `$revisionEnabled` to false in your model. This can be handy if you want to temporarily disable revisioning, or if you want to create your own base model that extends revisionable, which all of your models extend, but you want to turn revisionable off for certain models. +If needed, you can disable the revisioning by setting `$revisionEnabled` to false in your Model. This can be handy if you want to temporarily disable revisioning, or if you want to create your own base Model that extends Revisionable, which all of your models extend, but you want to turn Revisionable off for certain models. ```php namespace App; @@ -156,9 +155,8 @@ class Article extends \Illuminate\Database\Eloquent\Model { } ``` -### Storing soft deletes - -By default, if your model supports soft deletes, revisionable will store this and any restores as updates on the model. +### Storing Soft Deletes +By default, if your model supports soft deletes, Revisionable will store this and any restores as updates on the model. You can choose to ignore deletes and restores by adding `deleted_at` to your `$dontKeepRevisionOf` array. @@ -166,7 +164,7 @@ To better format the output for `deleted_at` entries, you can use the `isEmpty` -### Storing creations +### Storing Creations By default the creation of a new model is not stored as a revision. Only subsequent changes to a model is stored. @@ -175,7 +173,7 @@ If you want to store the creation as a revision you can override this behavior b protected $revisionCreationsEnabled = true; ``` -## More control +## More Control No doubt, there'll be cases where you don't want to store a revision history only for certain fields of the model, this is supported in two different ways. In your model you can either specifiy which fields you explicitly want to track and all other fields are ignored: @@ -197,10 +195,11 @@ Every time a model revision is created an event is fired. You can listen for `re `revisionable.saved` or `revisionable.deleted`. ```php -// app/Providers/EventServiceProviders.php -public function boot(DispatcherContract $events) +// app/Providers/EventServiceProvider.php + +public function boot() { - parent::boot($events); + parent::boot(); $events->listen('revisionable.*', function($model, $revisions) { // Do something with the revisions or the changed model. @@ -213,8 +212,8 @@ public function boot(DispatcherContract $events) ## Format output -> You can continue (and are encouraged to) use `eloquent accessors` in your model to set the -output of your values, see the [Laravel docs for more information on accessors](https://laravel.com/docs/eloquent-mutators#accessors-and-mutators) +> You can continue (and are encouraged to) use `Eloquent accessors` in your model to set the +output of your values, see the [Laravel Documentation for more information on accessors](https://laravel.com/docs/eloquent-mutators#accessors-and-mutators) > The below documentation is therefor deprecated In cases where you want to have control over the format of the output of the values, for example a boolean field, you can set them in the `$revisionFormattedFields` array in your model. e.g., From 08f1dc50f519344e5a86552e93c35920238b0e89 Mon Sep 17 00:00:00 2001 From: James Judd Date: Wed, 18 Apr 2018 15:55:57 +0100 Subject: [PATCH 23/74] adds orchestra and first tests to project --- composer.json | 8 +++++ phpunit.xml | 29 +++++++++++++++ tests/Models/User.php | 20 +++++++++++ tests/RevisionTest.php | 82 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 phpunit.xml create mode 100644 tests/Models/User.php create mode 100644 tests/RevisionTest.php diff --git a/composer.json b/composer.json index 52677d68..dc58ed00 100644 --- a/composer.json +++ b/composer.json @@ -25,5 +25,13 @@ "psr-0": { "Venturecraft\\Revisionable": "src/" } + }, + "autoload-dev": { + "psr-4": { + "Venturecraft\\Revisionable\\Tests\\": "tests/" + } + }, + "require-dev": { + "orchestra/testbench": "~3.0" } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..96d96447 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,29 @@ + + + + + + ./tests/ + + + + + + src/ + + + + + + + \ No newline at end of file diff --git a/tests/Models/User.php b/tests/Models/User.php new file mode 100644 index 00000000..1bbc835d --- /dev/null +++ b/tests/Models/User.php @@ -0,0 +1,20 @@ +loadLaravelMigrations(['--database' => 'testing']); + + // call migrations specific to our tests, e.g. to seed the db + // the path option should be an absolute path. + $this->loadMigrationsFrom([ + '--database' => 'testing', + '--path' => realpath(__DIR__.'/../src/migrations'), + ]); + } + + /** + * Define environment setup. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function getEnvironmentSetUp($app) + { + // Setup default database to use sqlite :memory: + $app['config']->set('database.default', 'testbench'); + $app['config']->set('database.connections.testbench', array( + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + )); + } + + /** + * Test we can interact with the database + */ + public function testUsersTable() + { + User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'password' => \Hash::make('456'), + ]); + + $users = User::findOrFail(1); + $this->assertEquals('james.judd@revisionable.test', $users->email); + $this->assertTrue(\Hash::check('456', $users->password)); + } + + /** + * Make sure revisions are created + */ + public function testRevisionsStored() + { + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'password' => \Hash::make('456'), + ]); + + // change to my nickname + $user->update([ + 'name' => 'Judd' + ]); + + // change to my forename + $user->update([ + 'name' => 'James' + ]); + + // we should have two revisions to my name + $this->assertCount(2, $user->revisionHistory); + } +} From 07fde661e2767354d8e0c05c40bfd02b6ac3022c Mon Sep 17 00:00:00 2001 From: Robert Fridzema Date: Thu, 26 Apr 2018 10:53:20 +0200 Subject: [PATCH 24/74] Correct path --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0d7d3aaa..3e758f6e 100644 --- a/readme.md +++ b/readme.md @@ -53,7 +53,7 @@ php artisan migrate --package=venturecraft/revisionable > If you're going to be migrating up and down completely a lot (using `migrate:refresh`), one thing you can do instead is to copy the migration file from the package to your `app/database` folder, and change the classname from `CreateRevisionsTable` to something like `CreateRevisionTable` (without the 's', otherwise you'll get an error saying there's a duplicate class) -> `cp vendor/venturecraft/revisionable/src/migrations/2013_04_09_062329_create_revisions_table.php app/database/migrations/` +> `cp vendor/venturecraft/revisionable/src/migrations/2013_04_09_062329_create_revisions_table.php database/migrations/` ## Docs From 76dcb4c725230271b7224ce1f96a600e7e7855cd Mon Sep 17 00:00:00 2001 From: Jitendra Patel Date: Wed, 11 Jul 2018 16:27:08 +0530 Subject: [PATCH 25/74] Sorts the keys of a JSON object due Normalization performed by MySQL So it doesn't include if it is changed only order of key or whitespace after comma --- .../Revisionable/RevisionableTrait.php | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f298dcf7..f50f2146 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -122,7 +122,15 @@ public function preSave() // we can only safely compare basic items, // so for now we drop any object based items, like DateTime foreach ($this->updatedData as $key => $val) { - if (gettype($val) == 'object' && !method_exists($val, '__toString')) { + if (isset($this->casts[$key]) && in_array($this->casts[$key], ['object', 'array']) && isset($this->originalData[$key])) { + // Sorts the keys of a JSON object due Normalization performed by MySQL + // So it doesn't set false flag if it is changed only order of key or whitespace after comma + + $updatedData = $this->sortJsonKeys(json_decode($this->updatedData[$key], true)); + + $this->updatedData[$key] = json_encode($updatedData); + $this->originalData[$key] = json_encode(json_decode($this->originalData[$key], true)); + } else if (gettype($val) == 'object' && !method_exists($val, '__toString')) { unset($this->originalData[$key]); unset($this->updatedData[$key]); array_push($this->dontKeep, $key); @@ -435,4 +443,34 @@ public function disableRevisionField($field) unset($donts); } } + + /** + * Sorts the keys of a JSON object + * + * Normalization performed by MySQL and + * discards extra whitespace between keys, values, or elements + * in the original JSON document. + * To make lookups more efficient, it sorts the keys of a JSON object. + * + * @param mixed $attribute + * + * @return mixed + */ + private function sortJsonKeys($attribute) + { + if(empty($attribute)) return $attribute; + + foreach ($attribute as $key=>$value) { + if(is_array($value) || is_object($value)){ + $value = $this->sortJsonKeys($value); + } else { + continue; + } + + ksort($value); + $attribute[$key] = $value; + } + + return $attribute; + } } From 60c26dd6ad157b6973129ef3579748d32bff7fca Mon Sep 17 00:00:00 2001 From: Rick Mills Date: Mon, 23 Jul 2018 08:32:58 +0100 Subject: [PATCH 26/74] Mark package as abandoned As the number of MR's and Issues has stacked up and the original author appears to no longer wish to do anything to this (including answering people) this should really be marked as abandoned to save people wasting their time. This will flag it on packagist as abandoned, which will allow people to use alternatives. --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 52677d68..db6213d7 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "email": "me@chrisduell.com" } ], + "abandoned": true, "support": { "issues": "https://github.com/VentureCraft/revisionable/issues", "source": "https://github.com/VentureCraft/revisionable" From 3655420b4725415562e1d6d2637cbde12b0850ed Mon Sep 17 00:00:00 2001 From: seedgabo Date: Wed, 27 Mar 2019 11:34:45 -0500 Subject: [PATCH 27/74] adding compatibility with laravel 5.8 --- src/Venturecraft/Revisionable/RevisionableTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f298dcf7..75a9f66f 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -196,7 +196,7 @@ public function postSave() } $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)); } } } @@ -230,7 +230,7 @@ public function postCreate() $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)); } } @@ -256,7 +256,7 @@ public function postDelete() ); $revision = new \Venturecraft\Revisionable\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)); } } From 7c5393c92260eb9f1ae83dbf1a416eee44b674d8 Mon Sep 17 00:00:00 2001 From: seedgabo Date: Wed, 27 Mar 2019 11:42:33 -0500 Subject: [PATCH 28/74] Update RevisionableTrait.php --- src/Venturecraft/Revisionable/RevisionableTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f298dcf7..75a9f66f 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -196,7 +196,7 @@ public function postSave() } $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)); } } } @@ -230,7 +230,7 @@ public function postCreate() $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)); } } @@ -256,7 +256,7 @@ public function postDelete() ); $revision = new \Venturecraft\Revisionable\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)); } } From 519be8e20064ad2065bffe42f03510614637033e Mon Sep 17 00:00:00 2001 From: seedgabo Date: Wed, 27 Mar 2019 11:43:18 -0500 Subject: [PATCH 29/74] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2dfd15d4..71028a07 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "venturecraft/revisionable", + "name": "seedgabo/revisionable", "license": "MIT", "description": "Keep a revision history for your models without thinking, created as a package for use with Laravel", "keywords": ["model", "laravel", "ardent", "revision", "history"], From 941ea65bb4dc200205bdea5a24e2abb8a2587e68 Mon Sep 17 00:00:00 2001 From: seedgabo Date: Wed, 27 Mar 2019 11:52:28 -0500 Subject: [PATCH 30/74] Update RevisionableTrait.php --- src/Venturecraft/Revisionable/RevisionableTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 75a9f66f..05188a11 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -1,4 +1,4 @@ - Date: Tue, 23 Apr 2019 11:31:12 +0300 Subject: [PATCH 31/74] Require Laravel 5.4 or higher --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 71028a07..283b8dce 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ }, "require": { "php": ">=5.3.0", - "illuminate/support": "~4.0|~5.0|~5.1" + "illuminate/support": "~4.0|~5.0|~5.1", + "laravel/framework": "~5.4" }, "autoload": { "classmap": [ From 5437e0bd2692bfc25369bb92694c0fc434f89665 Mon Sep 17 00:00:00 2001 From: Cristian Tabacitu Date: Tue, 23 Apr 2019 11:33:09 +0300 Subject: [PATCH 32/74] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 283b8dce..a0d5afde 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "seedgabo/revisionable", + "name": "venturecraft/revisionable", "license": "MIT", "description": "Keep a revision history for your models without thinking, created as a package for use with Laravel", "keywords": ["model", "laravel", "ardent", "revision", "history"], From 3d58f72069f7d96aa339f31d88abfe1ff7d4c262 Mon Sep 17 00:00:00 2001 From: Cristian Tabacitu Date: Tue, 23 Apr 2019 11:34:23 +0300 Subject: [PATCH 33/74] Update RevisionableTrait.php --- src/Venturecraft/Revisionable/RevisionableTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 05188a11..75a9f66f 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -1,4 +1,4 @@ - Date: Tue, 23 Apr 2019 11:38:14 +0300 Subject: [PATCH 34/74] Update composer.json --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 1fa84f51..68ca95e6 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "venturecraft/revisionable", "license": "MIT", "description": "Keep a revision history for your models without thinking, created as a package for use with Laravel", - "keywords": ["model", "laravel", "ardent", "revision", "history"], + "keywords": ["model", "laravel", "ardent", "revision", "audit", "history"], "homepage": "http://github.com/venturecraft/revisionable", "authors": [ { @@ -16,7 +16,7 @@ "source": "https://github.com/VentureCraft/revisionable" }, "require": { - "php": ">=5.3.0", + "php": ">=5.4.0", "illuminate/support": "~4.0|~5.0|~5.1", "laravel/framework": "~5.4" }, @@ -36,4 +36,4 @@ "require-dev": { "orchestra/testbench": "~3.0" } -} \ No newline at end of file +} From 51e431d5ad7ca525e373f1a0c78740c182dea1b3 Mon Sep 17 00:00:00 2001 From: Chris Duell Date: Fri, 14 Jun 2019 12:27:26 +1000 Subject: [PATCH 35/74] Update composer.json --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 68ca95e6..1b1a512f 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,6 @@ "email": "me@chrisduell.com" } ], - "abandoned": true, "support": { "issues": "https://github.com/VentureCraft/revisionable/issues", "source": "https://github.com/VentureCraft/revisionable" From 8e5b6de9d1b275618c57661ce529b8f243469138 Mon Sep 17 00:00:00 2001 From: Manash Sonowal Date: Mon, 8 Jul 2019 13:20:51 +0530 Subject: [PATCH 36/74] added auto discovery for 5.5 onwards added auto discovery for the service provider RevisionableServiceProvider that will amke hasslefree installations --- composer.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1b1a512f..510ff87d 100644 --- a/composer.json +++ b/composer.json @@ -34,5 +34,12 @@ }, "require-dev": { "orchestra/testbench": "~3.0" - } + }, + "extra": { + "laravel": { + "providers": [ + "Venturecraft\\Revisionable\\RevisionableServiceProvider" + ] + } + }, } From 8ebadae42cb11b9d8f1ca99058f2397739d9a63b Mon Sep 17 00:00:00 2001 From: Chris Duell Date: Tue, 6 Aug 2019 09:28:28 +1000 Subject: [PATCH 37/74] Composer update to allow for Laravel 6.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1b1a512f..87129220 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ }, "require": { "php": ">=5.4.0", - "illuminate/support": "~4.0|~5.0|~5.1", + "illuminate/support": "~4.0|~5.0|~5.1|^6.0", "laravel/framework": "~5.4" }, "autoload": { From 5f3e0c25f9d6118147b44cd2381212f343ae1c9f Mon Sep 17 00:00:00 2001 From: Ethan Ransdell Date: Tue, 3 Sep 2019 15:39:49 -0400 Subject: [PATCH 38/74] Add support for Laravel 6.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 87129220..facb25d5 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=5.4.0", "illuminate/support": "~4.0|~5.0|~5.1|^6.0", - "laravel/framework": "~5.4" + "laravel/framework": "~5.4|^6.0" }, "autoload": { "classmap": [ From a9f592bdf0e09f932d45561f39047a629150e07e Mon Sep 17 00:00:00 2001 From: Andrew Judd Date: Mon, 9 Sep 2019 17:31:41 -0400 Subject: [PATCH 39/74] Removing the dependency on array_get Resolving issue with deprecated string function --- src/Venturecraft/Revisionable/Revision.php | 5 +++-- src/Venturecraft/Revisionable/Revisionable.php | 3 ++- src/Venturecraft/Revisionable/RevisionableTrait.php | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Venturecraft/Revisionable/Revision.php index 357baaf3..af09d865 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Venturecraft/Revisionable/Revision.php @@ -2,6 +2,7 @@ namespace Venturecraft\Revisionable; +use Illuminate\Support\Str; use Illuminate\Database\Eloquent\Model as Eloquent; use Illuminate\Support\Facades\Log; @@ -163,7 +164,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 +180,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)); } diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 82348834..12a1e06d 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -1,5 +1,6 @@ $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(), diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 12b97910..54bc5f76 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -1,5 +1,7 @@ $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(), From 4e288ba15f82a5614cf10bc18698cc8a8f374498 Mon Sep 17 00:00:00 2001 From: Andrew Judd Date: Mon, 9 Sep 2019 19:22:02 -0400 Subject: [PATCH 40/74] Adding in the call to camel case --- src/Venturecraft/Revisionable/Revision.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/Revision.php b/src/Venturecraft/Revisionable/Revision.php index af09d865..2fdc8389 100644 --- a/src/Venturecraft/Revisionable/Revision.php +++ b/src/Venturecraft/Revisionable/Revision.php @@ -139,7 +139,7 @@ private function getValue($which = 'new') // Now we can find out the namespace of of related model if (!method_exists($main_model, $related_model)) { - $related_model = camel_case($related_model); // for cases like published_status_id + $related_model = Str::camel($related_model); // for cases like published_status_id if (!method_exists($main_model, $related_model)) { throw new \Exception('Relation ' . $related_model . ' does not exist for ' . get_class($main_model)); } From e09256c3db5a7a081223c7dbeb6ed0ebb6489cfb Mon Sep 17 00:00:00 2001 From: Sergey Zhidkov Date: Wed, 23 Oct 2019 19:06:25 +0300 Subject: [PATCH 41/74] Added the output format for values - "options". Analogous to "boolean", only any text or numeric values can act as a source value (often flags are stored in the database). The format allows you to specify different outputs depending on the value. options: search.On the search|network.In networks Look at this as an associative array in which the key is separated from the value by a dot. Array elements are separated by a vertical line. --- .../Revisionable/FieldFormatter.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Venturecraft/Revisionable/FieldFormatter.php b/src/Venturecraft/Revisionable/FieldFormatter.php index b9572c34..3cf89d88 100644 --- a/src/Venturecraft/Revisionable/FieldFormatter.php +++ b/src/Venturecraft/Revisionable/FieldFormatter.php @@ -117,4 +117,29 @@ public static function datetime($value, $format = 'Y-m-d H:i:s') return $datetime->format($format); } + + /** + * Format options + * + * @param string $value + * @param string $format + * @return string + */ + public static function options($value, $format) + { + $options = explode('|', $format); + + $result = []; + + foreach ($options as $option) { + $transform = explode('.', $option); + $result[$transform[0]] = $transform[1]; + } + + if (isset($result[$value])) { + return $result[$value]; + } + + return 'undefined'; + } } From 31034cf307808eae0bf776e9c13793495d7f19f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=96=D0=B8=D0=B4?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2?= <53189193+adiafora@users.noreply.github.com> Date: Wed, 23 Oct 2019 19:43:19 +0300 Subject: [PATCH 42/74] Update readme.md --- readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/readme.md b/readme.md index 4e4ba58b..6e1e9b63 100644 --- a/readme.md +++ b/readme.md @@ -253,6 +253,14 @@ Booleans by default will display as a 0 or a 1, which is pretty bland and won't boolean:No|Yes ``` +### Options +Analogous to "boolean", only any text or numeric values can act as a source value (often flags are stored in the database). The format allows you to specify different outputs depending on the value. +Look at this as an associative array in which the key is separated from the value by a dot. Array elements are separated by a vertical line. + +``` +options: search.On the search|network.In networks +``` + ### DateTime DateTime by default will display as Y-m-d H:i:s. Prefix the value with `datetime:` and then add your datetime format, e.g., From dacfcd53b21e76a306f45bbabe07f89c45638928 Mon Sep 17 00:00:00 2001 From: Chris Duell Date: Tue, 12 Nov 2019 19:40:18 +1100 Subject: [PATCH 43/74] Removing trailing comma --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6b722aa3..85f7f964 100644 --- a/composer.json +++ b/composer.json @@ -41,5 +41,5 @@ "Venturecraft\\Revisionable\\RevisionableServiceProvider" ] } - }, + } } From 151336f0cd51921a00a1ac249afdbcbedb3bc8b3 Mon Sep 17 00:00:00 2001 From: Cristian Tabacitu Date: Tue, 3 Mar 2020 09:26:35 +0200 Subject: [PATCH 44/74] added support for Laravel 7 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 85f7f964..e6c07b3c 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,8 @@ }, "require": { "php": ">=5.4.0", - "illuminate/support": "~4.0|~5.0|~5.1|^6.0", - "laravel/framework": "~5.4|^6.0" + "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0", + "laravel/framework": "~5.4|^6.0|^7.0" }, "autoload": { "classmap": [ From aa3fa944de56dcd66606ab35a95ac6e28a8f33c0 Mon Sep 17 00:00:00 2001 From: alan-smith-be <65946659+alan-smith-be@users.noreply.github.com> Date: Mon, 17 Aug 2020 17:05:52 +0200 Subject: [PATCH 45/74] Update Revisionable - Support Backpack Implementation for "backpack for laravel" to support backpack user authentication --- src/Venturecraft/Revisionable/Revisionable.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 12a1e06d..04e5986e 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -234,6 +234,8 @@ private function getSystemUserId() if (class_exists($class = '\Cartalyst\Sentry\Facades\Laravel\Sentry') || class_exists($class = '\Cartalyst\Sentinel\Laravel\Facades\Sentinel')) { return ($class::check()) ? $class::getUser()->id : null; + } elseif (function_exists('backpack_auth') && backpack_auth()->check()) { + return backpack_user()->id; } elseif (\Auth::check()) { return \Auth::user()->getAuthIdentifier(); } From 48485d5d21209812785b961d0db565269bfe9f6d Mon Sep 17 00:00:00 2001 From: Chris Duell Date: Tue, 8 Sep 2020 11:13:24 +1000 Subject: [PATCH 46/74] Update readme.md --- readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/readme.md b/readme.md index 6e1e9b63..5b69153d 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,5 @@ Revisionable for Laravel -[![Laravel 4.x](https://img.shields.io/badge/Laravel-4.x-yellow.svg?style=flat-square)](https://laravel.com/) -[![Laravel 5.2](https://img.shields.io/badge/Laravel-5.x-brightgreen.svg?style=flat-square)](https://laravel.com/) [![Latest Version](https://img.shields.io/github/release/venturecraft/revisionable.svg?style=flat-square)](https://packagist.org/packages/venturecraft/revisionable) [![Downloads](https://img.shields.io/packagist/dt/venturecraft/revisionable.svg?style=flat-square)](https://packagist.org/packages/venturecraft/revisionable) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) From 97c11169b57f7aa11ef7fb6ef0ad426d63edf43d Mon Sep 17 00:00:00 2001 From: Chris Duell Date: Tue, 8 Sep 2020 11:14:44 +1000 Subject: [PATCH 47/74] Update composer.json --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e6c07b3c..8ec2fa76 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,8 @@ }, "require": { "php": ">=5.4.0", - "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0", - "laravel/framework": "~5.4|^6.0|^7.0" + "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0|^8.0", + "laravel/framework": "~5.4|^6.0|^7.0|^8.0" }, "autoload": { "classmap": [ From 800d6f90e4343814f061be09535490c5061b1712 Mon Sep 17 00:00:00 2001 From: Joshua Ziering Date: Thu, 2 Jan 2020 16:29:04 -0700 Subject: [PATCH 48/74] Support additional field within revisions --- .gitignore | 2 + readme.md | 15 ++++++ .../Revisionable/RevisionableTrait.php | 49 ++++++++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8b7ef350..e2652f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /vendor composer.lock +/.idea +.phpunit.result.cache diff --git a/readme.md b/readme.md index 5b69153d..120c7328 100644 --- a/readme.md +++ b/readme.md @@ -187,6 +187,21 @@ protected $dontKeepRevisionOf = ['category_id']; > The `$keepRevisionOf` setting takes precedence over `$dontKeepRevisionOf` +### Storing additional fields in revisions + +In some cases, you'll want additional metadata from the models in each revision. An example of this might be if you +have to keep track of accounts as well as users. Simply create a migration to add the fields you'd like to your revision model, +add them to your config/revisionable.php separated by pipes like so: + +```php +'additional_fields_in_model' => "account_id|permissions_id|other_id", +``` + +If the column exists in the model, it will be included in the revision. + +Make sure that if you can't guarantee the column in every model, you make that column ```nullable()``` in your migrations. + + ### Events Every time a model revision is created an event is fired. You can listen for `revisionable.created`, diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 54bc5f76..f9ac0dcb 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -186,7 +186,7 @@ public function postSave() $revisions = array(); foreach ($changes_to_record as $key => $change) { - $revisions[] = array( + $original = array( 'revisionable_type' => $this->getMorphClass(), 'revisionable_id' => $this->getKey(), 'key' => $key, @@ -196,6 +196,8 @@ public function postSave() 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); + + $revisions[] = array_merge($original, $this->getAdditionalFields()); } if (count($revisions) > 0) { @@ -239,6 +241,10 @@ public function postCreate() 'updated_at' => new \DateTime(), ); + //Determine if there are any additional fields we'd like to add to our model contained in the config file, and + //get them into an array. + $revisions = array_merge($revisions[0], $this->getAdditionalFields()); + $revision = Revisionable::newModel(); \DB::table($revision->getTable())->insert($revisions); \Event::dispatch('revisionable.created', array('model' => $this, 'revisions' => $revisions)); @@ -265,6 +271,10 @@ public function postDelete() 'created_at' => new \DateTime(), 'updated_at' => new \DateTime(), ); + + //Since there is only one revision because it's deleted, let's just merge into revision[0] + $revisions = array_merge($revisions[0], $this->getAdditionalFields()); + $revision = Revisionable::newModel(); \DB::table($revision->getTable())->insert($revisions); \Event::dispatch('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); @@ -293,6 +303,43 @@ public function getSystemUserId() return null; } + + public function getAdditionalFields() + { + $additional = []; + //Determine if there are any additional fields we'd like to add to our model contained in the config file, and + //get them into an array. + $fields = $this->getAdditionalFieldNames(); + foreach($fields as $field) + { + if(Arr::has($this->originalData, $field)) + { + $additional[$field] = Arr::get($this->originalData, $field); + } + } + + return $additional; + } + + + /** + * Get any fields that should be included in the revision model that have been selected in + * config/revisionable.php + * + * @return array fields + */ + public function getAdditionalFieldNames() + { + if(config('revisionable.additional_fields_in_model', null) != null) { + + $fields = config('revisionable.additional_fields_in_model', null); + $fields = explode('|', $fields); + return $fields; + } + + return []; + } + /** * Get all of the changes that have been made, that are also supposed * to have their changes recorded From 2112f253d6708ff03583aa7760c58347d36d1c79 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Thu, 2 Jan 2020 18:16:34 -0500 Subject: [PATCH 49/74] Add tests --- .../Revisionable/RevisionableTrait.php | 27 +----- src/config/revisionable.php | 3 + tests/RevisionTest.php | 89 +++++++++++++++++++ ...2329_add_additional_field_to_revisions.php | 29 ++++++ ...2_062330_add_additional_field_to_users.php | 29 ++++++ 5 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 tests/migrations/2020_01_02_062329_add_additional_field_to_revisions.php create mode 100644 tests/migrations/2020_01_02_062330_add_additional_field_to_users.php diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f9ac0dcb..4f6fe2b6 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -309,11 +309,9 @@ public function getAdditionalFields() $additional = []; //Determine if there are any additional fields we'd like to add to our model contained in the config file, and //get them into an array. - $fields = $this->getAdditionalFieldNames(); - foreach($fields as $field) - { - if(Arr::has($this->originalData, $field)) - { + $fields = config('revisionable.additional_fields', []); + foreach($fields as $field) { + if(Arr::has($this->originalData, $field)) { $additional[$field] = Arr::get($this->originalData, $field); } } @@ -321,25 +319,6 @@ public function getAdditionalFields() return $additional; } - - /** - * Get any fields that should be included in the revision model that have been selected in - * config/revisionable.php - * - * @return array fields - */ - public function getAdditionalFieldNames() - { - if(config('revisionable.additional_fields_in_model', null) != null) { - - $fields = config('revisionable.additional_fields_in_model', null); - $fields = explode('|', $fields); - return $fields; - } - - return []; - } - /** * Get all of the changes that have been made, that are also supposed * to have their changes recorded diff --git a/src/config/revisionable.php b/src/config/revisionable.php index 4f6666b8..32e590ce 100644 --- a/src/config/revisionable.php +++ b/src/config/revisionable.php @@ -7,4 +7,7 @@ |-------------------------------------------------------------------------- */ 'model' => Venturecraft\Revisionable\Revision::class, + + 'additional_fields' => [], + ]; diff --git a/tests/RevisionTest.php b/tests/RevisionTest.php index 79161536..7c29b8b5 100644 --- a/tests/RevisionTest.php +++ b/tests/RevisionTest.php @@ -79,4 +79,93 @@ public function testRevisionsStored() // we should have two revisions to my name $this->assertCount(2, $user->revisionHistory); } + + /** + * Make sure additional fields are saved with revision + */ + public function testRevisionStoredAdditionalFields() + { + $this->loadMigrationsFrom([ + '--database' => 'testing', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $this->app['config']->set('revisionable.additional_fields', ['additional_field']); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'additional_field' => 678, + 'password' => \Hash::make('456'), + ]); + + + // change to my nickname + $user->update([ + 'name' => 'Judd' + ]); + + // we should have two revisions to my name + $this->assertCount(1, $user->revisionHistory); + + $this->assertEquals(678, $user->revisionHistory->first()->additional_field); + } + + /** + * Make sure additional fields without values don't break + */ + public function testRevisionSkipsAdditionalFieldsWhenNotAvailable() + { + $this->loadMigrationsFrom([ + '--database' => 'testing', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $this->app['config']->set('revisionable.additional_fields', ['additional_field']); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'password' => \Hash::make('456'), + ]); + + + // change to my nickname + $user->update([ + 'name' => 'Judd' + ]); + + // we should have two revisions to my name + $this->assertCount(1, $user->revisionHistory); + + $this->assertNull($user->revisionHistory->first()->additional_field); + } + + /** + * Make sure additional fields which don't exist on the model still save revision + */ + public function testRevisionSkipsAdditionalFieldsWhenMisconfigured() + { + $this->loadMigrationsFrom([ + '--database' => 'testing', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $this->app['config']->set('revisionable.additional_fields', ['unknown_field']); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'password' => \Hash::make('456'), + ]); + + + // change to my nickname + $user->update([ + 'name' => 'Judd' + ]); + + // we should have two revisions to my name + $this->assertCount(1, $user->revisionHistory); + } } diff --git a/tests/migrations/2020_01_02_062329_add_additional_field_to_revisions.php b/tests/migrations/2020_01_02_062329_add_additional_field_to_revisions.php new file mode 100644 index 00000000..389ed10e --- /dev/null +++ b/tests/migrations/2020_01_02_062329_add_additional_field_to_revisions.php @@ -0,0 +1,29 @@ +string('additional_field')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/tests/migrations/2020_01_02_062330_add_additional_field_to_users.php b/tests/migrations/2020_01_02_062330_add_additional_field_to_users.php new file mode 100644 index 00000000..bf695f42 --- /dev/null +++ b/tests/migrations/2020_01_02_062330_add_additional_field_to_users.php @@ -0,0 +1,29 @@ +string('additional_field')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} From d52b7bc216d6e334e11bec3d51411c061f865503 Mon Sep 17 00:00:00 2001 From: Chris Duell Date: Tue, 8 Sep 2020 15:47:07 +1000 Subject: [PATCH 50/74] Update readme.md --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 120c7328..46ba578d 100644 --- a/readme.md +++ b/readme.md @@ -190,11 +190,11 @@ protected $dontKeepRevisionOf = ['category_id']; ### Storing additional fields in revisions In some cases, you'll want additional metadata from the models in each revision. An example of this might be if you -have to keep track of accounts as well as users. Simply create a migration to add the fields you'd like to your revision model, -add them to your config/revisionable.php separated by pipes like so: +have to keep track of accounts as well as users. Simply create your own new migration to add the fields you'd like to your revision model, +add them to your config/revisionable.php in an array like so: ```php -'additional_fields_in_model' => "account_id|permissions_id|other_id", +'additional_fields' => ['account_id', 'permissions_id', 'other_id'], ``` If the column exists in the model, it will be included in the revision. From 23cfb2d52c4715f1041e9d30a12de78329668204 Mon Sep 17 00:00:00 2001 From: zachweix Date: Thu, 21 Nov 2019 09:18:11 -0500 Subject: [PATCH 51/74] use newer conventions use larger integers for ID's and in down function change to dropIfExists, to prevent a potential failure --- .../2013_04_09_062329_create_revisions_table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/migrations/2013_04_09_062329_create_revisions_table.php b/src/migrations/2013_04_09_062329_create_revisions_table.php index fdd5b1e8..8d3d2d2a 100644 --- a/src/migrations/2013_04_09_062329_create_revisions_table.php +++ b/src/migrations/2013_04_09_062329_create_revisions_table.php @@ -12,10 +12,10 @@ class CreateRevisionsTable extends Migration public function up() { Schema::create('revisions', function ($table) { - $table->increments('id'); + $table->bigIncrements('id'); $table->string('revisionable_type'); - $table->integer('revisionable_id'); - $table->integer('user_id')->nullable(); + $table->unsignedBigInteger('revisionable_id'); + $table->unsignedBigInteger('user_id')->nullable(); $table->string('key'); $table->text('old_value')->nullable(); $table->text('new_value')->nullable(); @@ -32,6 +32,6 @@ public function up() */ public function down() { - Schema::drop('revisions'); + Schema::dropIfExists('revisions'); } } From b3c462e340fa45b05540a2004a0ba853dfa1e8cc Mon Sep 17 00:00:00 2001 From: Sergey Zhidkov Date: Fri, 25 Oct 2019 17:50:33 +0300 Subject: [PATCH 52/74] Added storing Force Delete --- readme.md | 8 +++++ .../Revisionable/Revisionable.php | 32 +++++++++++++++++++ .../Revisionable/RevisionableTrait.php | 32 +++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/readme.md b/readme.md index 46ba578d..3ac95380 100644 --- a/readme.md +++ b/readme.md @@ -162,6 +162,14 @@ To better format the output for `deleted_at` entries, you can use the `isEmpty` +### Storing Force Delete +By default the Force Delete of a model is not stored as a revision. + +If you want to store the Force Delete as a revision you can override this behavior by setting `revisionForceDeleteEnabled ` to `true` by adding the following to your model: +```php +protected $revisionForceDeleteEnabled = true; +``` + ### Storing Creations By default the creation of a new model is not stored as a revision. Only subsequent changes to a model is stored. diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 04e5986e..21b60328 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -72,6 +72,7 @@ public static function boot() static::deleted(function ($model) { $model->preSave(); $model->postDelete(); + $model->postForceDelete(); }); } /** @@ -224,6 +225,37 @@ public function postDelete() } } + /** + * If forcedeletes are enabled, set the value created_at of model to null + * + * @return void|bool + */ + public function postForceDelete() + { + if (empty($this->revisionForceDeleteEnabled)) { + return false; + } + + if ((!isset($this->revisionEnabled) || $this->revisionEnabled) + && (($this->isSoftDelete() && $this->isForceDeleting()) || !$this->isSoftDelete())) { + + $revisions[] = array( + 'revisionable_type' => $this->getMorphClass(), + 'revisionable_id' => $this->getKey(), + 'key' => self::CREATED_AT, + 'old_value' => $this->{self::CREATED_AT}, + 'new_value' => null, + 'user_id' => $this->getSystemUserId(), + 'created_at' => new \DateTime(), + 'updated_at' => new \DateTime(), + ); + + $revision = Revisionable::newModel(); + \DB::table($revision->getTable())->insert($revisions); + \Event::dispatch('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); + } + } + /** * Attempt to find the user id of the currently logged in user * Supports Cartalyst Sentry/Sentinel based authentication, as well as stock Auth diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 4f6fe2b6..b4163d32 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -84,6 +84,7 @@ public static function bootRevisionableTrait() static::deleted(function ($model) { $model->preSave(); $model->postDelete(); + $model->postForceDelete(); }); } @@ -281,6 +282,37 @@ public function postDelete() } } + /** + * If forcedeletes are enabled, set the value created_at of model to null + * + * @return void|bool + */ + public function postForceDelete() + { + if (empty($this->revisionForceDeleteEnabled)) { + return false; + } + + if ((!isset($this->revisionEnabled) || $this->revisionEnabled) + && (($this->isSoftDelete() && $this->isForceDeleting()) || !$this->isSoftDelete())) { + + $revisions[] = array( + 'revisionable_type' => $this->getMorphClass(), + 'revisionable_id' => $this->getKey(), + 'key' => self::CREATED_AT, + 'old_value' => $this->{self::CREATED_AT}, + 'new_value' => null, + 'user_id' => $this->getSystemUserId(), + 'created_at' => new \DateTime(), + 'updated_at' => new \DateTime(), + ); + + $revision = Revisionable::newModel(); + \DB::table($revision->getTable())->insert($revisions); + \Event::dispatch('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); + } + } + /** * Attempt to find the user id of the currently logged in user * Supports Cartalyst Sentry/Sentinel based authentication, as well as stock Auth From 2f29a96e75e97f35833c6ce327d95bc2bf76f5af Mon Sep 17 00:00:00 2001 From: Sergey Zhidkov Date: Fri, 25 Oct 2019 17:59:22 +0300 Subject: [PATCH 53/74] Storing Force Delete in README --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 3ac95380..8063e4fb 100644 --- a/readme.md +++ b/readme.md @@ -170,6 +170,10 @@ If you want to store the Force Delete as a revision you can override this behavi protected $revisionForceDeleteEnabled = true; ``` +In which case, the `created_at` field will be stored as a key with the `oldValue()` value equal to the model creation date and the `newValue()` value equal to null. + +**Attention!** Turn on this setting carefully! Since the model saved in the revision, now does not exist, so you will not be able to get its object or its relations. + ### Storing Creations By default the creation of a new model is not stored as a revision. Only subsequent changes to a model is stored. From 2fb4324c66ae4c9504be8ab29ac615582fe36f8b Mon Sep 17 00:00:00 2001 From: Sergey Zhidkov Date: Fri, 25 Oct 2019 18:08:34 +0300 Subject: [PATCH 54/74] Storing Force Delete in README --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 8063e4fb..b1986928 100644 --- a/readme.md +++ b/readme.md @@ -170,7 +170,7 @@ If you want to store the Force Delete as a revision you can override this behavi protected $revisionForceDeleteEnabled = true; ``` -In which case, the `created_at` field will be stored as a key with the `oldValue()` value equal to the model creation date and the `newValue()` value equal to null. +In which case, the `created_at` field will be stored as a key with the `oldValue()` value equal to the model creation date and the `newValue()` value equal to `null`. **Attention!** Turn on this setting carefully! Since the model saved in the revision, now does not exist, so you will not be able to get its object or its relations. From 4fe0bcf6ab997b7336ea60e89bd34763bf62a40c Mon Sep 17 00:00:00 2001 From: Paul Stanisci Date: Mon, 31 Aug 2020 17:15:25 -0400 Subject: [PATCH 55/74] checks value is object before using json_decode() in preSave() --- src/Venturecraft/Revisionable/RevisionableTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index b4163d32..f1fcd1b9 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -126,7 +126,7 @@ public function preSave() // we can only safely compare basic items, // so for now we drop any object based items, like DateTime foreach ($this->updatedData as $key => $val) { - if (isset($this->casts[$key]) && in_array($this->casts[$key], ['object', 'array']) && isset($this->originalData[$key])) { + if (gettype($val) == 'object' && isset($this->casts[$key]) && in_array($this->casts[$key], ['object', 'array']) && isset($this->originalData[$key])) { // Sorts the keys of a JSON object due Normalization performed by MySQL // So it doesn't set false flag if it is changed only order of key or whitespace after comma From 49e2db2373d0c9cd8ae3394b46c344dc422cde5f Mon Sep 17 00:00:00 2001 From: Paul Stanisci Date: Wed, 9 Sep 2020 15:22:41 -0400 Subject: [PATCH 56/74] Cast values are now correctly checked for either array or object --- src/Venturecraft/Revisionable/RevisionableTrait.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f1fcd1b9..4f0ff400 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -126,7 +126,8 @@ public function preSave() // we can only safely compare basic items, // so for now we drop any object based items, like DateTime foreach ($this->updatedData as $key => $val) { - if (gettype($val) == 'object' && isset($this->casts[$key]) && in_array($this->casts[$key], ['object', 'array']) && isset($this->originalData[$key])) { + $castCheck = ['object', 'array']; + if (isset($this->casts[$key]) && in_array(gettype($val), $castCheck) && in_array($this->casts[$key], $castCheck) && isset($this->originalData[$key])) { // Sorts the keys of a JSON object due Normalization performed by MySQL // So it doesn't set false flag if it is changed only order of key or whitespace after comma From 2c68e7a22ed10c4d27aad743c639d8c67b26d6cf Mon Sep 17 00:00:00 2001 From: Paul Stanisci Date: Wed, 9 Sep 2020 15:26:26 -0400 Subject: [PATCH 57/74] fixed indentation to match original format --- src/Venturecraft/Revisionable/RevisionableTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 4f0ff400..d57d180a 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -126,7 +126,7 @@ public function preSave() // we can only safely compare basic items, // so for now we drop any object based items, like DateTime foreach ($this->updatedData as $key => $val) { - $castCheck = ['object', 'array']; + $castCheck = ['object', 'array']; if (isset($this->casts[$key]) && in_array(gettype($val), $castCheck) && in_array($this->casts[$key], $castCheck) && isset($this->originalData[$key])) { // Sorts the keys of a JSON object due Normalization performed by MySQL // So it doesn't set false flag if it is changed only order of key or whitespace after comma From e8a89ce3c6d622a36e99fc87e9aaa70e7396260e Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Tue, 2 Mar 2021 18:34:38 -0500 Subject: [PATCH 58/74] Revert #112 and follow #113 --- src/Venturecraft/Revisionable/Revisionable.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Venturecraft/Revisionable/Revisionable.php b/src/Venturecraft/Revisionable/Revisionable.php index 21b60328..5134af12 100644 --- a/src/Venturecraft/Revisionable/Revisionable.php +++ b/src/Venturecraft/Revisionable/Revisionable.php @@ -81,7 +81,12 @@ public static function boot() */ public static function newModel() { - $model = \Config::get('revisionable.model', 'Venturecraft\Revisionable\Revision'); + $model = app('config')->get('revisionable.model'); + + if (! $model) { + $model = 'Venturecraft\Revisionable\Revision'; + } + return new $model; } From 7dd938dd3b4abe0ff1bbd7aaf8245f3d66dce205 Mon Sep 17 00:00:00 2001 From: Reinier Date: Mon, 29 Mar 2021 11:48:22 +0200 Subject: [PATCH 59/74] Update RevisionableTrait.php --- src/Venturecraft/Revisionable/RevisionableTrait.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index d57d180a..0def63f2 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -326,6 +326,8 @@ public function getSystemUserId() || class_exists($class = '\Cartalyst\Sentinel\Laravel\Facades\Sentinel') ) { return ($class::check()) ? $class::getUser()->id : null; + } elseif (function_exists('backpack_auth') && backpack_auth()->check()) { + return backpack_user()->id; } elseif (\Auth::check()) { return \Auth::user()->getAuthIdentifier(); } From 24ef304dfe7fe64362cc815faab0fc80030d0c59 Mon Sep 17 00:00:00 2001 From: Cristian Tabacitu Date: Wed, 19 Jan 2022 20:02:41 +0200 Subject: [PATCH 60/74] support Laravel 9 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8ec2fa76..4aff9eee 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,8 @@ }, "require": { "php": ">=5.4.0", - "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0|^8.0", - "laravel/framework": "~5.4|^6.0|^7.0|^8.0" + "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0|^8.0|^9.0", + "laravel/framework": "~5.4|^6.0|^7.0|^8.0|^9.0" }, "autoload": { "classmap": [ From e40e8240a78a6f48ff9de2bc7a917c34b30931e1 Mon Sep 17 00:00:00 2001 From: AHMAD BWIDANI Date: Sun, 6 Nov 2022 11:10:16 +0300 Subject: [PATCH 61/74] Update readme.md remove misleading space (not being trimmed in model) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b1986928..1bb43d9a 100644 --- a/readme.md +++ b/readme.md @@ -283,7 +283,7 @@ Analogous to "boolean", only any text or numeric values can act as a source valu Look at this as an associative array in which the key is separated from the value by a dot. Array elements are separated by a vertical line. ``` -options: search.On the search|network.In networks +options:search.On the search|network.In networks ``` ### DateTime From 0533f5fa967dbb656b098dac123046fff5ecc69f Mon Sep 17 00:00:00 2001 From: Shift Date: Thu, 2 Feb 2023 06:29:08 +0000 Subject: [PATCH 62/74] Bump dependencies for Laravel 10 --- composer.json | 97 +++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/composer.json b/composer.json index 4aff9eee..8e0ada31 100644 --- a/composer.json +++ b/composer.json @@ -1,45 +1,52 @@ -{ - "name": "venturecraft/revisionable", - "license": "MIT", - "description": "Keep a revision history for your models without thinking, created as a package for use with Laravel", - "keywords": ["model", "laravel", "ardent", "revision", "audit", "history"], - "homepage": "http://github.com/venturecraft/revisionable", - "authors": [ - { - "name": "Chris Duell", - "email": "me@chrisduell.com" - } - ], - "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|^6.0|^7.0|^8.0|^9.0", - "laravel/framework": "~5.4|^6.0|^7.0|^8.0|^9.0" - }, - "autoload": { - "classmap": [ - "src/migrations" - ], - "psr-0": { - "Venturecraft\\Revisionable": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Venturecraft\\Revisionable\\Tests\\": "tests/" - } - }, - "require-dev": { - "orchestra/testbench": "~3.0" - }, - "extra": { - "laravel": { - "providers": [ - "Venturecraft\\Revisionable\\RevisionableServiceProvider" - ] - } - } -} +{ + "name": "venturecraft/revisionable", + "license": "MIT", + "description": "Keep a revision history for your models without thinking, created as a package for use with Laravel", + "keywords": [ + "model", + "laravel", + "ardent", + "revision", + "audit", + "history" + ], + "homepage": "http://github.com/venturecraft/revisionable", + "authors": [ + { + "name": "Chris Duell", + "email": "me@chrisduell.com" + } + ], + "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|^6.0|^7.0|^8.0|^9.0|^10.0", + "laravel/framework": "~5.4|^6.0|^7.0|^8.0|^9.0|^10.0" + }, + "autoload": { + "classmap": [ + "src/migrations" + ], + "psr-0": { + "Venturecraft\\Revisionable": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Venturecraft\\Revisionable\\Tests\\": "tests/" + } + }, + "require-dev": { + "orchestra/testbench": "~3.0|^8.0" + }, + "extra": { + "laravel": { + "providers": [ + "Venturecraft\\Revisionable\\RevisionableServiceProvider" + ] + } + } +} From 40b122b7ad2eb42316e1e0a6674d96a6f8295f2d Mon Sep 17 00:00:00 2001 From: CedricFrossard <113982099+CedricFrossard@users.noreply.github.com> Date: Wed, 8 Mar 2023 15:31:23 +0100 Subject: [PATCH 63/74] Update readme.md doc: userResponsible returns false and not null. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1bb43d9a..0e85c25b 100644 --- a/readme.md +++ b/readme.md @@ -344,7 +344,7 @@ If you have enabled revisions of creations as well you can display it like this: ### userResponsible() -Returns the User that was responsible for making the revision. A user model is returned, or null if there was no user recorded. +Returns the User that was responsible for making the revision. A user model is returned, or false if there was no user recorded. The user model that is loaded depends on what you have set in your `config/auth.php` file for the `model` variable. From 42d37d544f95ea008078207bed9383c79cf7578d Mon Sep 17 00:00:00 2001 From: Shift Date: Tue, 27 Feb 2024 01:34:43 +0000 Subject: [PATCH 64/74] Bump dependencies for Laravel 11 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 8e0ada31..440a4a52 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ }, "require": { "php": ">=5.4.0", - "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0|^8.0|^9.0|^10.0", - "laravel/framework": "~5.4|^6.0|^7.0|^8.0|^9.0|^10.0" + "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "laravel/framework": "~5.4|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0" }, "autoload": { "classmap": [ @@ -40,7 +40,7 @@ } }, "require-dev": { - "orchestra/testbench": "~3.0|^8.0" + "orchestra/testbench": "~3.0|^8.0|^9.0" }, "extra": { "laravel": { From 01c3a4c38ae8989cc4f65ec00a0043866f1cb272 Mon Sep 17 00:00:00 2001 From: Shift Date: Sat, 15 Feb 2025 19:12:08 +0000 Subject: [PATCH 65/74] Bump dependencies for Laravel 12 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 440a4a52..023d4c7f 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ }, "require": { "php": ">=5.4.0", - "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "laravel/framework": "~5.4|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0" + "illuminate/support": "~4.0|~5.0|~5.1|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "laravel/framework": "~5.4|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0" }, "autoload": { "classmap": [ @@ -40,7 +40,7 @@ } }, "require-dev": { - "orchestra/testbench": "~3.0|^8.0|^9.0" + "orchestra/testbench": "~3.0|^8.0|^9.0|^10.0" }, "extra": { "laravel": { From 972a930c963d68dc11f06d418affbd3e49c59429 Mon Sep 17 00:00:00 2001 From: philipmclifton Date: Wed, 10 Feb 2016 20:13:50 +0000 Subject: [PATCH 66/74] All for passing custom attributes to revision table --- .../Revisionable/RevisionableTrait.php | 139 +++++++++--------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 0def63f2..588292ce 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -1,5 +1,8 @@ -revisionEnabled) || $this->revisionEnabled) { @@ -135,7 +138,7 @@ public function preSave() $this->updatedData[$key] = json_encode($updatedData); $this->originalData[$key] = json_encode(json_decode($this->originalData[$key], true)); - } else if (gettype($val) == 'object' && !method_exists($val, '__toString')) { + } elseif (gettype($val) == 'object' && !method_exists($val, '__toString')) { unset($this->originalData[$key]); unset($this->updatedData[$key]); array_push($this->dontKeep, $key); @@ -169,43 +172,43 @@ public function preSave() public function postSave() { if (isset($this->historyLimit) && $this->revisionHistory()->count() >= $this->historyLimit) { - $LimitReached = true; + $limitReached = true; } else { - $LimitReached = false; + $limitReached = false; } - if (isset($this->revisionCleanup)){ - $RevisionCleanup=$this->revisionCleanup; - }else{ - $RevisionCleanup=false; + if (isset($this->revisionCleanup)) { + $revisionCleanup = $this->revisionCleanup; + } else { + $revisionCleanup = false; } // check if the model already exists - if (((!isset($this->revisionEnabled) || $this->revisionEnabled) && $this->updating) && (!$LimitReached || $RevisionCleanup)) { + if (((!isset($this->revisionEnabled) || $this->revisionEnabled) && $this->updating) && (!$limitReached || $revisionCleanup)) { // if it does, it means we're updating - $changes_to_record = $this->changedRevisionableFields(); + $changesToRecord = $this->changedRevisionableFields(); $revisions = array(); - foreach ($changes_to_record as $key => $change) { - $original = array( - 'revisionable_type' => $this->getMorphClass(), - 'revisionable_id' => $this->getKey(), - 'key' => $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(), - ); + foreach ($changesToRecord as $key => $change) { + $original = [ + 'revisionable_type' => $this->getMorphClass(), + 'revisionable_id' => $this->getKey(), + 'key' => $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(), + ] + app(RevisionRepository::class)->getExtraAttributes(); $revisions[] = array_merge($original, $this->getAdditionalFields()); } if (count($revisions) > 0) { - if($LimitReached && $RevisionCleanup){ - $toDelete = $this->revisionHistory()->orderBy('id','asc')->limit(count($revisions))->get(); - foreach($toDelete as $delete){ + if ($limitReached && $revisionCleanup) { + $toDelete = $this->revisionHistory()->orderBy('id', 'asc')->limit(count($revisions))->get(); + foreach ($toDelete as $delete) { $delete->delete(); } } @@ -217,30 +220,27 @@ public function postSave() } /** - * Called after record successfully created - */ + * Called after record successfully created + */ public function postCreate() { - // Check if we should store creations in our revision history // Set this value to true in your model if you want to - if(empty($this->revisionCreationsEnabled)) - { + if (empty($this->revisionCreationsEnabled)) { // We should not store creations. return false; } - if ((!isset($this->revisionEnabled) || $this->revisionEnabled)) - { + if ((!isset($this->revisionEnabled) || $this->revisionEnabled)) { $revisions[] = array( 'revisionable_type' => $this->getMorphClass(), - 'revisionable_id' => $this->getKey(), - 'key' => self::CREATED_AT, - 'old_value' => null, - 'new_value' => $this->{self::CREATED_AT}, - 'user_id' => $this->getSystemUserId(), - 'created_at' => new \DateTime(), - 'updated_at' => new \DateTime(), + 'revisionable_id' => $this->getKey(), + 'key' => self::CREATED_AT, + 'old_value' => null, + 'new_value' => $this->{self::CREATED_AT}, + 'user_id' => $this->getSystemUserId(), + 'created_at' => new \DateTime(), + 'updated_at' => new \DateTime(), ); //Determine if there are any additional fields we'd like to add to our model contained in the config file, and @@ -265,13 +265,13 @@ public function postDelete() ) { $revisions[] = array( 'revisionable_type' => $this->getMorphClass(), - 'revisionable_id' => $this->getKey(), - 'key' => $this->getDeletedAtColumn(), - 'old_value' => null, - 'new_value' => $this->{$this->getDeletedAtColumn()}, - 'user_id' => $this->getSystemUserId(), - 'created_at' => new \DateTime(), - 'updated_at' => new \DateTime(), + 'revisionable_id' => $this->getKey(), + 'key' => $this->getDeletedAtColumn(), + 'old_value' => null, + 'new_value' => $this->{$this->getDeletedAtColumn()}, + 'user_id' => $this->getSystemUserId(), + 'created_at' => new \DateTime(), + 'updated_at' => new \DateTime(), ); //Since there is only one revision because it's deleted, let's just merge into revision[0] @@ -299,13 +299,13 @@ public function postForceDelete() $revisions[] = array( 'revisionable_type' => $this->getMorphClass(), - 'revisionable_id' => $this->getKey(), - 'key' => self::CREATED_AT, - 'old_value' => $this->{self::CREATED_AT}, - 'new_value' => null, - 'user_id' => $this->getSystemUserId(), - 'created_at' => new \DateTime(), - 'updated_at' => new \DateTime(), + 'revisionable_id' => $this->getKey(), + 'key' => self::CREATED_AT, + 'old_value' => $this->{self::CREATED_AT}, + 'new_value' => null, + 'user_id' => $this->getSystemUserId(), + 'created_at' => new \DateTime(), + 'updated_at' => new \DateTime(), ); $revision = Revisionable::newModel(); @@ -326,9 +326,11 @@ public function getSystemUserId() || class_exists($class = '\Cartalyst\Sentinel\Laravel\Facades\Sentinel') ) { return ($class::check()) ? $class::getUser()->id : null; - } elseif (function_exists('backpack_auth') && backpack_auth()->check()) { + } + if (function_exists('backpack_auth') && backpack_auth()->check()) { return backpack_user()->id; - } elseif (\Auth::check()) { + } + if (\Auth::check()) { return \Auth::user()->getAuthIdentifier(); } } catch (\Exception $e) { @@ -345,9 +347,9 @@ public function getAdditionalFields() //Determine if there are any additional fields we'd like to add to our model contained in the config file, and //get them into an array. $fields = config('revisionable.additional_fields', []); - foreach($fields as $field) { - if(Arr::has($this->originalData, $field)) { - $additional[$field] = Arr::get($this->originalData, $field); + foreach ($fields as $field) { + if (Arr::has($this->originalData, $field)) { + $additional[$field] = Arr::get($this->originalData, $field); } } @@ -362,13 +364,13 @@ public function getAdditionalFields() */ private function changedRevisionableFields() { - $changes_to_record = array(); + $changesToRecord = array(); foreach ($this->dirtyData as $key => $value) { // check that the field is revisionable, and double check // that it's actually new data in case dirty is, well, clean if ($this->isRevisionable($key) && !is_array($value)) { if (!array_key_exists($key, $this->originalData) || $this->originalData[$key] != $this->updatedData[$key]) { - $changes_to_record[$key] = $value; + $changesToRecord[$key] = $value; } } else { // we don't need these any more, and they could @@ -378,7 +380,7 @@ private function changedRevisionableFields() } } - return $changes_to_record; + return $changesToRecord; } /** @@ -390,7 +392,6 @@ private function changedRevisionableFields() */ private function isRevisionable($key) { - // If the field is explicitly revisionable, then return true. // If it's explicitly not revisionable, return false. // Otherwise, if neither condition is met, only return true if @@ -522,10 +523,12 @@ public function disableRevisionField($field) */ private function sortJsonKeys($attribute) { - if(empty($attribute)) return $attribute; + if (empty($attribute)) { + return $attribute; + } - foreach ($attribute as $key=>$value) { - if(is_array($value) || is_object($value)){ + foreach ($attribute as $key => $value) { + if (is_array($value) || is_object($value)) { $value = $this->sortJsonKeys($value); } else { continue; From 094827f27689a920161b31a0eb3e4ab7e18fd8fa Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Wed, 11 Jun 2025 11:56:05 +0100 Subject: [PATCH 67/74] DS-2114 - Support revisions in MDP This requires connection switching support, to store revisions in school databases. --- src/Venturecraft/Revisionable/RevisionableTrait.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 588292ce..99935e27 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -213,7 +213,7 @@ public function postSave() } } $revision = Revisionable::newModel(); - \DB::table($revision->getTable())->insert($revisions); + \DB::connection($revision->getConnectionName())->table($revision->getTable())->insert($revisions); \Event::dispatch('revisionable.saved', array('model' => $this, 'revisions' => $revisions)); } } @@ -248,7 +248,7 @@ public function postCreate() $revisions = array_merge($revisions[0], $this->getAdditionalFields()); $revision = Revisionable::newModel(); - \DB::table($revision->getTable())->insert($revisions); + \DB::connection($revision->getConnectionName())->table($revision->getTable())->insert($revisions); \Event::dispatch('revisionable.created', array('model' => $this, 'revisions' => $revisions)); } @@ -278,7 +278,7 @@ public function postDelete() $revisions = array_merge($revisions[0], $this->getAdditionalFields()); $revision = Revisionable::newModel(); - \DB::table($revision->getTable())->insert($revisions); + \DB::connection($revision->getConnectionName())->table($revision->getTable())->insert($revisions); \Event::dispatch('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); } } @@ -309,7 +309,7 @@ public function postForceDelete() ); $revision = Revisionable::newModel(); - \DB::table($revision->getTable())->insert($revisions); + \DB::connection($revision->getConnectionName())->table($revision->getTable())->insert($revisions); \Event::dispatch('revisionable.deleted', array('model' => $this, 'revisions' => $revisions)); } } From b6c559ca99be728be773d4bac15c28a26fbd4ae8 Mon Sep 17 00:00:00 2001 From: claire mcevoy Date: Thu, 18 Dec 2025 17:10:42 +0000 Subject: [PATCH 68/74] Handle changed date attributes which are not formatted as date strings --- phpunit.xml | 13 +- .../Revisionable/RevisionableTrait.php | 32 +++- tests/DateRevisionTest.php | 146 ++++++++++++++++++ tests/RevisionTest.php | 41 +---- tests/TestCase.php | 52 +++++++ ...5_12_18_062330_add_date_field_to_users.php | 31 ++++ 6 files changed, 263 insertions(+), 52 deletions(-) create mode 100644 tests/DateRevisionTest.php create mode 100644 tests/TestCase.php create mode 100644 tests/migrations/2025_12_18_062330_add_date_field_to_users.php diff --git a/phpunit.xml b/phpunit.xml index 96d96447..f7237bf4 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,14 +1,9 @@ @@ -17,13 +12,7 @@ - - - src/ - - - - \ No newline at end of file + diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 99935e27..7d902885 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -4,6 +4,7 @@ use App\Repositories\Revision\RevisionRepository; use Illuminate\Support\Arr; +use Carbon\Carbon; /* * This file is part of the Revisionable package by Venture Craft @@ -126,11 +127,23 @@ public function preSave() $this->originalData = $this->original; $this->updatedData = $this->attributes; - // we can only safely compare basic items, - // so for now we drop any object based items, like DateTime foreach ($this->updatedData as $key => $val) { + + // Handle changed attributes which are stored as date strings in the DB + if ( + $this->changedAttributeIsADate($key) && + $this->isStandardDateFormat($this->originalData[$key]) + ) { + $dateObject = $this->asDateTime($val); + $this->updatedData[$key] = $dateObject->toDateString(); + } + $castCheck = ['object', 'array']; - if (isset($this->casts[$key]) && in_array(gettype($val), $castCheck) && in_array($this->casts[$key], $castCheck) && isset($this->originalData[$key])) { + if (isset($this->casts[$key]) && + in_array(gettype($val), $castCheck) && + in_array($this->casts[$key], $castCheck) && + isset($this->originalData[$key]) + ) { // Sorts the keys of a JSON object due Normalization performed by MySQL // So it doesn't set false flag if it is changed only order of key or whitespace after comma @@ -540,4 +553,17 @@ private function sortJsonKeys($attribute) return $attribute; } + + private function changedAttributeIsADate(string $key): bool + { + if (!isset($this->originalData[$key])) { + return false; + } + + $value = $this->updatedData[$key]; + + return $value instanceof Carbon || + $this->isDateAttribute($key) || + $this->isDateCastableWithCustomFormat($key); + } } diff --git a/tests/DateRevisionTest.php b/tests/DateRevisionTest.php new file mode 100644 index 00000000..80472a24 --- /dev/null +++ b/tests/DateRevisionTest.php @@ -0,0 +1,146 @@ +loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'date' => '2025-12-18', + 'password' => \Hash::make('456'), + ]); + + // Change date + $user->update([ + 'date' => '2025-12-19' + ]); + + // we should have 1 revision to the date + $this->assertCount(1, $user->revisionHistory); + $this->assertEquals('2025-12-18', $user->revisionHistory->first()['old_value']); + } + + + #[Test] + public function revision_is_stored_when_date_attribute_is_carbon() + { + $this->loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'date' => '2025-12-18', + 'password' => \Hash::make('456'), + ]); + + // Change date + $user->update([ + 'date' => Carbon::parse('2025-12-19'), + ]); + + // we should have 1 revision to the date + $this->assertCount(1, $user->revisionHistory); + $this->assertEquals('2025-12-19', $user->revisionHistory->first()['new_value']); + } + + + #[Test] + public function revision_is_stored_when_date_attribute_is_date_time_string() + { + $this->loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'date' => '2025-12-18', + 'password' => \Hash::make('456'), + ]); + + // Set casts on date attribute + $user->mergeCasts([ + 'date' => 'datetime', + ]); + + // Change date + $user->update([ + 'date' => '2025-12-19 01:00:00', + ]); + + // we should have 1 revision to the date + $this->assertCount(1, $user->revisionHistory); + $this->assertEquals('2025-12-19', $user->revisionHistory->first()['new_value']); + } + + #[Test] + public function revision_is_not_stored_when_date_attribute_is_carbon_but_date_is_same() + { + $this->loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'date' => '2025-12-18', + 'password' => \Hash::make('456'), + ]); + + // Change date + $user->update([ + 'date' => Carbon::parse('2025-12-18'), + ]); + + // we should have no revisions to the date + $this->assertCount(0, $user->revisionHistory); + } + + + #[Test] + public function revision_is_not_stored_when_date_attribute_is_datetime_string_but_date_is_same() + { + $this->loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'date' => '2025-12-18', + 'password' => \Hash::make('456'), + ]); + + // Set casts on date attribute + $user->mergeCasts([ + 'date' => 'date', + ]); + + // Change date + $user->update([ + 'date' => '2025-12-18 23:59:59', + ]); + + // we should have no revisions to the date + $this->assertCount(0, $user->revisionHistory); + } +} diff --git a/tests/RevisionTest.php b/tests/RevisionTest.php index 7c29b8b5..41caf7a9 100644 --- a/tests/RevisionTest.php +++ b/tests/RevisionTest.php @@ -4,41 +4,8 @@ use Venturecraft\Revisionable\Tests\Models\User; -class RevisionTest extends \Orchestra\Testbench\TestCase +class RevisionTest extends TestCase { - /** - * Setup the test environment. - */ - protected function setUp() - { - parent::setUp(); - $this->loadLaravelMigrations(['--database' => 'testing']); - - // call migrations specific to our tests, e.g. to seed the db - // the path option should be an absolute path. - $this->loadMigrationsFrom([ - '--database' => 'testing', - '--path' => realpath(__DIR__.'/../src/migrations'), - ]); - } - - /** - * Define environment setup. - * - * @param \Illuminate\Foundation\Application $app - * @return void - */ - protected function getEnvironmentSetUp($app) - { - // Setup default database to use sqlite :memory: - $app['config']->set('database.default', 'testbench'); - $app['config']->set('database.connections.testbench', array( - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '', - )); - } - /** * Test we can interact with the database */ @@ -86,7 +53,7 @@ public function testRevisionsStored() public function testRevisionStoredAdditionalFields() { $this->loadMigrationsFrom([ - '--database' => 'testing', + '--database' => 'testbench', '--path' => realpath(__DIR__.'/migrations'), ]); @@ -117,7 +84,7 @@ public function testRevisionStoredAdditionalFields() public function testRevisionSkipsAdditionalFieldsWhenNotAvailable() { $this->loadMigrationsFrom([ - '--database' => 'testing', + '--database' => 'testbench', '--path' => realpath(__DIR__.'/migrations'), ]); @@ -147,7 +114,7 @@ public function testRevisionSkipsAdditionalFieldsWhenNotAvailable() public function testRevisionSkipsAdditionalFieldsWhenMisconfigured() { $this->loadMigrationsFrom([ - '--database' => 'testing', + '--database' => 'testbench', '--path' => realpath(__DIR__.'/migrations'), ]); diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 00000000..8a886b3c --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,52 @@ +loadLaravelMigrations(['--database' => 'testbench']); + + // call migrations specific to our tests, e.g. to seed the db + // the path option should be an absolute path. + $this->loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/../src/migrations'), + ]); + + // Bind mock RevisionRepository Class + $this->app->singleton(\App\Repositories\Revision\RevisionRepository::class, function () { + return new class { + public function getExtraAttributes(): array { + return []; + } + }; + }); + } + + /** + * Define environment setup. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function getEnvironmentSetUp($app) + { + // Setup default database to use sqlite :memory: + $app['config']->set('database.default', 'testbench'); + $app['config']->set('database.connections.testbench', array( + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + )); + } +} diff --git a/tests/migrations/2025_12_18_062330_add_date_field_to_users.php b/tests/migrations/2025_12_18_062330_add_date_field_to_users.php new file mode 100644 index 00000000..482ef9b4 --- /dev/null +++ b/tests/migrations/2025_12_18_062330_add_date_field_to_users.php @@ -0,0 +1,31 @@ +date('date')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('date'); + }); + } +} From 3ec0ddd2b2e1e6c936e37c24f128b88e421e2c70 Mon Sep 17 00:00:00 2001 From: claire mcevoy Date: Fri, 19 Dec 2025 15:28:21 +0000 Subject: [PATCH 69/74] Cast Carbon object to UTC time before converting to a string --- .../Revisionable/RevisionableTrait.php | 10 +++--- tests/DateRevisionTest.php | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 7d902885..edb75e16 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -123,19 +123,21 @@ public function preSave() { if (!isset($this->revisionEnabled) || $this->revisionEnabled) { // if there's no revisionEnabled. Or if there is, if it's true - $this->originalData = $this->original; $this->updatedData = $this->attributes; foreach ($this->updatedData as $key => $val) { - // Handle changed attributes which are stored as date strings in the DB if ( $this->changedAttributeIsADate($key) && $this->isStandardDateFormat($this->originalData[$key]) ) { - $dateObject = $this->asDateTime($val); - $this->updatedData[$key] = $dateObject->toDateString(); + $carbonObject = $this->asDateTime($val); + + // Use the app timezone configuration to standardize date comparison + $this->updatedData[$key] = $carbonObject + ->setTimezone('UTC') + ->toDateString(); } $castCheck = ['object', 'array']; diff --git a/tests/DateRevisionTest.php b/tests/DateRevisionTest.php index 80472a24..8c7c324b 100644 --- a/tests/DateRevisionTest.php +++ b/tests/DateRevisionTest.php @@ -3,6 +3,8 @@ namespace Venturecraft\Revisionable\Tests; use Carbon\Carbon; +use Config; +use Illuminate\Support\Facades\DB; use Venturecraft\Revisionable\Tests\Models\User; use PHPUnit\Framework\Attributes\Test; @@ -143,4 +145,35 @@ public function revision_is_not_stored_when_date_attribute_is_datetime_string_bu // we should have no revisions to the date $this->assertCount(0, $user->revisionHistory); } + + #[Test] + public function revision_is_not_stored_when_a_custom_cast_datetime_object_in_a_different_timezone_is_set() + { + $this->loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'date' => '2025-04-01', + 'password' => \Hash::make('456'), + ]); + + // Set custom casts + $user->mergeCasts([ + 'date' => 'date::Y-m-d', + ]); + + // Create a datetime string that represents the same date in a different timezone + $dateTimeInNonUtcTimezone = Carbon::parse('2025-03-31 20:00:00.0', 'America/New_York'); + + $user->update([ + 'date' => $dateTimeInNonUtcTimezone + ]); + + // we should have no revisions to the date + $this->assertCount(0, $user->revisionHistory); + } } From ad9cb046864eab5a116f67c73e4ef124c97d1ed6 Mon Sep 17 00:00:00 2001 From: claire mcevoy Date: Mon, 22 Dec 2025 14:36:02 +0000 Subject: [PATCH 70/74] Revert casting carbon date object to UTC before comparing --- src/Venturecraft/Revisionable/RevisionableTrait.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index edb75e16..4dd114d1 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -133,11 +133,8 @@ public function preSave() $this->isStandardDateFormat($this->originalData[$key]) ) { $carbonObject = $this->asDateTime($val); - - // Use the app timezone configuration to standardize date comparison - $this->updatedData[$key] = $carbonObject - ->setTimezone('UTC') - ->toDateString(); + + $this->updatedData[$key] = $carbonObject->toDateString(); } $castCheck = ['object', 'array']; From 21eb60f5124d9a422488e2992ea91e6d3e64502b Mon Sep 17 00:00:00 2001 From: claire mcevoy Date: Fri, 9 Jan 2026 16:53:30 +0000 Subject: [PATCH 71/74] Convert passed date to UTC before comparing changed data --- .../Revisionable/RevisionableTrait.php | 4 ++- tests/DateRevisionTest.php | 34 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index 4dd114d1..c9c88e65 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -134,7 +134,9 @@ public function preSave() ) { $carbonObject = $this->asDateTime($val); - $this->updatedData[$key] = $carbonObject->toDateString(); + $this->updatedData[$key] = $carbonObject + ->timezone('UTC') + ->toDateString(); } $castCheck = ['object', 'array']; diff --git a/tests/DateRevisionTest.php b/tests/DateRevisionTest.php index 8c7c324b..2dc51bad 100644 --- a/tests/DateRevisionTest.php +++ b/tests/DateRevisionTest.php @@ -166,7 +166,7 @@ public function revision_is_not_stored_when_a_custom_cast_datetime_object_in_a_d 'date' => 'date::Y-m-d', ]); - // Create a datetime string that represents the same date in a different timezone + // Create a datetime object that represents the same date in a different timezone $dateTimeInNonUtcTimezone = Carbon::parse('2025-03-31 20:00:00.0', 'America/New_York'); $user->update([ @@ -176,4 +176,36 @@ public function revision_is_not_stored_when_a_custom_cast_datetime_object_in_a_d // we should have no revisions to the date $this->assertCount(0, $user->revisionHistory); } + + + #[Test] + public function revision_is_not_stored_when_a_custom_cast_datetime_string_in_a_different_timezone_is_set() + { + $this->loadMigrationsFrom([ + '--database' => 'testbench', + '--path' => realpath(__DIR__.'/migrations'), + ]); + + $user = User::create([ + 'name' => 'James Judd', + 'email' => 'james.judd@revisionable.test', + 'date' => '2026-07-30', + 'password' => \Hash::make('456'), + ]); + + // Set custom casts + $user->mergeCasts([ + 'date' => 'date::Y-m-d', + ]); + + // Create a datetime string that represents the same date in a different timezone + $dateTimeInNonUtcTimezone = "2026-07-31T00:00:00+01:00"; + + $user->update([ + 'date' => $dateTimeInNonUtcTimezone + ]); + + // we should have no revisions to the date + $this->assertCount(0, $user->revisionHistory); + } } From 914b17904c1326a33d2bcc097fb6f748a8436275 Mon Sep 17 00:00:00 2001 From: claire mcevoy Date: Fri, 16 Jan 2026 09:32:28 +0000 Subject: [PATCH 72/74] Update formatting --- .../Revisionable/RevisionableTrait.php | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index c9c88e65..f9f7b082 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -129,22 +129,22 @@ public function preSave() foreach ($this->updatedData as $key => $val) { // Handle changed attributes which are stored as date strings in the DB if ( - $this->changedAttributeIsADate($key) && - $this->isStandardDateFormat($this->originalData[$key]) - ) { - $carbonObject = $this->asDateTime($val); - - $this->updatedData[$key] = $carbonObject - ->timezone('UTC') - ->toDateString(); + $this->changedAttributeIsADate($key) + && $this->isStandardDateFormat($this->originalData[$key]) + ) { + $carbonObject = $this->asDateTime($val); + + $this->updatedData[$key] = $carbonObject + ->timezone('UTC') + ->toDateString(); } $castCheck = ['object', 'array']; - if (isset($this->casts[$key]) && - in_array(gettype($val), $castCheck) && - in_array($this->casts[$key], $castCheck) && - isset($this->originalData[$key]) - ) { + if (isset($this->casts[$key]) + && in_array(gettype($val), $castCheck) + && in_array($this->casts[$key], $castCheck) + && isset($this->originalData[$key]) + ) { // Sorts the keys of a JSON object due Normalization performed by MySQL // So it doesn't set false flag if it is changed only order of key or whitespace after comma @@ -557,14 +557,14 @@ private function sortJsonKeys($attribute) private function changedAttributeIsADate(string $key): bool { - if (!isset($this->originalData[$key])) { + if (!isset($this->originalData[$key])) { return false; } $value = $this->updatedData[$key]; - return $value instanceof Carbon || - $this->isDateAttribute($key) || - $this->isDateCastableWithCustomFormat($key); + return $value instanceof Carbon + || $this->isDateAttribute($key) + || $this->isDateCastableWithCustomFormat($key); } } From c0f4da5a72dcdab93e26a060062705f3fb320616 Mon Sep 17 00:00:00 2001 From: claire mcevoy Date: Mon, 19 Jan 2026 10:07:33 +0000 Subject: [PATCH 73/74] Rehydrate models in Date tests for comparison --- tests/DateRevisionTest.php | 71 +++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/tests/DateRevisionTest.php b/tests/DateRevisionTest.php index 2dc51bad..63568f1b 100644 --- a/tests/DateRevisionTest.php +++ b/tests/DateRevisionTest.php @@ -3,8 +3,6 @@ namespace Venturecraft\Revisionable\Tests; use Carbon\Carbon; -use Config; -use Illuminate\Support\Facades\DB; use Venturecraft\Revisionable\Tests\Models\User; use PHPUnit\Framework\Attributes\Test; @@ -25,6 +23,8 @@ public function revision_is_stored_when_date_attribute_is_date_string() 'password' => \Hash::make('456'), ]); + $user->fresh(); + // Change date $user->update([ 'date' => '2025-12-19' @@ -36,7 +36,7 @@ public function revision_is_stored_when_date_attribute_is_date_string() } - #[Test] + #[Test] public function revision_is_stored_when_date_attribute_is_carbon() { $this->loadMigrationsFrom([ @@ -51,6 +51,8 @@ public function revision_is_stored_when_date_attribute_is_carbon() 'password' => \Hash::make('456'), ]); + $user->fresh(); + // Change date $user->update([ 'date' => Carbon::parse('2025-12-19'), @@ -70,17 +72,22 @@ public function revision_is_stored_when_date_attribute_is_date_time_string() '--path' => realpath(__DIR__.'/migrations'), ]); - $user = User::create([ + $user = new User(); + + $user->mergeCasts([ + 'date' => 'datetime', + ]); + + $user->fill([ 'name' => 'James Judd', 'email' => 'james.judd@revisionable.test', 'date' => '2025-12-18', 'password' => \Hash::make('456'), ]); - // Set casts on date attribute - $user->mergeCasts([ - 'date' => 'datetime', - ]); + $user->save(); + + $user->fresh(); // Change date $user->update([ @@ -89,7 +96,6 @@ public function revision_is_stored_when_date_attribute_is_date_time_string() // we should have 1 revision to the date $this->assertCount(1, $user->revisionHistory); - $this->assertEquals('2025-12-19', $user->revisionHistory->first()['new_value']); } #[Test] @@ -107,6 +113,8 @@ public function revision_is_not_stored_when_date_attribute_is_carbon_but_date_is 'password' => \Hash::make('456'), ]); + $user->fresh(); + // Change date $user->update([ 'date' => Carbon::parse('2025-12-18'), @@ -125,17 +133,22 @@ public function revision_is_not_stored_when_date_attribute_is_datetime_string_bu '--path' => realpath(__DIR__.'/migrations'), ]); - $user = User::create([ + $user = new User(); + + $user->mergeCasts([ + 'date' => 'date', + ]); + + $user->fill([ 'name' => 'James Judd', 'email' => 'james.judd@revisionable.test', 'date' => '2025-12-18', 'password' => \Hash::make('456'), ]); - // Set casts on date attribute - $user->mergeCasts([ - 'date' => 'date', - ]); + $user->save(); + + $user->fresh(); // Change date $user->update([ @@ -154,17 +167,22 @@ public function revision_is_not_stored_when_a_custom_cast_datetime_object_in_a_d '--path' => realpath(__DIR__.'/migrations'), ]); - $user = User::create([ + $user = new User(); + + $user->mergeCasts([ + 'date' => 'date:Y-m-d', + ]); + + $user->fill([ 'name' => 'James Judd', 'email' => 'james.judd@revisionable.test', 'date' => '2025-04-01', 'password' => \Hash::make('456'), ]); - // Set custom casts - $user->mergeCasts([ - 'date' => 'date::Y-m-d', - ]); + $user->save(); + + $user->fresh(); // Create a datetime object that represents the same date in a different timezone $dateTimeInNonUtcTimezone = Carbon::parse('2025-03-31 20:00:00.0', 'America/New_York'); @@ -186,17 +204,22 @@ public function revision_is_not_stored_when_a_custom_cast_datetime_string_in_a_d '--path' => realpath(__DIR__.'/migrations'), ]); - $user = User::create([ + $user = new User(); + + $user->mergeCasts([ + 'date' => 'date:Y-m-d', + ]); + + $user->fill([ 'name' => 'James Judd', 'email' => 'james.judd@revisionable.test', 'date' => '2026-07-30', 'password' => \Hash::make('456'), ]); - // Set custom casts - $user->mergeCasts([ - 'date' => 'date::Y-m-d', - ]); + $user->save(); + + $user->fresh(); // Create a datetime string that represents the same date in a different timezone $dateTimeInNonUtcTimezone = "2026-07-31T00:00:00+01:00"; From fe4f9c4ef3d7025f81fd46550aeb875c4c3910fa Mon Sep 17 00:00:00 2001 From: claire mcevoy Date: Mon, 19 Jan 2026 10:08:40 +0000 Subject: [PATCH 74/74] Ensure original date is string before passing to isStandardDateFormat method --- .../Revisionable/RevisionableTrait.php | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/Venturecraft/Revisionable/RevisionableTrait.php b/src/Venturecraft/Revisionable/RevisionableTrait.php index f9f7b082..bcb86193 100644 --- a/src/Venturecraft/Revisionable/RevisionableTrait.php +++ b/src/Venturecraft/Revisionable/RevisionableTrait.php @@ -127,16 +127,9 @@ public function preSave() $this->updatedData = $this->attributes; foreach ($this->updatedData as $key => $val) { - // Handle changed attributes which are stored as date strings in the DB - if ( - $this->changedAttributeIsADate($key) - && $this->isStandardDateFormat($this->originalData[$key]) - ) { - $carbonObject = $this->asDateTime($val); - - $this->updatedData[$key] = $carbonObject - ->timezone('UTC') - ->toDateString(); + // Handle changed date attributes + if ($this->changedAttributeIsADateType($key)) { + $this->normalizeDatesForRevisionCheck($key); } $castCheck = ['object', 'array']; @@ -555,7 +548,37 @@ private function sortJsonKeys($attribute) return $attribute; } - private function changedAttributeIsADate(string $key): bool + private function normalizeDatesForRevisionCheck(string $key) + { + $originalValue = $this->originalData[$key] ?? null; + $updatedValue = $this->updatedData[$key] ?? null; + + // Return if new or old values are null + if ($originalValue === null || $updatedValue === null) { + return; + } + + // If original data is a date string (Y-m-d), cast values to date string for comparison + if (is_string($originalValue) + && $this->isStandardDateFormat($originalValue) + || $this->castIsDate($key)) { + + try { + $carbon = $this->asDateTime($updatedValue); + + // Normalize values to a date only string + $this->updatedData[$key] = $carbon->timezone('UTC')->toDateString(); + $this->originalData[$key] = $this->asDateTime($originalValue)->toDateString(); + + } catch (\Throwable $e) { + // If parsing fails, fall back to default revision logic + } + + return; + } + } + + private function changedAttributeIsADateType(string $key): bool { if (!isset($this->originalData[$key])) { return false; @@ -567,4 +590,10 @@ private function changedAttributeIsADate(string $key): bool || $this->isDateAttribute($key) || $this->isDateCastableWithCustomFormat($key); } + + + private function castIsDate(string $key): bool + { + return isset($this->casts[$key]) && $this->casts[$key] === 'date'; + } }