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
]);
}
+ /**
+ * 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);
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');
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
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;
}
/**
* Activity text strings.
- * Is used for all the text within activity logs.
+ * Is used for all the text within activity logs & notifications.
*/
// Pages
'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',
--- /dev/null
+@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">»</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
@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>
@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>
{!! 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>
--- /dev/null
+<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>
\ No newline at end of file
->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);
+ }
+
}
\ No newline at end of file