]> BookStack Code Mirror - bookstack/commitdiff
Rolled out reference pages to all entities, added testing
authorDan Brown <redacted>
Fri, 19 Aug 2022 21:40:44 +0000 (22:40 +0100)
committerDan Brown <redacted>
Fri, 19 Aug 2022 21:40:44 +0000 (22:40 +0100)
Including testing to check permissions applied to listed references.

app/Http/Controllers/ReferenceController.php
resources/views/books/references.blade.php [new file with mode: 0644]
resources/views/chapters/references.blade.php [new file with mode: 0644]
resources/views/entities/references.blade.php [new file with mode: 0644]
resources/views/pages/references.blade.php
resources/views/shelves/references.blade.php [new file with mode: 0644]
routes/web.php
tests/References/ReferencesTest.php
tests/TestCase.php

index bed2c5f305cc7903d0b30dd91754ac8089d49c74..3af4feb06108b9cd29afd5a131561ead8b5e9f09 100644 (file)
@@ -3,7 +3,12 @@
 namespace BookStack\Http\Controllers;
 
 use BookStack\Auth\Permissions\PermissionApplicator;
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Chapter;
+use BookStack\Entities\Models\Entity;
 use BookStack\Entities\Models\Page;
+use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Database\Eloquent\Relations\Relation;
 
 class ReferenceController extends Controller
@@ -23,8 +28,64 @@ class ReferenceController extends Controller
     {
         /** @var Page $page */
         $page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->firstOrFail();
+        $references = $this->getEntityReferences($page);
 
-        $baseQuery = $page->referencesTo()
+        return view('pages.references', [
+            'page' => $page,
+            'references' => $references,
+        ]);
+    }
+
+    /**
+     * Display the references to a given chapter.
+     */
+    public function chapter(string $bookSlug, string $chapterSlug)
+    {
+        /** @var Chapter $chapter */
+        $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail();
+        $references = $this->getEntityReferences($chapter);
+
+        return view('chapters.references', [
+            'chapter' => $chapter,
+            'references' => $references,
+        ]);
+    }
+
+    /**
+     * Display the references to a given book.
+     */
+    public function book(string $slug)
+    {
+        $book = Book::visible()->where('slug', '=', $slug)->firstOrFail();
+        $references = $this->getEntityReferences($book);
+
+        return view('books.references', [
+            'book' => $book,
+            'references' => $references,
+        ]);
+    }
+
+    /**
+     * Display the references to a given shelf.
+     */
+    public function shelf(string $slug)
+    {
+        $shelf = Bookshelf::visible()->where('slug', '=', $slug)->firstOrFail();
+        $references = $this->getEntityReferences($shelf);
+
+        return view('shelves.references', [
+            'shelf' => $shelf,
+            'references' => $references,
+        ]);
+    }
+
+    /**
+     * Query the references for the given entities.
+     * Loads the commonly required relations while taking permissions into account.
+     */
+    protected function getEntityReferences(Entity $entity): Collection
+    {
+        $baseQuery = $entity->referencesTo()
             ->where('from_type', '=', (new Page())->getMorphClass())
             ->with([
                 'from' => fn(Relation $query) => $query->select(Page::$listAttributes),
@@ -39,9 +100,6 @@ class ReferenceController extends Controller
             'from_type'
         )->get();
 
-        return view('pages.references', [
-            'page' => $page,
-            'references' => $references,
-        ]);
+        return $references;
     }
 }
diff --git a/resources/views/books/references.blade.php b/resources/views/books/references.blade.php
new file mode 100644 (file)
index 0000000..2468ed1
--- /dev/null
@@ -0,0 +1,20 @@
+@extends('layouts.simple')
+
+@section('body')
+
+    <div class="container small">
+
+        <div class="my-s">
+            @include('entities.breadcrumbs', ['crumbs' => [
+                $book,
+                $book->getUrl('/references') => [
+                    'text' => trans('entities.references'),
+                    'icon' => 'reference',
+                ]
+            ]])
+        </div>
+
+        @include('entities.references', ['references' => $references])
+    </div>
+
+@stop
diff --git a/resources/views/chapters/references.blade.php b/resources/views/chapters/references.blade.php
new file mode 100644 (file)
index 0000000..7241c2b
--- /dev/null
@@ -0,0 +1,21 @@
+@extends('layouts.simple')
+
+@section('body')
+
+    <div class="container small">
+
+        <div class="my-s">
+            @include('entities.breadcrumbs', ['crumbs' => [
+                $chapter->book,
+                $chapter,
+                $chapter->getUrl('/references') => [
+                    'text' => trans('entities.references'),
+                    'icon' => 'reference',
+                ]
+            ]])
+        </div>
+
+        @include('entities.references', ['references' => $references])
+    </div>
+
+@stop
diff --git a/resources/views/entities/references.blade.php b/resources/views/entities/references.blade.php
new file mode 100644 (file)
index 0000000..db9e167
--- /dev/null
@@ -0,0 +1,13 @@
+<main class="card content-wrap">
+    <h1 class="list-heading">{{ trans('entities.references') }}</h1>
+    <p>{{ trans('entities.references_to_desc') }}</p>
+
+    @if(count($references) > 0)
+        <div class="book-contents">
+            @include('entities.list', ['entities' => $references->pluck('from'), 'showPath' => true])
+        </div>
+    @else
+        <p class="text-muted italic">{{ trans('entities.references_none') }}</p>
+    @endif
+
+</main>
\ No newline at end of file
index 3f35a1629a39276d3bb0ae4e134a56a402ad289f..42ae7076ffd0704e5bafeb8d2d94a89c9cbea928 100644 (file)
             ]])
         </div>
 
-        <main class="card content-wrap">
-            <h1 class="list-heading">{{ trans('entities.references') }}</h1>
-            <p>{{ trans('entities.references_to_desc') }}</p>
-
-            @if(count($references) > 0)
-                <div class="book-contents">
-                    @include('entities.list', ['entities' => $references->pluck('from'), 'showPath' => true])
-                </div>
-            @else
-                <p class="text-muted italic">{{ trans('entities.references_none') }}</p>
-            @endif
-
-        </main>
+        @include('entities.references', ['references' => $references])
     </div>
 
 @stop
diff --git a/resources/views/shelves/references.blade.php b/resources/views/shelves/references.blade.php
new file mode 100644 (file)
index 0000000..7336c07
--- /dev/null
@@ -0,0 +1,20 @@
+@extends('layouts.simple')
+
+@section('body')
+
+    <div class="container small">
+
+        <div class="my-s">
+            @include('entities.breadcrumbs', ['crumbs' => [
+                $shelf,
+                $shelf->getUrl('/references') => [
+                    'text' => trans('entities.references'),
+                    'icon' => 'reference',
+                ]
+            ]])
+        </div>
+
+        @include('entities.references', ['references' => $references])
+    </div>
+
+@stop
index a16960283b15977d0ef7b6e178fbcf55c95dcd4e..dc46821cb86eb63ff98f588035c020643b11c4ac 100644 (file)
@@ -64,6 +64,7 @@ Route::middleware('auth')->group(function () {
     Route::get('/shelves/{slug}/permissions', [BookshelfController::class, 'showPermissions']);
     Route::put('/shelves/{slug}/permissions', [BookshelfController::class, 'permissions']);
     Route::post('/shelves/{slug}/copy-permissions', [BookshelfController::class, 'copyPermissions']);
+    Route::get('/shelves/{slug}/references', [ReferenceController::class, 'shelf']);
 
     // Book Creation
     Route::get('/shelves/{shelfSlug}/create-book', [BookController::class, 'create']);
@@ -86,6 +87,7 @@ Route::middleware('auth')->group(function () {
     Route::post('/books/{bookSlug}/convert-to-shelf', [BookController::class, 'convertToShelf']);
     Route::get('/books/{bookSlug}/sort', [BookSortController::class, 'show']);
     Route::put('/books/{bookSlug}/sort', [BookSortController::class, 'update']);
+    Route::get('/books/{slug}/references', [ReferenceController::class, 'book']);
     Route::get('/books/{bookSlug}/export/html', [BookExportController::class, 'html']);
     Route::get('/books/{bookSlug}/export/pdf', [BookExportController::class, 'pdf']);
     Route::get('/books/{bookSlug}/export/markdown', [BookExportController::class, 'markdown']);
@@ -142,6 +144,7 @@ Route::middleware('auth')->group(function () {
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/markdown', [ChapterExportController::class, 'markdown']);
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/plaintext', [ChapterExportController::class, 'plainText']);
     Route::put('/books/{bookSlug}/chapter/{chapterSlug}/permissions', [ChapterController::class, 'permissions']);
+    Route::get('/books/{bookSlug}/chapter/{chapterSlug}/references', [ReferenceController::class, 'chapter']);
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/delete', [ChapterController::class, 'showDelete']);
     Route::delete('/books/{bookSlug}/chapter/{chapterSlug}', [ChapterController::class, 'destroy']);
 
index 1285f591664915b0eaa76870ff61ebe1669041f0..20829b6b4ac695d4c39c5c458834a033b20cd199 100644 (file)
@@ -54,6 +54,46 @@ class ReferencesTest extends TestCase
         $this->assertDatabaseMissing('references', ['to_id' => $pageA->id, 'to_type' => $pageA->getMorphClass()]);
     }
 
+    public function test_references_to_visible_on_references_page()
+    {
+        $entities = $this->getEachEntityType();
+        $this->asEditor();
+        foreach ($entities as $entity) {
+            $this->createReference($entities['page'], $entity);
+        }
+
+        foreach ($entities as $entity) {
+            $resp = $this->get($entity->getUrl('/references'));
+            $resp->assertSee('References');
+            $resp->assertSee($entities['page']->name);
+            $resp->assertDontSee('There are no tracked references');
+        }
+    }
+
+    public function test_reference_not_visible_if_view_permission_does_not_permit()
+    {
+        /** @var Page $page */
+        /** @var Page $pageB */
+        $page = Page::query()->first();
+        $pageB = Page::query()->where('id', '!=', $page->id)->first();
+        $this->createReference($pageB, $page);
+
+        $this->setEntityRestrictions($pageB);
+
+        $this->asEditor()->get($page->getUrl('/references'))->assertDontSee($pageB->name);
+        $this->asAdmin()->get($page->getUrl('/references'))->assertSee($pageB->name);
+    }
+
+    public function test_reference_page_shows_empty_state_with_no_references()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+
+        $this->asEditor()
+            ->get($page->getUrl('/references'))
+            ->assertSee('There are no tracked references');
+    }
+
     protected function createReference(Model $from, Model $to)
     {
         (new Reference())->forceFill([
index 92ae33a4ebeeef1729ea4934cb8d5e9882788aeb..3ca7638c8e92c8a4d6071e024fb282f1c4e0cfa9 100644 (file)
@@ -430,7 +430,7 @@ abstract class TestCase extends BaseTestCase
     }
 
     /**
-     * @return Entity[]
+     * @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf}
      */
     protected function getEachEntityType(): array
     {