diff --git a/app/Handlers/Contracts/AppHandler.php b/app/Handlers/Contracts/AppHandler.php new file mode 100644 index 00000000..968941bc --- /dev/null +++ b/app/Handlers/Contracts/AppHandler.php @@ -0,0 +1,30 @@ +column) { + case 'pageContent': + $route = route('history.diff', [ + 'revision' => $changeset->edit_batch, + 'id' => $changeset->id + ]); + + return "(" . trans('admin/history.diff') . ")"; + break; + case 'metaDescription': + return Differ::htmlDiff($changeset->old_value, $changeset->new_value); + break; + case 'status': + $statuses = ContentStatuses::getWithTranslations(); + + return trans('admin/history.changedFromTo', [ + 'from' => $statuses[$changeset->old_value], + 'to' => $statuses[$changeset->new_value] + ]); + break; + default: + return trans('admin/history.changedFromTo', [ + 'from' => $changeset->old_value, + 'to' => $changeset->new_value + ]); + break; + } + } +} \ No newline at end of file diff --git a/app/Helpers/Differ.php b/app/Helpers/Differ.php new file mode 100644 index 00000000..d6f6df9b --- /dev/null +++ b/app/Helpers/Differ.php @@ -0,0 +1,64 @@ + + May be used and distributed under the zlib/libpng license. + + This code is intended for learning purposes; it was written with short + code taking priority over performance. It could be used in a practical + application, but there are a few ways it could be optimized. + + Given two arrays, the function diff will return an array of the changes. + I won't describe the format of the array, but it will be obvious + if you use print_r() on the result of a diff on some test data. + + htmlDiff is a wrapper for the diff command, it takes two strings and + returns the differences in HTML. The tags used are and , + which can easily be styled with CSS. + + @url https://github.com/paulgb/simplediff/blob/master/php/simplediff.php + + Sorry for this kind of magic, I have no idea how this works.. :( +*/ + +class Differ +{ + public static function diff($old, $new) + { + $matrix = []; + $maxlen = 0; + foreach ($old as $oindex => $ovalue) { + $nkeys = array_keys($new, $ovalue); + foreach ($nkeys as $nindex) { + $matrix[$oindex][$nindex] = isset($matrix[$oindex - 1][$nindex - 1]) ? $matrix[$oindex - 1][$nindex - 1] + 1 : 1; + if ($matrix[$oindex][$nindex] > $maxlen) { + $maxlen = $matrix[$oindex][$nindex]; + $omax = $oindex + 1 - $maxlen; + $nmax = $nindex + 1 - $maxlen; + } + } + } + if ($maxlen == 0) { + return [['d' => $old, 'i' => $new]]; + } + + return array_merge(self::diff(array_slice($old, 0, $omax), array_slice($new, 0, $nmax)), array_slice($new, $nmax, $maxlen), self::diff(array_slice($old, $omax + $maxlen), array_slice($new, $nmax + $maxlen))); + } + + public static function htmlDiff($old, $new) + { + $ret = ''; + $diff = self::diff(preg_split("/[\s]+/", $old), preg_split("/[\s]+/", $new)); + foreach ($diff as $k) { + if (is_array($k)) { + $ret .= (!empty($k['d']) ? "" . implode(' ', $k['d']) . " " : '') . (!empty($k['i']) ? "" . implode(' ', $k['i']) . " " : ''); + } else { + $ret .= $k . ' '; + } + } + + return $ret; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/History.php b/app/Http/Controllers/Admin/History.php new file mode 100644 index 00000000..f7ad6d86 --- /dev/null +++ b/app/Http/Controllers/Admin/History.php @@ -0,0 +1,49 @@ +history = $history; + } + + /** + * @param $id + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function getOverview($id) + { + // test if given content id is static page and if exists + $content = Content::findOrFail($id); + + $history = $this->history->getStructuredHistoryOf($content); + + return view('admin/history.overview', [ + 'history' => $history, + 'page' => $content + ]); + } + + public function getDiff($revision, $id) + { + $change = $this->history->findOrFail($id); + $diff = $this->history->getDiff($change); + + return view('admin/history.diff', [ + 'change' => $change, + 'columnName' => 'Page content', + 'diff' => $diff + ]); + } +} \ No newline at end of file diff --git a/app/Http/routes.php b/app/Http/routes.php index 763d8c64..b783ff42 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -81,6 +81,14 @@ Route::group([ Route::get('users/overview/{state?}/{perPage?}', 'UsersOverview@getOverview') ->name('users.overview'); + // history of changes for specified page + Route::get('history/{id}', 'History@getOverview') + ->name('content.history'); + + // history of changes for specified page + Route::get('history/{revision}/{id}', 'History@getDiff') + ->name('history.diff'); + // custom fields // ------------------------------ diff --git a/app/Models/ContentHistory.php b/app/Models/ContentHistory.php index 9c6e7ada..b5ea444c 100644 --- a/app/Models/ContentHistory.php +++ b/app/Models/ContentHistory.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Handlers\Contracts\AppHandler; +use App\Handlers\Contracts\HistoryHandler; +use App\Handlers\Handler; +use App\Helpers\Differ; use Illuminate\Database\Eloquent\Model; /** @@ -10,10 +14,8 @@ use Illuminate\Database\Eloquent\Model; class ContentHistory extends Model { protected $table = 'content_history'; - // this table does not have created_at and updated_at fields public $timestamps = false; - protected $fillable = [ 'user_id', 'content_id', @@ -22,8 +24,81 @@ class ContentHistory extends Model 'new_value', 'old_value' ]; - protected $guarded = []; - + /** + * Returns history grouped by revisions (edit_batches) + * + * @param Content $content + * + * @return array + */ + public function getStructuredHistoryOf(Content $content) + { + $data = $content->history() + ->select('content_history.*', 'contents.module_handler', 'users.email AS user_email', 'users.name AS user_name') + ->join('users', 'users.id', '=', 'content_history.user_id') + ->join('contents', 'contents.id', '=', 'content_history.content_id') + ->orderBy('changed_at', 'DESC') + ->get(); + + $info = []; + $history = []; + foreach ($data as $change) { + $info[$change->edit_batch] = [ + 'changed_at' => $change->changed_at, + 'user' => [ + 'id' => $change->user_id, + 'name' => $change->user_name, + 'email' => $change->user_email + ] + ]; + + $change = $this->handleChangeset($change); + + $history[$change->edit_batch][] = $change; + } + + return [ + 'changesets' => $history, + 'info' => $info + ]; + } + + /** + * @param $change + * + * @return HistoryHandler + */ + protected function getHistoryHandler($change) + { + /** + * @var $handler AppHandler + * @var $historyHandler HistoryHandler + */ + + // get handler of given content page + if ($change->column == 'module_settings') { + $handler = $handler = $change->module_handler; + } else { + $handler = Handler::class; + } + + return $handler::getHistoryHandler(); + } + + public function handleChangeset($change) + { + $historyHandler = $this->getHistoryHandler($change); + + $change->column_trans = $historyHandler::parseColumnName($change->column); + $change->diff = $historyHandler::parseChangedValue($change); + + return $change; + } + + public function getDiff($change) + { + return Differ::htmlDiff($change->old_value, $change->new_value); + } } \ No newline at end of file diff --git a/resources/assets/css/admin.css b/resources/assets/css/admin.css index 8583030c..483d5e27 100644 --- a/resources/assets/css/admin.css +++ b/resources/assets/css/admin.css @@ -1,4 +1,13 @@ +del { + text-decoration: line-through; + color: red; +} +ins { + text-decoration: none; + color: green; +} + .columns:last-child:not(:first-child), .column:last-child:not(:first-child) { float: left; } diff --git a/resources/lang/en/admin/history.php b/resources/lang/en/admin/history.php new file mode 100644 index 00000000..892f35e7 --- /dev/null +++ b/resources/lang/en/admin/history.php @@ -0,0 +1,16 @@ + 'History of :name', + 'columnPageContent' => 'Page content', + 'columnUrl' => 'URL', + 'columnTitle' => 'Title', + 'columnMetaKeywords' => 'Meta keywords', + 'columnMetaDescription' => 'Description', + 'columnStatus' => 'Page visibility', + 'changedFromTo' => 'Changed from :from to :to', + 'numOfChanges' => 'One change|:count changes', + 'diff' => 'Difference', + 'diffPageName' => 'Difference of :column', + 'backToOverview' => 'Back to history overview' + +]; \ No newline at end of file diff --git a/resources/views/admin/history/diff.blade.php b/resources/views/admin/history/diff.blade.php new file mode 100644 index 00000000..ebc89eb6 --- /dev/null +++ b/resources/views/admin/history/diff.blade.php @@ -0,0 +1,21 @@ +@extends('admin.layout') + +@section('pageName', @trans('admin/history.diffPageName', ['column' => $columnName])) + +@section('content') + +
+

@lang('admin/history.diffPageName', ['column' => $columnName])

+ +
+ +
+ {!! $diff !!} +
+ + +@stop \ No newline at end of file diff --git a/resources/views/admin/history/overview.blade.php b/resources/views/admin/history/overview.blade.php new file mode 100644 index 00000000..3ca98360 --- /dev/null +++ b/resources/views/admin/history/overview.blade.php @@ -0,0 +1,31 @@ +@extends('admin.layout') + +@section('pageName', @trans('admin/history.pageName', ['name' => $page->title])) + +@section('content') + +
+

@lang('admin/history.pageName', ['name' => $page->title])

+ +
+ +
+ +
+@stop \ No newline at end of file