]> BookStack Code Mirror - bookstack/commitdiff
Added chapter move actions. Closes #86
authorDan Brown <redacted>
Sat, 25 Jun 2016 14:31:38 +0000 (15:31 +0100)
committerDan Brown <redacted>
Sat, 25 Jun 2016 14:31:38 +0000 (15:31 +0100)
app/Http/Controllers/ChapterController.php
app/Http/Controllers/PageController.php
app/Http/routes.php
app/Repos/ChapterRepo.php
resources/lang/en/activities.php
resources/views/chapters/move.blade.php [new file with mode: 0644]
resources/views/chapters/show.blade.php
resources/views/pages/move.blade.php
resources/views/partials/entity-selector.blade.php [new file with mode: 0644]
tests/Entity/SortTest.php

index 69e9488b908d7fe887b762e71adae1e13b8a59a4..a3a939f617bc0218b1563e0220b77dd53bddbab1 100644 (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
index 373e49de762a2a113793cc20a96b9c195ea0d9cc..f35834e6221ea73332b7e88b9187c73b899fb8a5 100644 (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);
index d7c090953f2c741997a2c75eb7cfc45ad32ca012..eb35f2a119846bd87836b3ff4b7ff156f242c89a 100644 (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');
index 048e0a63bd24ef047a59c810570b446f83b6d93c..3c518bde9de3f01a0c6a056ffdcc50cb5f27c772 100644 (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;
     }
 
index c59513aa95a57901d8a26c06ce978e0969be7927..56af4ca07b3c9914c9158a9e6c33fa116d07c2b7 100644 (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',
diff --git a/resources/views/chapters/move.blade.php b/resources/views/chapters/move.blade.php
new file mode 100644 (file)
index 0000000..024fc66
--- /dev/null
@@ -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
index 26935847150706d5eae65ccf9ecb8b91e36c2c7a..b79cd8415fffd8d2fd1e0d463b56fc1784d0d852 100644 (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>
                         @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>
index 5b02c827e7daecdc89ef8f7a7822c1525d4a6f55..27ee4cd925644d2ea32e418cd7f2a418a789ebb7 100644 (file)
             {!! 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>
diff --git a/resources/views/partials/entity-selector.blade.php b/resources/views/partials/entity-selector.blade.php
new file mode 100644 (file)
index 0000000..59e1741
--- /dev/null
@@ -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>
\ No newline at end of file
index 2f2f9109d3f254adcb3a32ff7deea749bebe3391..80783912a46f79e9ef93a8aad43574bc1772e4a7 100644 (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);
+    }
+
 }
\ No newline at end of file