X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/3b31ac75ec41b3990cea770a9e48e2066bd8e9a3..refs/pull/5280/head:/app/Entities/Controllers/ChapterApiController.php diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index ce20e6b96..430654330 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -2,38 +2,44 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Queries\ChapterQueries; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Repos\ChapterRepo; +use BookStack\Exceptions\PermissionsException; use BookStack\Http\ApiController; +use Exception; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Http\Request; class ChapterApiController extends ApiController { - protected $chapterRepo; - protected $rules = [ 'create' => [ - 'book_id' => ['required', 'integer'], - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'tags' => ['array'], + 'book_id' => ['required', 'integer'], + 'name' => ['required', 'string', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], + 'default_template_id' => ['nullable', 'integer'], ], 'update' => [ - 'book_id' => ['integer'], - 'name' => ['string', 'min:1', 'max:255'], - 'description' => ['string', 'max:1000'], - 'tags' => ['array'], + 'book_id' => ['integer'], + 'name' => ['string', 'min:1', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], + 'default_template_id' => ['nullable', 'integer'], ], ]; - /** - * ChapterController constructor. - */ - public function __construct(ChapterRepo $chapterRepo) - { - $this->chapterRepo = $chapterRepo; + public function __construct( + protected ChapterRepo $chapterRepo, + protected ChapterQueries $queries, + protected EntityQueries $entityQueries, + ) { } /** @@ -41,7 +47,8 @@ class ChapterApiController extends ApiController */ public function list() { - $chapters = Chapter::visible(); + $chapters = $this->queries->visibleForList() + ->addSelect(['created_by', 'updated_by']); return $this->apiListingResponse($chapters, [ 'id', 'book_id', 'name', 'slug', 'description', 'priority', @@ -54,15 +61,15 @@ class ChapterApiController extends ApiController */ public function create(Request $request) { - $this->validate($request, $this->rules['create']); + $requestData = $this->validate($request, $this->rules['create']); $bookId = $request->get('book_id'); - $book = Book::visible()->findOrFail($bookId); + $book = $this->entityQueries->books->findVisibleByIdOrFail(intval($bookId)); $this->checkOwnablePermission('chapter-create', $book); - $chapter = $this->chapterRepo->create($request->all(), $book); + $chapter = $this->chapterRepo->create($requestData, $book); - return response()->json($chapter->load(['tags'])); + return response()->json($this->forJsonDisplay($chapter)); } /** @@ -70,24 +77,49 @@ class ChapterApiController extends ApiController */ public function read(string $id) { - $chapter = Chapter::visible()->with(['tags', 'createdBy', 'updatedBy', 'ownedBy', 'pages' => function (HasMany $query) { - $query->scopes('visible')->get(['id', 'name', 'slug']); - }])->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); + $chapter = $this->forJsonDisplay($chapter); + + $chapter->load(['createdBy', 'updatedBy', 'ownedBy']); + + // Note: More fields than usual here, for backwards compatibility, + // due to previously accidentally including more fields that desired. + $pages = $this->entityQueries->pages->visibleForChapterList($chapter->id) + ->addSelect(['created_by', 'updated_by', 'revision_count', 'editor']) + ->get(); + $chapter->setRelation('pages', $pages); return response()->json($chapter); } /** * Update the details of a single chapter. + * Providing a 'book_id' property will essentially move the chapter + * into that parent element if you have permissions to do so. */ public function update(Request $request, string $id) { - $chapter = Chapter::visible()->findOrFail($id); + $requestData = $this->validate($request, $this->rules()['update']); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('chapter-update', $chapter); - $updatedChapter = $this->chapterRepo->update($chapter, $request->all()); + if ($request->has('book_id') && $chapter->book_id !== intval($requestData['book_id'])) { + $this->checkOwnablePermission('chapter-delete', $chapter); + + try { + $this->chapterRepo->move($chapter, "book:{$requestData['book_id']}"); + } catch (Exception $exception) { + if ($exception instanceof PermissionsException) { + $this->showPermissionError(); + } - return response()->json($updatedChapter->load(['tags'])); + return $this->jsonError(trans('errors.selected_book_not_found')); + } + } + + $updatedChapter = $this->chapterRepo->update($chapter, $requestData); + + return response()->json($this->forJsonDisplay($updatedChapter)); } /** @@ -96,11 +128,24 @@ class ChapterApiController extends ApiController */ public function delete(string $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('chapter-delete', $chapter); $this->chapterRepo->destroy($chapter); return response('', 204); } + + protected function forJsonDisplay(Chapter $chapter): Chapter + { + $chapter = clone $chapter; + $chapter->unsetRelations()->refresh(); + + $chapter->load(['tags']); + $chapter->makeVisible('description_html'); + $chapter->setAttribute('description_html', $chapter->descriptionHtml()); + $chapter->setAttribute('book_slug', $chapter->book()->first()->slug); + + return $chapter; + } }