FEATURE #1373: add history of content

This commit contains only core funcionality of displaying history. Module settings must be implemented in each module.
This commit is contained in:
Michal Kváček
2016-02-05 16:06:13 +01:00
parent 0216d923d4
commit 620a5545d8
12 changed files with 370 additions and 18 deletions
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Handlers\Contracts;
use App\Models\Content;
use App\Models\User;
interface AppHandler
{
/**
* Render given module into visitor area.
*
* @param Content $content DB model from table contents
* @param array $parameters parameters used for modifications/custom settings of given model
*
* @return mixed
*/
public function render(Content $content, $parameters);
public static function getHistoryHandler();
/**
* Register admin menu (or multiple ones)
*
* This cannot be in ServiceProvider because Session is started at middleware. And ServiceProvider is called before
* starting any middleware..
*
* @param User $user model of logged user
*/
public static function registerAdminMenu(User $user);
}
@@ -0,0 +1,9 @@
<?php
namespace App\Handlers\Contracts;
interface HistoryHandler
{
public static function parseColumnName($name);
public static function parseChangedValue($changeset);
}
+9 -14
View File
@@ -1,28 +1,23 @@
<?php
namespace App\Handlers;
use App\Models\Content;
use App\Handlers\Contracts\AppHandler;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
/**
* Class Handler
*
* Handler is a class which handles module-specific behaviour which cannot be done by module itself, resp. cannot be done by some abstract magic.
* Handler is a class which handles module-specific behaviour which cannot be done by module itself, resp. cannot be
* done by some abstract magic.
*
* @package App\Handlers
*/
abstract class Handler
abstract class Handler implements AppHandler
{
/**
* Render given module into visitor area.
*
* @param Content $content DB model from table contents
* @param array $parameters parameters used for modifications/custom settings of given model
*
* @return mixed
*/
abstract public function render(Content $content, $parameters);
public static function getHistoryHandler()
{
return History::class;
}
/**
* Register admin menu (or multiple ones)
@@ -34,6 +29,6 @@ abstract class Handler
*/
public static function registerAdminMenu(User $user)
{
//
// core menu is registered in AdminMenuServiceProvider
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Handlers;
use App\Enumerations\ContentStatuses;
use App\Handlers\Contracts\HistoryHandler;
use App\Helpers\Differ;
class History implements HistoryHandler
{
public static function parseColumnName($column)
{
return trans('admin/history.column' . ucfirst($column));
}
public static function parseChangedValue($changeset)
{
switch ($changeset->column) {
case 'pageContent':
$route = route('history.diff', [
'revision' => $changeset->edit_batch,
'id' => $changeset->id
]);
return "(<a href='" . $route . "'>" . trans('admin/history.diff') . "</a>)";
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;
}
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace App\Helpers;
/*
Paul's Simple Differ Algorithm v 0.1
(C) Paul Butler 2007 <http://www.paulbutler.org/>
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 <ins> and <del>,
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']) ? "<del>" . implode(' ', $k['d']) . "</del> " : '') . (!empty($k['i']) ? "<ins>" . implode(' ', $k['i']) . "</ins> " : '');
} else {
$ret .= $k . ' ';
}
}
return $ret;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Content;
use App\Models\ContentHistory;
class History extends Controller
{
/**
* @var ContentHistory
*/
private $history;
public function __construct(ContentHistory $history)
{
parent::__construct();
$this->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
]);
}
}
+8
View File
@@ -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
// ------------------------------
+79 -4
View File
@@ -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);
}
}
+9
View File
@@ -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;
}
+16
View File
@@ -0,0 +1,16 @@
<?php
return [
'pageName' => 'History of :name',
'columnPageContent' => 'Page content',
'columnUrl' => 'URL',
'columnTitle' => 'Title',
'columnMetaKeywords' => 'Meta keywords',
'columnMetaDescription' => 'Description',
'columnStatus' => 'Page visibility',
'changedFromTo' => 'Changed from <del>:from</del> to <ins>:to</ins>',
'numOfChanges' => 'One change|:count changes',
'diff' => 'Difference',
'diffPageName' => 'Difference of :column',
'backToOverview' => 'Back to history overview'
];
@@ -0,0 +1,21 @@
@extends('admin.layout')
@section('pageName', @trans('admin/history.diffPageName', ['column' => $columnName]))
@section('content')
<div class="row">
<h2>@lang('admin/history.diffPageName', ['column' => $columnName])</h2>
</div>
<div class="row callout">
{!! $diff !!}
</div>
<div class="row">
<a href="{{route('content.history', ['id' => $change->content_id])}}" class="button success">
@lang('admin/history.backToOverview')
</a>
</div>
@stop
@@ -0,0 +1,31 @@
@extends('admin.layout')
@section('pageName', @trans('admin/history.pageName', ['name' => $page->title]))
@section('content')
<div class="row">
<h2>@lang('admin/history.pageName', ['name' => $page->title])</h2>
</div>
<div class="row">
<ul class="accordion" data-accordion>
@foreach($history['changesets'] as $revision => $changes)
<li class="accordion-item" data-accordion-item>
<a class="accordion-title">
<strong>{{$history['info'][$revision]['user']['name']}}</strong> |
{{$history['info'][$revision]['changed_at']}} |
{{trans_choice('admin/history.numOfChanges', count($changes))}}
</a>
@foreach($changes as $change)
<div class="accordion-content" data-tab-content>
<strong>{{$change->column_trans}}</strong>: {!! $change->diff !!}
</div>
@endforeach
</li>
@endforeach
</ul>
</div>
@stop