Merge pull request #1008 from BookStackApp/revision-deletion

#784 - Adds ability to remove particular revision.
This commit is contained in:
Abijeet Patro 2018-09-16 21:30:04 +05:30 committed by GitHub
commit 32e34f10ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 208 additions and 7 deletions

View File

@ -454,6 +454,40 @@ class PageController extends Controller
return redirect($page->getUrl()); return redirect($page->getUrl());
} }
/**
* Deletes a revision using the id of the specified revision.
* @param string $bookSlug
* @param string $pageSlug
* @param int $revId
* @throws NotFoundException
* @throws BadRequestException
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroyRevision($bookSlug, $pageSlug, $revId)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-delete', $page);
$revision = $page->revisions()->where('id', '=', $revId)->first();
if ($revision === null) {
throw new NotFoundException("Revision #{$revId} not found");
}
// Get the current revision for the page
$currentRevision = $page->getCurrentRevision();
// Check if its the latest revision, cannot delete latest revision.
if (intval($currentRevision->id) === intval($revId)) {
session()->flash('error', trans('entities.revision_cannot_delete_latest'));
return response()->view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page], 400);
}
$revision->delete();
session()->flash('success', trans('entities.revision_delete_success'));
return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
}
/** /**
* Exports a page to a PDF. * Exports a page to a PDF.
* https://github.com/barryvdh/laravel-dompdf * https://github.com/barryvdh/laravel-dompdf

View File

@ -112,4 +112,13 @@ class Page extends Entity
$htmlQuery = $withContent ? 'html' : "'' as html"; $htmlQuery = $withContent ? 'html' : "'' as html";
return "'BookStack\\\\Page' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, {$htmlQuery}, book_id, priority, chapter_id, draft, created_by, updated_by, updated_at, created_at"; return "'BookStack\\\\Page' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, {$htmlQuery}, book_id, priority, chapter_id, draft, created_by, updated_by, updated_at, created_at";
} }
/**
* Get the current revision for the page if existing
* @return \BookStack\PageRevision|null
*/
public function getCurrentRevision()
{
return $this->revisions()->first();
}
} }

View File

@ -367,7 +367,7 @@ ul.pagination {
padding: $-xs $-m; padding: $-xs $-m;
line-height: 1.2; line-height: 1.2;
} }
a { a, button {
display: block; display: block;
padding: $-xs $-m; padding: $-xs $-m;
color: #555; color: #555;
@ -382,6 +382,10 @@ ul.pagination {
width: 16px; width: 16px;
} }
} }
button {
width: 100%;
text-align: left;
}
li.border-bottom { li.border-bottom {
border-bottom: 1px solid #DDD; border-bottom: 1px solid #DDD;
} }

View File

@ -41,6 +41,9 @@ table.table {
.text-center { .text-center {
text-align: center; text-align: center;
} }
td.actions {
overflow: visible;
}
} }
table.no-style { table.no-style {

View File

@ -256,4 +256,11 @@ return [
'comment_updated_success' => 'Kommentar aktualisiert', 'comment_updated_success' => 'Kommentar aktualisiert',
'comment_delete_confirm' => 'Möchten Sie diesen Kommentar wirklich löschen?', 'comment_delete_confirm' => 'Möchten Sie diesen Kommentar wirklich löschen?',
'comment_in_reply_to' => 'Antwort auf :commentId', 'comment_in_reply_to' => 'Antwort auf :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Sind Sie sicher, dass Sie diese Revision löschen wollen?',
'revision_delete_success' => 'Revision gelöscht',
'revision_cannot_delete_latest' => 'Die letzte Version kann nicht gelöscht werden.'
]; ];

View File

@ -265,4 +265,11 @@ return [
'comment_updated_success' => 'Comment updated', 'comment_updated_success' => 'Comment updated',
'comment_delete_confirm' => 'Are you sure you want to delete this comment?', 'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
'comment_in_reply_to' => 'In reply to :commentId', 'comment_in_reply_to' => 'In reply to :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Are you sure you want to delete this revision?',
'revision_delete_success' => 'Revision deleted',
'revision_cannot_delete_latest' => 'Cannot delete the latest revision.'
]; ];

View File

@ -265,4 +265,11 @@ return [
'comment_updated_success' => 'Comentario actualizado', 'comment_updated_success' => 'Comentario actualizado',
'comment_delete_confirm' => '¿Está seguro de que quiere borrar este comentario?', 'comment_delete_confirm' => '¿Está seguro de que quiere borrar este comentario?',
'comment_in_reply_to' => 'En respuesta a :commentId', 'comment_in_reply_to' => 'En respuesta a :commentId',
/**
* Revision
*/
'revision_delete_confirm' => '¿Está seguro de que desea eliminar esta revisión?',
'revision_delete_success' => 'Revisión eliminada',
'revision_cannot_delete_latest' => 'No se puede eliminar la última revisión.'
]; ];

View File

@ -265,4 +265,11 @@ return [
'comment_updated_success' => 'Comentario actualizado', 'comment_updated_success' => 'Comentario actualizado',
'comment_delete_confirm' => '¿Está seguro que quiere borrar este comentario?', 'comment_delete_confirm' => '¿Está seguro que quiere borrar este comentario?',
'comment_in_reply_to' => 'En respuesta a :commentId', 'comment_in_reply_to' => 'En respuesta a :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Are you sure you want to delete this revision?',
'revision_delete_success' => 'Revisión eliminada',
'revision_cannot_delete_latest' => 'No se puede eliminar la última revisión.'
]; ];

View File

@ -265,4 +265,11 @@ return [
'comment_updated_success' => 'Commentaire mis à jour', 'comment_updated_success' => 'Commentaire mis à jour',
'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?', 'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?',
'comment_in_reply_to' => 'En réponse à :commentId', 'comment_in_reply_to' => 'En réponse à :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Êtes-vous sûr de vouloir supprimer cette révision?',
'revision_delete_success' => 'Révision supprimée',
'revision_cannot_delete_latest' => 'Impossible de supprimer la dernière révision.'
]; ];

View File

@ -260,4 +260,11 @@ return [
'comment_updated_success' => 'Commento aggiornato', 'comment_updated_success' => 'Commento aggiornato',
'comment_delete_confirm' => 'Sei sicuro di voler elminare questo commento?', 'comment_delete_confirm' => 'Sei sicuro di voler elminare questo commento?',
'comment_in_reply_to' => 'In risposta a :commentId', 'comment_in_reply_to' => 'In risposta a :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Sei sicuro di voler eliminare questa revisione?',
'revision_delete_success' => 'Revisione cancellata',
'revision_cannot_delete_latest' => 'Impossibile eliminare l\'ultima revisione.'
]; ];

View File

@ -257,4 +257,11 @@ return [
'comment_updated_success' => 'コメントを更新しました', 'comment_updated_success' => 'コメントを更新しました',
'comment_delete_confirm' => '本当にこのコメントを削除しますか?', 'comment_delete_confirm' => '本当にこのコメントを削除しますか?',
'comment_in_reply_to' => ':commentIdへ返信', 'comment_in_reply_to' => ':commentIdへ返信',
/**
* Revision
*/
'revision_delete_confirm' => 'このリビジョンを削除しますか?',
'revision_delete_success' => 'リビジョンを削除しました',
'revision_cannot_delete_latest' => '最新のリビジョンを削除できません。'
]; ];

View File

@ -259,4 +259,11 @@ return [
'comment_updated_success' => 'Reactie bijgewerkt', 'comment_updated_success' => 'Reactie bijgewerkt',
'comment_delete_confirm' => 'Zeker reactie verwijderen?', 'comment_delete_confirm' => 'Zeker reactie verwijderen?',
'comment_in_reply_to' => 'Antwoord op :commentId', 'comment_in_reply_to' => 'Antwoord op :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Weet u zeker dat u deze revisie wilt verwijderen?',
'revision_delete_success' => 'Revisie verwijderd',
'revision_cannot_delete_latest' => 'Kan de laatste revisie niet verwijderen.'
]; ];

View File

@ -257,4 +257,11 @@ return [
'comment_updated_success' => 'Komentarz zaktualizowany', 'comment_updated_success' => 'Komentarz zaktualizowany',
'comment_delete_confirm' => 'Czy na pewno chcesz usunąc ten komentarz?', 'comment_delete_confirm' => 'Czy na pewno chcesz usunąc ten komentarz?',
'comment_in_reply_to' => 'W odpowiedzi na :commentId', 'comment_in_reply_to' => 'W odpowiedzi na :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Czy na pewno chcesz usunąć tę wersję?',
'revision_delete_success' => 'Usunięto wersję',
'revision_cannot_delete_latest' => 'Nie można usunąć najnowszej wersji.'
]; ];

View File

@ -258,4 +258,11 @@ return [
'comment_updated_success' => 'Comentário editado', 'comment_updated_success' => 'Comentário editado',
'comment_delete_confirm' => 'Você tem certeza de que quer deletar este comentário?', 'comment_delete_confirm' => 'Você tem certeza de que quer deletar este comentário?',
'comment_in_reply_to' => 'Em resposta à :commentId', 'comment_in_reply_to' => 'Em resposta à :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Tem certeza de que deseja excluir esta revisão?',
'revision_delete_success' => 'Revisão excluída',
'revision_cannot_delete_latest' => 'Não é possível excluir a revisão mais recente.'
]; ];

View File

@ -258,4 +258,11 @@ return [
'comment_updated_success' => 'Комментарий обновлён', 'comment_updated_success' => 'Комментарий обновлён',
'comment_delete_confirm' => 'Вы уверенны, что хотите удалить этот комментарий?', 'comment_delete_confirm' => 'Вы уверенны, что хотите удалить этот комментарий?',
'comment_in_reply_to' => 'В ответ на :commentId', 'comment_in_reply_to' => 'В ответ на :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Вы действительно хотите удалить эту ревизию?',
'revision_delete_success' => 'Редактирование удалено',
'revision_cannot_delete_latest' => 'Не удается удалить последнюю версию.'
]; ];

View File

@ -232,4 +232,11 @@ return [
'comments' => 'Komentáre', 'comments' => 'Komentáre',
'comment_placeholder' => 'Tu zadajte svoje pripomienky', 'comment_placeholder' => 'Tu zadajte svoje pripomienky',
'comment_save' => 'Uložiť komentár', 'comment_save' => 'Uložiť komentár',
/**
* Revision
*/
'revision_delete_confirm' => 'Naozaj chcete túto revíziu odstrániť?',
'revision_delete_success' => 'Revízia bola vymazaná',
'revision_cannot_delete_latest' => 'Nie je možné vymazať poslednú revíziu.'
]; ];

View File

@ -265,4 +265,11 @@ return [
'comment_updated_success' => 'Kommentaren har uppdaterats', 'comment_updated_success' => 'Kommentaren har uppdaterats',
'comment_delete_confirm' => 'Är du säker på att du vill ta bort den här kommentaren?', 'comment_delete_confirm' => 'Är du säker på att du vill ta bort den här kommentaren?',
'comment_in_reply_to' => 'Som svar på :commentId', 'comment_in_reply_to' => 'Som svar på :commentId',
/**
* Revision
*/
'revision_delete_confirm' => 'Är du säker på att du vill radera den här versionen?',
'revision_delete_success' => 'Revisionen raderad',
'revision_cannot_delete_latest' => 'Det går inte att ta bort den senaste versionen.'
]; ];

View File

@ -258,4 +258,11 @@ return [
'comment_updated_success' => '评论已更新', 'comment_updated_success' => '评论已更新',
'comment_delete_confirm' => '你确定要删除这条评论?', 'comment_delete_confirm' => '你确定要删除这条评论?',
'comment_in_reply_to' => '回复 :commentId', 'comment_in_reply_to' => '回复 :commentId',
/**
* Revision
*/
'revision_delete_confirm' => '您确定要删除此修订版吗?',
'revision_delete_success' => '修订删除',
'revision_cannot_delete_latest' => '无法删除最新版本。'
]; ];

View File

@ -259,4 +259,11 @@ return [
'comment_updated_success' => '評論已更新', 'comment_updated_success' => '評論已更新',
'comment_delete_confirm' => '你確定要刪除這條評論?', 'comment_delete_confirm' => '你確定要刪除這條評論?',
'comment_in_reply_to' => '回覆 :commentId', 'comment_in_reply_to' => '回覆 :commentId',
/**
* Revision
*/
'revision_delete_confirm' => '您確定要刪除此修訂版嗎?',
'revision_delete_success' => '修訂刪除',
'revision_cannot_delete_latest' => '無法刪除最新版本。'
]; ];

View File

@ -36,16 +36,31 @@
<td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif</td> <td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif</td>
<td><small>{{ $revision->created_at->format('jS F, Y H:i:s') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td> <td><small>{{ $revision->created_at->format('jS F, Y H:i:s') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td>
<td>{{ $revision->summary }}</td> <td>{{ $revision->summary }}</td>
<td> <td class="actions">
<a href="{{ $revision->getUrl('changes') }}" target="_blank">{{ trans('entities.pages_revisions_changes') }}</a> <a href="{{ $revision->getUrl('changes') }}" target="_blank">{{ trans('entities.pages_revisions_changes') }}</a>
<span class="text-muted">&nbsp;|&nbsp;</span> <span class="text-muted">&nbsp;|&nbsp;</span>
@if ($index === 0) @if ($index === 0)
<a target="_blank" href="{{ $page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a> <a target="_blank" href="{{ $page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
@else @else
<a href="{{ $revision->getUrl() }}" target="_blank">{{ trans('entities.pages_revisions_preview') }}</a> <a href="{{ $revision->getUrl() }}" target="_blank">{{ trans('entities.pages_revisions_preview') }}</a>
<span class="text-muted">&nbsp;|&nbsp;</span> <span class="text-muted">&nbsp;|&nbsp;</span>
<a href="{{ $revision->getUrl('restore') }}">{{ trans('entities.pages_revisions_restore') }}</a> <a href="{{ $revision->getUrl('restore') }}">{{ trans('entities.pages_revisions_restore') }}</a>
<span class="text-muted">&nbsp;|&nbsp;</span>
<div dropdown class="dropdown-container">
<a dropdown-toggle>{{ trans('common.delete') }}</a>
<ul>
<li class="padded"><small class="text-muted">{{trans('entities.revision_delete_confirm')}}</small></li>
<li>
<form action="{{ $revision->getUrl('/delete/') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="text-button neg">@icon('delete'){{ trans('common.delete') }}</button>
</form>
</li>
</ul>
</div>
@endif @endif
</td> </td>
</tr> </tr>

View File

@ -19,4 +19,4 @@
color: {{ setting('app-color') }}; color: {{ setting('app-color') }};
fill: {{ setting('app-color') }}; fill: {{ setting('app-color') }};
} }
</style> </style>

View File

@ -61,6 +61,7 @@ Route::group(['middleware' => 'auth'], function () {
Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageController@showRevision'); Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageController@showRevision');
Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/changes', 'PageController@showRevisionChanges'); Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/changes', 'PageController@showRevisionChanges');
Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageController@restoreRevision'); Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageController@restoreRevision');
Route::delete('/{bookSlug}/page/{pageSlug}/revisions/{revId}/delete', 'PageController@destroyRevision');
// Chapters // Chapters
Route::get('/{bookSlug}/chapter/{chapterSlug}/create-page', 'PageController@create'); Route::get('/{bookSlug}/chapter/{chapterSlug}/create-page', 'PageController@create');
@ -79,7 +80,6 @@ Route::group(['middleware' => 'auth'], function () {
Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict'); Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict');
Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
}); });
// User Profile routes // User Profile routes

View File

@ -11,7 +11,6 @@ class PageRevisionTest extends TestCase
{ {
$page = Page::first(); $page = Page::first();
$startCount = $page->revision_count; $startCount = $page->revision_count;
$resp = $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); $resp = $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
$resp->assertStatus(302); $resp->assertStatus(302);
@ -22,11 +21,43 @@ class PageRevisionTest extends TestCase
{ {
$page = Page::first(); $page = Page::first();
$this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
$this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
$page = Page::find($page->id);
$page = Page::find($page->id);
$this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
$page = Page::find($page->id);
$pageView = $this->get($page->getUrl()); $pageView = $this->get($page->getUrl());
$pageView->assertSee('Revision #' . $page->revision_count); $pageView->assertSee('Revision #' . $page->revision_count);
} }
public function test_revision_deletion() {
$page = Page::first();
$this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
$page = Page::find($page->id);
$this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']);
$page = Page::find($page->id);
$beforeRevisionCount = $page->revisions->count();
// Delete the first revision
$revision = $page->revisions->get(1);
$resp = $this->asEditor()->delete($revision->getUrl('/delete/'));
$resp->assertStatus(200);
$page = Page::find($page->id);
$afterRevisionCount = $page->revisions->count();
$this->assertTrue($beforeRevisionCount === ($afterRevisionCount + 1));
// Try to delete the latest revision
$beforeRevisionCount = $page->revisions->count();
$currentRevision = $page->getCurrentRevision();
$resp = $this->asEditor()->delete($currentRevision->getUrl('/delete/'));
$resp->assertStatus(400);
$page = Page::find($page->id);
$afterRevisionCount = $page->revisions->count();
$this->assertTrue($beforeRevisionCount === $afterRevisionCount);
}
} }