Added chapter move actions. Closes #86

This commit is contained in:
Dan Brown 2016-06-25 15:31:38 +01:00
parent e584b4926f
commit 9baa96d41c
10 changed files with 167 additions and 17 deletions

View File

@ -154,6 +154,55 @@ class ChapterController extends Controller
return redirect($book->getUrl());
}
/**
* Show the page for moving a chapter.
* @param $bookSlug
* @param $chapterSlug
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException
*/
public function showMove($bookSlug, $chapterSlug) {
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$this->checkOwnablePermission('chapter-update', $chapter);
return view('chapters/move', [
'chapter' => $chapter,
'book' => $book
]);
}
public function move($bookSlug, $chapterSlug, Request $request) {
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$this->checkOwnablePermission('chapter-update', $chapter);
$entitySelection = $request->get('entity_selection', null);
if ($entitySelection === null || $entitySelection === '') {
return redirect($chapter->getUrl());
}
$stringExploded = explode(':', $entitySelection);
$entityType = $stringExploded[0];
$entityId = intval($stringExploded[1]);
$parent = false;
if ($entityType == 'book') {
$parent = $this->bookRepo->getById($entityId);
}
if ($parent === false || $parent === null) {
session()->flash('The selected Book was not found');
return redirect()->back();
}
$this->chapterRepo->changeBook($parent->id, $chapter);
Activity::add($chapter, 'chapter_move', $chapter->book->id);
session()->flash('success', sprintf('Chapter moved to "%s"', $parent->name));
return redirect($chapter->getUrl());
}
/**
* Show the Restrictions view.
* @param $bookSlug

View File

@ -468,6 +468,14 @@ class PageController extends Controller
]);
}
/**
* Does the action of moving the location of a page
* @param $bookSlug
* @param $pageSlug
* @param Request $request
* @return mixed
* @throws NotFoundException
*/
public function move($bookSlug, $pageSlug, Request $request)
{
$book = $this->bookRepo->getBySlug($bookSlug);

View File

@ -55,6 +55,8 @@ Route::group(['middleware' => 'auth'], function () {
Route::post('/{bookSlug}/chapter/create', 'ChapterController@store');
Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show');
Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update');
Route::get('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@showMove');
Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move');
Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict');
Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict');

View File

@ -9,6 +9,18 @@ use BookStack\Chapter;
class ChapterRepo extends EntityRepo
{
protected $pageRepo;
/**
* ChapterRepo constructor.
* @param $pageRepo
*/
public function __construct(PageRepo $pageRepo)
{
$this->pageRepo = $pageRepo;
parent::__construct();
}
/**
* Base query for getting chapters, Takes permissions into account.
* @return mixed
@ -189,12 +201,21 @@ class ChapterRepo extends EntityRepo
public function changeBook($bookId, Chapter $chapter)
{
$chapter->book_id = $bookId;
// Update related activity
foreach ($chapter->activity as $activity) {
$activity->book_id = $bookId;
$activity->save();
}
$chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id);
$chapter->save();
// Update all child pages
foreach ($chapter->pages as $page) {
$this->pageRepo->changeBook($bookId, $page);
}
// Update permissions
$chapter->load('book');
$this->permissionService->buildJointPermissionsForEntity($chapter->book);
return $chapter;
}

View File

@ -4,7 +4,7 @@ return [
/**
* Activity text strings.
* Is used for all the text within activity logs.
* Is used for all the text within activity logs & notifications.
*/
// Pages
@ -25,6 +25,7 @@ return [
'chapter_update_notification' => 'Chapter Successfully Updated',
'chapter_delete' => 'deleted chapter',
'chapter_delete_notification' => 'Chapter Successfully Deleted',
'chapter_move' => 'moved chapter',
// Books
'book_create' => 'created book',

View File

@ -0,0 +1,33 @@
@extends('base')
@section('content')
<div class="faded-small toolbar">
<div class="container">
<div class="row">
<div class="col-sm-12 faded">
<div class="breadcrumbs">
<a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
<span class="sep">&raquo;</span>
<a href="{{$chapter->getUrl()}}" class="text-page text-button"><i class="zmdi zmdi-file-text"></i>{{ $chapter->getShortName() }}</a>
</div>
</div>
</div>
</div>
</div>
<div class="container">
<h1>Move Chapter <small class="subheader">{{$chapter->name}}</small></h1>
<form action="{{ $chapter->getUrl() }}/move" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
@include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book'])
<a href="{{ $chapter->getUrl() }}" class="button muted">Cancel</a>
<button type="submit" class="button pos">Move Chapter</button>
</form>
</div>
@stop

View File

@ -2,15 +2,15 @@
@section('content')
<div class="faded-small toolbar" ng-non-bindable>
<div class="faded-small toolbar">
<div class="container">
<div class="row">
<div class="col-md-4 faded">
<div class="col-sm-8 faded" ng-non-bindable>
<div class="breadcrumbs">
<a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
</div>
</div>
<div class="col-md-8 faded">
<div class="col-sm-4 faded">
<div class="action-buttons">
@if(userCan('page-create', $chapter))
<a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a>
@ -18,11 +18,21 @@
@if(userCan('chapter-update', $chapter))
<a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
@endif
@if(userCan('restrictions-manage', $chapter))
<a href="{{$chapter->getUrl()}}/permissions" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Permissions</a>
@endif
@if(userCan('chapter-delete', $chapter))
<a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
@if(userCan('chapter-update', $chapter) || userCan('restrictions-manage', $chapter) || userCan('chapter-delete', $chapter))
<div dropdown class="dropdown-container">
<a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
<ul>
@if(userCan('chapter-update', $chapter))
<li><a href="{{$chapter->getUrl() . '/move'}}" class="text-primary"><i class="zmdi zmdi-folder"></i>Move</a></li>
@endif
@if(userCan('restrictions-manage', $chapter))
<li><a href="{{$chapter->getUrl()}}/permissions" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Permissions</a></li>
@endif
@if(userCan('chapter-delete', $chapter))
<li><a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li>
@endif
</ul>
</div>
@endif
</div>
</div>

View File

@ -30,14 +30,7 @@
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
<div class="form-group">
<div entity-selector class="entity-selector large" entity-types="book,chapter">
<input type="hidden" entity-selector-input name="entity_selection" value="">
<input type="text" placeholder="Search" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()">
<div class="text-center loading" ng-show="loading">@include('partials/loading-icon')</div>
<div ng-show="!loading" ng-bind-html="entityResults"></div>
</div>
</div>
@include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter'])
<a href="{{ $page->getUrl() }}" class="button muted">Cancel</a>
<button type="submit" class="button pos">Move Page</button>

View File

@ -0,0 +1,8 @@
<div class="form-group">
<div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}">
<input type="hidden" entity-selector-input name="{{$name}}" value="">
<input type="text" placeholder="Search" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()">
<div class="text-center loading" ng-show="loading">@include('partials/loading-icon')</div>
<div ng-show="!loading" ng-bind-html="entityResults"></div>
</div>
</div>

View File

@ -40,4 +40,29 @@ class SortTest extends TestCase
->seeInNthElement('.activity-list-item', 0, $page->name);
}
public function test_chapter_move()
{
$chapter = \BookStack\Chapter::first();
$currentBook = $chapter->book;
$pageToCheck = $chapter->pages->first();
$newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
$this->asAdmin()->visit($chapter->getUrl() . '/move')
->see('Move Chapter')->see($chapter->name)
->type('book:' . $newBook->id, 'entity_selection')->press('Move Chapter');
$chapter = \BookStack\Chapter::find($chapter->id);
$this->seePageIs($chapter->getUrl());
$this->assertTrue($chapter->book->id === $newBook->id, 'Chapter Book is now the new book');
$this->visit($newBook->getUrl())
->seeInNthElement('.activity-list-item', 0, 'moved chapter')
->seeInNthElement('.activity-list-item', 0, $chapter->name);
$pageToCheck = \BookStack\Page::find($pageToCheck->id);
$this->assertTrue($pageToCheck->book_id === $newBook->id, 'Chapter child page\'s book id has changed to the new book');
$this->visit($pageToCheck->getUrl())
->see($newBook->name);
}
}