Dan Brown
Committed by GitHub

Merge pull request #262 from BookStackApp/entity_repo_refactor

Entity repo refactor
...@@ -5,6 +5,8 @@ class Chapter extends Entity ...@@ -5,6 +5,8 @@ class Chapter extends Entity
5 { 5 {
6 protected $fillable = ['name', 'description', 'priority', 'book_id']; 6 protected $fillable = ['name', 'description', 'priority', 'book_id'];
7 7
8 + protected $with = ['book'];
9 +
8 /** 10 /**
9 * Get the book this chapter is within. 11 * Get the book this chapter is within.
10 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 12 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
...@@ -16,11 +18,12 @@ class Chapter extends Entity ...@@ -16,11 +18,12 @@ class Chapter extends Entity
16 18
17 /** 19 /**
18 * Get the pages that this chapter contains. 20 * Get the pages that this chapter contains.
21 + * @param string $dir
19 * @return mixed 22 * @return mixed
20 */ 23 */
21 - public function pages() 24 + public function pages($dir = 'ASC')
22 { 25 {
23 - return $this->hasMany(Page::class)->orderBy('priority', 'ASC'); 26 + return $this->hasMany(Page::class)->orderBy('priority', $dir);
24 } 27 }
25 28
26 /** 29 /**
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
4 class Entity extends Ownable 4 class Entity extends Ownable
5 { 5 {
6 6
7 + protected $fieldsToSearch = ['name', 'description'];
8 +
7 /** 9 /**
8 * Compares this entity to another given entity. 10 * Compares this entity to another given entity.
9 * Matches by comparing class and id. 11 * Matches by comparing class and id.
...@@ -157,7 +159,7 @@ class Entity extends Ownable ...@@ -157,7 +159,7 @@ class Entity extends Ownable
157 * @param string[] array $wheres 159 * @param string[] array $wheres
158 * @return mixed 160 * @return mixed
159 */ 161 */
160 - public function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = []) 162 + public function fullTextSearchQuery($terms, $wheres = [])
161 { 163 {
162 $exactTerms = []; 164 $exactTerms = [];
163 $fuzzyTerms = []; 165 $fuzzyTerms = [];
...@@ -181,16 +183,16 @@ class Entity extends Ownable ...@@ -181,16 +183,16 @@ class Entity extends Ownable
181 // Perform fulltext search if relevant terms exist. 183 // Perform fulltext search if relevant terms exist.
182 if ($isFuzzy) { 184 if ($isFuzzy) {
183 $termString = implode(' ', $fuzzyTerms); 185 $termString = implode(' ', $fuzzyTerms);
184 - $fields = implode(',', $fieldsToSearch); 186 + $fields = implode(',', $this->fieldsToSearch);
185 $search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]); 187 $search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]);
186 $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); 188 $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]);
187 } 189 }
188 190
189 // Ensure at least one exact term matches if in search 191 // Ensure at least one exact term matches if in search
190 if (count($exactTerms) > 0) { 192 if (count($exactTerms) > 0) {
191 - $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) { 193 + $search = $search->where(function ($query) use ($exactTerms) {
192 foreach ($exactTerms as $exactTerm) { 194 foreach ($exactTerms as $exactTerm) {
193 - foreach ($fieldsToSearch as $field) { 195 + foreach ($this->fieldsToSearch as $field) {
194 $query->orWhere($field, 'like', $exactTerm); 196 $query->orWhere($field, 'like', $exactTerm);
195 } 197 }
196 } 198 }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 use BookStack\Exceptions\FileUploadException; 3 use BookStack\Exceptions\FileUploadException;
4 use BookStack\Attachment; 4 use BookStack\Attachment;
5 -use BookStack\Repos\PageRepo; 5 +use BookStack\Repos\EntityRepo;
6 use BookStack\Services\AttachmentService; 6 use BookStack\Services\AttachmentService;
7 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
8 8
...@@ -10,19 +10,19 @@ class AttachmentController extends Controller ...@@ -10,19 +10,19 @@ class AttachmentController extends Controller
10 { 10 {
11 protected $attachmentService; 11 protected $attachmentService;
12 protected $attachment; 12 protected $attachment;
13 - protected $pageRepo; 13 + protected $entityRepo;
14 14
15 /** 15 /**
16 * AttachmentController constructor. 16 * AttachmentController constructor.
17 * @param AttachmentService $attachmentService 17 * @param AttachmentService $attachmentService
18 * @param Attachment $attachment 18 * @param Attachment $attachment
19 - * @param PageRepo $pageRepo 19 + * @param EntityRepo $entityRepo
20 */ 20 */
21 - public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo) 21 + public function __construct(AttachmentService $attachmentService, Attachment $attachment, EntityRepo $entityRepo)
22 { 22 {
23 $this->attachmentService = $attachmentService; 23 $this->attachmentService = $attachmentService;
24 $this->attachment = $attachment; 24 $this->attachment = $attachment;
25 - $this->pageRepo = $pageRepo; 25 + $this->entityRepo = $entityRepo;
26 parent::__construct(); 26 parent::__construct();
27 } 27 }
28 28
...@@ -40,7 +40,7 @@ class AttachmentController extends Controller ...@@ -40,7 +40,7 @@ class AttachmentController extends Controller
40 ]); 40 ]);
41 41
42 $pageId = $request->get('uploaded_to'); 42 $pageId = $request->get('uploaded_to');
43 - $page = $this->pageRepo->getById($pageId, true); 43 + $page = $this->entityRepo->getById('page', $pageId, true);
44 44
45 $this->checkPermission('attachment-create-all'); 45 $this->checkPermission('attachment-create-all');
46 $this->checkOwnablePermission('page-update', $page); 46 $this->checkOwnablePermission('page-update', $page);
...@@ -70,7 +70,7 @@ class AttachmentController extends Controller ...@@ -70,7 +70,7 @@ class AttachmentController extends Controller
70 ]); 70 ]);
71 71
72 $pageId = $request->get('uploaded_to'); 72 $pageId = $request->get('uploaded_to');
73 - $page = $this->pageRepo->getById($pageId, true); 73 + $page = $this->entityRepo->getById('page', $pageId, true);
74 $attachment = $this->attachment->findOrFail($attachmentId); 74 $attachment = $this->attachment->findOrFail($attachmentId);
75 75
76 $this->checkOwnablePermission('page-update', $page); 76 $this->checkOwnablePermission('page-update', $page);
...@@ -106,7 +106,7 @@ class AttachmentController extends Controller ...@@ -106,7 +106,7 @@ class AttachmentController extends Controller
106 ]); 106 ]);
107 107
108 $pageId = $request->get('uploaded_to'); 108 $pageId = $request->get('uploaded_to');
109 - $page = $this->pageRepo->getById($pageId, true); 109 + $page = $this->entityRepo->getById('page', $pageId, true);
110 $attachment = $this->attachment->findOrFail($attachmentId); 110 $attachment = $this->attachment->findOrFail($attachmentId);
111 111
112 $this->checkOwnablePermission('page-update', $page); 112 $this->checkOwnablePermission('page-update', $page);
...@@ -134,7 +134,7 @@ class AttachmentController extends Controller ...@@ -134,7 +134,7 @@ class AttachmentController extends Controller
134 ]); 134 ]);
135 135
136 $pageId = $request->get('uploaded_to'); 136 $pageId = $request->get('uploaded_to');
137 - $page = $this->pageRepo->getById($pageId, true); 137 + $page = $this->entityRepo->getById('page', $pageId, true);
138 138
139 $this->checkPermission('attachment-create-all'); 139 $this->checkPermission('attachment-create-all');
140 $this->checkOwnablePermission('page-update', $page); 140 $this->checkOwnablePermission('page-update', $page);
...@@ -153,7 +153,7 @@ class AttachmentController extends Controller ...@@ -153,7 +153,7 @@ class AttachmentController extends Controller
153 */ 153 */
154 public function listForPage($pageId) 154 public function listForPage($pageId)
155 { 155 {
156 - $page = $this->pageRepo->getById($pageId, true); 156 + $page = $this->entityRepo->getById('page', $pageId, true);
157 $this->checkOwnablePermission('page-view', $page); 157 $this->checkOwnablePermission('page-view', $page);
158 return response()->json($page->attachments); 158 return response()->json($page->attachments);
159 } 159 }
...@@ -170,7 +170,7 @@ class AttachmentController extends Controller ...@@ -170,7 +170,7 @@ class AttachmentController extends Controller
170 'files' => 'required|array', 170 'files' => 'required|array',
171 'files.*.id' => 'required|integer', 171 'files.*.id' => 'required|integer',
172 ]); 172 ]);
173 - $page = $this->pageRepo->getById($pageId); 173 + $page = $this->entityRepo->getById('page', $pageId);
174 $this->checkOwnablePermission('page-update', $page); 174 $this->checkOwnablePermission('page-update', $page);
175 175
176 $attachments = $request->get('files'); 176 $attachments = $request->get('files');
...@@ -186,7 +186,7 @@ class AttachmentController extends Controller ...@@ -186,7 +186,7 @@ class AttachmentController extends Controller
186 public function get($attachmentId) 186 public function get($attachmentId)
187 { 187 {
188 $attachment = $this->attachment->findOrFail($attachmentId); 188 $attachment = $this->attachment->findOrFail($attachmentId);
189 - $page = $this->pageRepo->getById($attachment->uploaded_to); 189 + $page = $this->entityRepo->getById('page', $attachment->uploaded_to);
190 $this->checkOwnablePermission('page-view', $page); 190 $this->checkOwnablePermission('page-view', $page);
191 191
192 if ($attachment->external) { 192 if ($attachment->external) {
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use Activity; 3 use Activity;
4 +use BookStack\Repos\EntityRepo;
4 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 -use BookStack\Http\Requests;
7 -use BookStack\Repos\BookRepo;
8 -use BookStack\Repos\ChapterRepo;
9 -use BookStack\Repos\PageRepo;
10 use Illuminate\Http\Response; 7 use Illuminate\Http\Response;
11 use Views; 8 use Views;
12 9
13 class BookController extends Controller 10 class BookController extends Controller
14 { 11 {
15 12
16 - protected $bookRepo; 13 + protected $entityRepo;
17 - protected $pageRepo;
18 - protected $chapterRepo;
19 protected $userRepo; 14 protected $userRepo;
20 15
21 /** 16 /**
22 * BookController constructor. 17 * BookController constructor.
23 - * @param BookRepo $bookRepo 18 + * @param EntityRepo $entityRepo
24 - * @param PageRepo $pageRepo
25 - * @param ChapterRepo $chapterRepo
26 * @param UserRepo $userRepo 19 * @param UserRepo $userRepo
27 */ 20 */
28 - public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo, UserRepo $userRepo) 21 + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
29 { 22 {
30 - $this->bookRepo = $bookRepo; 23 + $this->entityRepo = $entityRepo;
31 - $this->pageRepo = $pageRepo;
32 - $this->chapterRepo = $chapterRepo;
33 $this->userRepo = $userRepo; 24 $this->userRepo = $userRepo;
34 parent::__construct(); 25 parent::__construct();
35 } 26 }
...@@ -40,9 +31,9 @@ class BookController extends Controller ...@@ -40,9 +31,9 @@ class BookController extends Controller
40 */ 31 */
41 public function index() 32 public function index()
42 { 33 {
43 - $books = $this->bookRepo->getAllPaginated(10); 34 + $books = $this->entityRepo->getAllPaginated('book', 10);
44 - $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(4, 0) : false; 35 + $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
45 - $popular = $this->bookRepo->getPopular(4, 0); 36 + $popular = $this->entityRepo->getPopular('book', 4, 0);
46 $this->setPageTitle('Books'); 37 $this->setPageTitle('Books');
47 return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]); 38 return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]);
48 } 39 }
...@@ -71,7 +62,7 @@ class BookController extends Controller ...@@ -71,7 +62,7 @@ class BookController extends Controller
71 'name' => 'required|string|max:255', 62 'name' => 'required|string|max:255',
72 'description' => 'string|max:1000' 63 'description' => 'string|max:1000'
73 ]); 64 ]);
74 - $book = $this->bookRepo->createFromInput($request->all()); 65 + $book = $this->entityRepo->createFromInput('book', $request->all());
75 Activity::add($book, 'book_create', $book->id); 66 Activity::add($book, 'book_create', $book->id);
76 return redirect($book->getUrl()); 67 return redirect($book->getUrl());
77 } 68 }
...@@ -83,9 +74,9 @@ class BookController extends Controller ...@@ -83,9 +74,9 @@ class BookController extends Controller
83 */ 74 */
84 public function show($slug) 75 public function show($slug)
85 { 76 {
86 - $book = $this->bookRepo->getBySlug($slug); 77 + $book = $this->entityRepo->getBySlug('book', $slug);
87 $this->checkOwnablePermission('book-view', $book); 78 $this->checkOwnablePermission('book-view', $book);
88 - $bookChildren = $this->bookRepo->getChildren($book); 79 + $bookChildren = $this->entityRepo->getBookChildren($book);
89 Views::add($book); 80 Views::add($book);
90 $this->setPageTitle($book->getShortName()); 81 $this->setPageTitle($book->getShortName());
91 return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]); 82 return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
...@@ -98,7 +89,7 @@ class BookController extends Controller ...@@ -98,7 +89,7 @@ class BookController extends Controller
98 */ 89 */
99 public function edit($slug) 90 public function edit($slug)
100 { 91 {
101 - $book = $this->bookRepo->getBySlug($slug); 92 + $book = $this->entityRepo->getBySlug('book', $slug);
102 $this->checkOwnablePermission('book-update', $book); 93 $this->checkOwnablePermission('book-update', $book);
103 $this->setPageTitle(trans('entities.books_edit_named',['bookName'=>$book->getShortName()])); 94 $this->setPageTitle(trans('entities.books_edit_named',['bookName'=>$book->getShortName()]));
104 return view('books/edit', ['book' => $book, 'current' => $book]); 95 return view('books/edit', ['book' => $book, 'current' => $book]);
...@@ -112,13 +103,13 @@ class BookController extends Controller ...@@ -112,13 +103,13 @@ class BookController extends Controller
112 */ 103 */
113 public function update(Request $request, $slug) 104 public function update(Request $request, $slug)
114 { 105 {
115 - $book = $this->bookRepo->getBySlug($slug); 106 + $book = $this->entityRepo->getBySlug('book', $slug);
116 $this->checkOwnablePermission('book-update', $book); 107 $this->checkOwnablePermission('book-update', $book);
117 $this->validate($request, [ 108 $this->validate($request, [
118 'name' => 'required|string|max:255', 109 'name' => 'required|string|max:255',
119 'description' => 'string|max:1000' 110 'description' => 'string|max:1000'
120 ]); 111 ]);
121 - $book = $this->bookRepo->updateFromInput($book, $request->all()); 112 + $book = $this->entityRepo->updateFromInput('book', $book, $request->all());
122 Activity::add($book, 'book_update', $book->id); 113 Activity::add($book, 'book_update', $book->id);
123 return redirect($book->getUrl()); 114 return redirect($book->getUrl());
124 } 115 }
...@@ -130,7 +121,7 @@ class BookController extends Controller ...@@ -130,7 +121,7 @@ class BookController extends Controller
130 */ 121 */
131 public function showDelete($bookSlug) 122 public function showDelete($bookSlug)
132 { 123 {
133 - $book = $this->bookRepo->getBySlug($bookSlug); 124 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
134 $this->checkOwnablePermission('book-delete', $book); 125 $this->checkOwnablePermission('book-delete', $book);
135 $this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()])); 126 $this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()]));
136 return view('books/delete', ['book' => $book, 'current' => $book]); 127 return view('books/delete', ['book' => $book, 'current' => $book]);
...@@ -143,10 +134,10 @@ class BookController extends Controller ...@@ -143,10 +134,10 @@ class BookController extends Controller
143 */ 134 */
144 public function sort($bookSlug) 135 public function sort($bookSlug)
145 { 136 {
146 - $book = $this->bookRepo->getBySlug($bookSlug); 137 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
147 $this->checkOwnablePermission('book-update', $book); 138 $this->checkOwnablePermission('book-update', $book);
148 - $bookChildren = $this->bookRepo->getChildren($book, true); 139 + $bookChildren = $this->entityRepo->getBookChildren($book, true);
149 - $books = $this->bookRepo->getAll(false); 140 + $books = $this->entityRepo->getAll('book', false);
150 $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()])); 141 $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
151 return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]); 142 return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
152 } 143 }
...@@ -159,8 +150,8 @@ class BookController extends Controller ...@@ -159,8 +150,8 @@ class BookController extends Controller
159 */ 150 */
160 public function getSortItem($bookSlug) 151 public function getSortItem($bookSlug)
161 { 152 {
162 - $book = $this->bookRepo->getBySlug($bookSlug); 153 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
163 - $bookChildren = $this->bookRepo->getChildren($book); 154 + $bookChildren = $this->entityRepo->getBookChildren($book);
164 return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); 155 return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
165 } 156 }
166 157
...@@ -172,7 +163,7 @@ class BookController extends Controller ...@@ -172,7 +163,7 @@ class BookController extends Controller
172 */ 163 */
173 public function saveSort($bookSlug, Request $request) 164 public function saveSort($bookSlug, Request $request)
174 { 165 {
175 - $book = $this->bookRepo->getBySlug($bookSlug); 166 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
176 $this->checkOwnablePermission('book-update', $book); 167 $this->checkOwnablePermission('book-update', $book);
177 168
178 // Return if no map sent 169 // Return if no map sent
...@@ -191,13 +182,13 @@ class BookController extends Controller ...@@ -191,13 +182,13 @@ class BookController extends Controller
191 $priority = $bookChild->sort; 182 $priority = $bookChild->sort;
192 $id = intval($bookChild->id); 183 $id = intval($bookChild->id);
193 $isPage = $bookChild->type == 'page'; 184 $isPage = $bookChild->type == 'page';
194 - $bookId = $this->bookRepo->exists($bookChild->book) ? intval($bookChild->book) : $defaultBookId; 185 + $bookId = $this->entityRepo->exists('book', $bookChild->book) ? intval($bookChild->book) : $defaultBookId;
195 $chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter); 186 $chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter);
196 - $model = $isPage ? $this->pageRepo->getById($id) : $this->chapterRepo->getById($id); 187 + $model = $this->entityRepo->getById($isPage?'page':'chapter', $id);
197 188
198 // Update models only if there's a change in parent chain or ordering. 189 // Update models only if there's a change in parent chain or ordering.
199 if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) { 190 if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) {
200 - $isPage ? $this->pageRepo->changeBook($bookId, $model) : $this->chapterRepo->changeBook($bookId, $model); 191 + $this->entityRepo->changeBook($isPage?'page':'chapter', $bookId, $model);
201 $model->priority = $priority; 192 $model->priority = $priority;
202 if ($isPage) $model->chapter_id = $chapterId; 193 if ($isPage) $model->chapter_id = $chapterId;
203 $model->save(); 194 $model->save();
...@@ -212,12 +203,12 @@ class BookController extends Controller ...@@ -212,12 +203,12 @@ class BookController extends Controller
212 203
213 // Add activity for books 204 // Add activity for books
214 foreach ($sortedBooks as $bookId) { 205 foreach ($sortedBooks as $bookId) {
215 - $updatedBook = $this->bookRepo->getById($bookId); 206 + $updatedBook = $this->entityRepo->getById('book', $bookId);
216 Activity::add($updatedBook, 'book_sort', $updatedBook->id); 207 Activity::add($updatedBook, 'book_sort', $updatedBook->id);
217 } 208 }
218 209
219 // Update permissions on changed models 210 // Update permissions on changed models
220 - $this->bookRepo->buildJointPermissions($updatedModels); 211 + $this->entityRepo->buildJointPermissions($updatedModels);
221 212
222 return redirect($book->getUrl()); 213 return redirect($book->getUrl());
223 } 214 }
...@@ -229,11 +220,10 @@ class BookController extends Controller ...@@ -229,11 +220,10 @@ class BookController extends Controller
229 */ 220 */
230 public function destroy($bookSlug) 221 public function destroy($bookSlug)
231 { 222 {
232 - $book = $this->bookRepo->getBySlug($bookSlug); 223 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
233 $this->checkOwnablePermission('book-delete', $book); 224 $this->checkOwnablePermission('book-delete', $book);
234 Activity::addMessage('book_delete', 0, $book->name); 225 Activity::addMessage('book_delete', 0, $book->name);
235 - Activity::removeEntity($book); 226 + $this->entityRepo->destroyBook($book);
236 - $this->bookRepo->destroy($book);
237 return redirect('/books'); 227 return redirect('/books');
238 } 228 }
239 229
...@@ -244,7 +234,7 @@ class BookController extends Controller ...@@ -244,7 +234,7 @@ class BookController extends Controller
244 */ 234 */
245 public function showRestrict($bookSlug) 235 public function showRestrict($bookSlug)
246 { 236 {
247 - $book = $this->bookRepo->getBySlug($bookSlug); 237 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
248 $this->checkOwnablePermission('restrictions-manage', $book); 238 $this->checkOwnablePermission('restrictions-manage', $book);
249 $roles = $this->userRepo->getRestrictableRoles(); 239 $roles = $this->userRepo->getRestrictableRoles();
250 return view('books/restrictions', [ 240 return view('books/restrictions', [
...@@ -262,9 +252,9 @@ class BookController extends Controller ...@@ -262,9 +252,9 @@ class BookController extends Controller
262 */ 252 */
263 public function restrict($bookSlug, Request $request) 253 public function restrict($bookSlug, Request $request)
264 { 254 {
265 - $book = $this->bookRepo->getBySlug($bookSlug); 255 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
266 $this->checkOwnablePermission('restrictions-manage', $book); 256 $this->checkOwnablePermission('restrictions-manage', $book);
267 - $this->bookRepo->updateEntityPermissionsFromRequest($request, $book); 257 + $this->entityRepo->updateEntityPermissionsFromRequest($request, $book);
268 session()->flash('success', trans('entities.books_permissions_updated')); 258 session()->flash('success', trans('entities.books_permissions_updated'));
269 return redirect($book->getUrl()); 259 return redirect($book->getUrl());
270 } 260 }
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use Activity; 3 use Activity;
4 +use BookStack\Repos\EntityRepo;
4 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 -use BookStack\Repos\BookRepo;
7 -use BookStack\Repos\ChapterRepo;
8 use Illuminate\Http\Response; 7 use Illuminate\Http\Response;
9 use Views; 8 use Views;
10 9
11 class ChapterController extends Controller 10 class ChapterController extends Controller
12 { 11 {
13 12
14 - protected $bookRepo;
15 - protected $chapterRepo;
16 protected $userRepo; 13 protected $userRepo;
14 + protected $entityRepo;
17 15
18 /** 16 /**
19 * ChapterController constructor. 17 * ChapterController constructor.
20 - * @param BookRepo $bookRepo 18 + * @param EntityRepo $entityRepo
21 - * @param ChapterRepo $chapterRepo
22 * @param UserRepo $userRepo 19 * @param UserRepo $userRepo
23 */ 20 */
24 - public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo, UserRepo $userRepo) 21 + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
25 { 22 {
26 - $this->bookRepo = $bookRepo; 23 + $this->entityRepo = $entityRepo;
27 - $this->chapterRepo = $chapterRepo;
28 $this->userRepo = $userRepo; 24 $this->userRepo = $userRepo;
29 parent::__construct(); 25 parent::__construct();
30 } 26 }
...@@ -36,7 +32,7 @@ class ChapterController extends Controller ...@@ -36,7 +32,7 @@ class ChapterController extends Controller
36 */ 32 */
37 public function create($bookSlug) 33 public function create($bookSlug)
38 { 34 {
39 - $book = $this->bookRepo->getBySlug($bookSlug); 35 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
40 $this->checkOwnablePermission('chapter-create', $book); 36 $this->checkOwnablePermission('chapter-create', $book);
41 $this->setPageTitle(trans('entities.chapters_create')); 37 $this->setPageTitle(trans('entities.chapters_create'));
42 return view('chapters/create', ['book' => $book, 'current' => $book]); 38 return view('chapters/create', ['book' => $book, 'current' => $book]);
...@@ -54,12 +50,12 @@ class ChapterController extends Controller ...@@ -54,12 +50,12 @@ class ChapterController extends Controller
54 'name' => 'required|string|max:255' 50 'name' => 'required|string|max:255'
55 ]); 51 ]);
56 52
57 - $book = $this->bookRepo->getBySlug($bookSlug); 53 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
58 $this->checkOwnablePermission('chapter-create', $book); 54 $this->checkOwnablePermission('chapter-create', $book);
59 55
60 $input = $request->all(); 56 $input = $request->all();
61 - $input['priority'] = $this->bookRepo->getNewPriority($book); 57 + $input['priority'] = $this->entityRepo->getNewBookPriority($book);
62 - $chapter = $this->chapterRepo->createFromInput($input, $book); 58 + $chapter = $this->entityRepo->createFromInput('chapter', $input, $book);
63 Activity::add($chapter, 'chapter_create', $book->id); 59 Activity::add($chapter, 'chapter_create', $book->id);
64 return redirect($chapter->getUrl()); 60 return redirect($chapter->getUrl());
65 } 61 }
...@@ -72,15 +68,14 @@ class ChapterController extends Controller ...@@ -72,15 +68,14 @@ class ChapterController extends Controller
72 */ 68 */
73 public function show($bookSlug, $chapterSlug) 69 public function show($bookSlug, $chapterSlug)
74 { 70 {
75 - $book = $this->bookRepo->getBySlug($bookSlug); 71 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
76 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
77 $this->checkOwnablePermission('chapter-view', $chapter); 72 $this->checkOwnablePermission('chapter-view', $chapter);
78 - $sidebarTree = $this->bookRepo->getChildren($book); 73 + $sidebarTree = $this->entityRepo->getBookChildren($chapter->book);
79 Views::add($chapter); 74 Views::add($chapter);
80 $this->setPageTitle($chapter->getShortName()); 75 $this->setPageTitle($chapter->getShortName());
81 - $pages = $this->chapterRepo->getChildren($chapter); 76 + $pages = $this->entityRepo->getChapterChildren($chapter);
82 return view('chapters/show', [ 77 return view('chapters/show', [
83 - 'book' => $book, 78 + 'book' => $chapter->book,
84 'chapter' => $chapter, 79 'chapter' => $chapter,
85 'current' => $chapter, 80 'current' => $chapter,
86 'sidebarTree' => $sidebarTree, 81 'sidebarTree' => $sidebarTree,
...@@ -96,11 +91,10 @@ class ChapterController extends Controller ...@@ -96,11 +91,10 @@ class ChapterController extends Controller
96 */ 91 */
97 public function edit($bookSlug, $chapterSlug) 92 public function edit($bookSlug, $chapterSlug)
98 { 93 {
99 - $book = $this->bookRepo->getBySlug($bookSlug); 94 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
100 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
101 $this->checkOwnablePermission('chapter-update', $chapter); 95 $this->checkOwnablePermission('chapter-update', $chapter);
102 $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()])); 96 $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
103 - return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); 97 + return view('chapters/edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
104 } 98 }
105 99
106 /** 100 /**
...@@ -112,16 +106,15 @@ class ChapterController extends Controller ...@@ -112,16 +106,15 @@ class ChapterController extends Controller
112 */ 106 */
113 public function update(Request $request, $bookSlug, $chapterSlug) 107 public function update(Request $request, $bookSlug, $chapterSlug)
114 { 108 {
115 - $book = $this->bookRepo->getBySlug($bookSlug); 109 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
116 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
117 $this->checkOwnablePermission('chapter-update', $chapter); 110 $this->checkOwnablePermission('chapter-update', $chapter);
118 if ($chapter->name !== $request->get('name')) { 111 if ($chapter->name !== $request->get('name')) {
119 - $chapter->slug = $this->chapterRepo->findSuitableSlug($request->get('name'), $book->id, $chapter->id); 112 + $chapter->slug = $this->entityRepo->findSuitableSlug('chapter', $request->get('name'), $chapter->id, $chapter->book->id);
120 } 113 }
121 $chapter->fill($request->all()); 114 $chapter->fill($request->all());
122 $chapter->updated_by = user()->id; 115 $chapter->updated_by = user()->id;
123 $chapter->save(); 116 $chapter->save();
124 - Activity::add($chapter, 'chapter_update', $book->id); 117 + Activity::add($chapter, 'chapter_update', $chapter->book->id);
125 return redirect($chapter->getUrl()); 118 return redirect($chapter->getUrl());
126 } 119 }
127 120
...@@ -133,11 +126,10 @@ class ChapterController extends Controller ...@@ -133,11 +126,10 @@ class ChapterController extends Controller
133 */ 126 */
134 public function showDelete($bookSlug, $chapterSlug) 127 public function showDelete($bookSlug, $chapterSlug)
135 { 128 {
136 - $book = $this->bookRepo->getBySlug($bookSlug); 129 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
137 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
138 $this->checkOwnablePermission('chapter-delete', $chapter); 130 $this->checkOwnablePermission('chapter-delete', $chapter);
139 $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()])); 131 $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
140 - return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); 132 + return view('chapters/delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
141 } 133 }
142 134
143 /** 135 /**
...@@ -148,11 +140,11 @@ class ChapterController extends Controller ...@@ -148,11 +140,11 @@ class ChapterController extends Controller
148 */ 140 */
149 public function destroy($bookSlug, $chapterSlug) 141 public function destroy($bookSlug, $chapterSlug)
150 { 142 {
151 - $book = $this->bookRepo->getBySlug($bookSlug); 143 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
152 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 144 + $book = $chapter->book;
153 $this->checkOwnablePermission('chapter-delete', $chapter); 145 $this->checkOwnablePermission('chapter-delete', $chapter);
154 Activity::addMessage('chapter_delete', $book->id, $chapter->name); 146 Activity::addMessage('chapter_delete', $book->id, $chapter->name);
155 - $this->chapterRepo->destroy($chapter); 147 + $this->entityRepo->destroyChapter($chapter);
156 return redirect($book->getUrl()); 148 return redirect($book->getUrl());
157 } 149 }
158 150
...@@ -164,13 +156,12 @@ class ChapterController extends Controller ...@@ -164,13 +156,12 @@ class ChapterController extends Controller
164 * @throws \BookStack\Exceptions\NotFoundException 156 * @throws \BookStack\Exceptions\NotFoundException
165 */ 157 */
166 public function showMove($bookSlug, $chapterSlug) { 158 public function showMove($bookSlug, $chapterSlug) {
167 - $book = $this->bookRepo->getBySlug($bookSlug); 159 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
168 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
169 $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()])); 160 $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
170 $this->checkOwnablePermission('chapter-update', $chapter); 161 $this->checkOwnablePermission('chapter-update', $chapter);
171 return view('chapters/move', [ 162 return view('chapters/move', [
172 'chapter' => $chapter, 163 'chapter' => $chapter,
173 - 'book' => $book 164 + 'book' => $chapter->book
174 ]); 165 ]);
175 } 166 }
176 167
...@@ -183,8 +174,7 @@ class ChapterController extends Controller ...@@ -183,8 +174,7 @@ class ChapterController extends Controller
183 * @throws \BookStack\Exceptions\NotFoundException 174 * @throws \BookStack\Exceptions\NotFoundException
184 */ 175 */
185 public function move($bookSlug, $chapterSlug, Request $request) { 176 public function move($bookSlug, $chapterSlug, Request $request) {
186 - $book = $this->bookRepo->getBySlug($bookSlug); 177 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
187 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
188 $this->checkOwnablePermission('chapter-update', $chapter); 178 $this->checkOwnablePermission('chapter-update', $chapter);
189 179
190 $entitySelection = $request->get('entity_selection', null); 180 $entitySelection = $request->get('entity_selection', null);
...@@ -199,7 +189,7 @@ class ChapterController extends Controller ...@@ -199,7 +189,7 @@ class ChapterController extends Controller
199 $parent = false; 189 $parent = false;
200 190
201 if ($entityType == 'book') { 191 if ($entityType == 'book') {
202 - $parent = $this->bookRepo->getById($entityId); 192 + $parent = $this->entityRepo->getById('book', $entityId);
203 } 193 }
204 194
205 if ($parent === false || $parent === null) { 195 if ($parent === false || $parent === null) {
...@@ -207,7 +197,7 @@ class ChapterController extends Controller ...@@ -207,7 +197,7 @@ class ChapterController extends Controller
207 return redirect()->back(); 197 return redirect()->back();
208 } 198 }
209 199
210 - $this->chapterRepo->changeBook($parent->id, $chapter, true); 200 + $this->entityRepo->changeBook('chapter', $parent->id, $chapter, true);
211 Activity::add($chapter, 'chapter_move', $chapter->book->id); 201 Activity::add($chapter, 'chapter_move', $chapter->book->id);
212 session()->flash('success', trans('entities.chapter_move_success', ['bookName' => $parent->name])); 202 session()->flash('success', trans('entities.chapter_move_success', ['bookName' => $parent->name]));
213 203
...@@ -222,8 +212,7 @@ class ChapterController extends Controller ...@@ -222,8 +212,7 @@ class ChapterController extends Controller
222 */ 212 */
223 public function showRestrict($bookSlug, $chapterSlug) 213 public function showRestrict($bookSlug, $chapterSlug)
224 { 214 {
225 - $book = $this->bookRepo->getBySlug($bookSlug); 215 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
226 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
227 $this->checkOwnablePermission('restrictions-manage', $chapter); 216 $this->checkOwnablePermission('restrictions-manage', $chapter);
228 $roles = $this->userRepo->getRestrictableRoles(); 217 $roles = $this->userRepo->getRestrictableRoles();
229 return view('chapters/restrictions', [ 218 return view('chapters/restrictions', [
...@@ -241,10 +230,9 @@ class ChapterController extends Controller ...@@ -241,10 +230,9 @@ class ChapterController extends Controller
241 */ 230 */
242 public function restrict($bookSlug, $chapterSlug, Request $request) 231 public function restrict($bookSlug, $chapterSlug, Request $request)
243 { 232 {
244 - $book = $this->bookRepo->getBySlug($bookSlug); 233 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
245 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
246 $this->checkOwnablePermission('restrictions-manage', $chapter); 234 $this->checkOwnablePermission('restrictions-manage', $chapter);
247 - $this->chapterRepo->updateEntityPermissionsFromRequest($request, $chapter); 235 + $this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter);
248 session()->flash('success', trans('entities.chapters_permissions_success')); 236 session()->flash('success', trans('entities.chapters_permissions_success'));
249 return redirect($chapter->getUrl()); 237 return redirect($chapter->getUrl());
250 } 238 }
......
...@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers; ...@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers;
5 use Activity; 5 use Activity;
6 use BookStack\Repos\EntityRepo; 6 use BookStack\Repos\EntityRepo;
7 use BookStack\Http\Requests; 7 use BookStack\Http\Requests;
8 +use Illuminate\Http\Response;
8 use Views; 9 use Views;
9 10
10 class HomeController extends Controller 11 class HomeController extends Controller
...@@ -31,9 +32,9 @@ class HomeController extends Controller ...@@ -31,9 +32,9 @@ class HomeController extends Controller
31 $activity = Activity::latest(10); 32 $activity = Activity::latest(10);
32 $draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : []; 33 $draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : [];
33 $recentFactor = count($draftPages) > 0 ? 0.5 : 1; 34 $recentFactor = count($draftPages) > 0 ? 0.5 : 1;
34 - $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreatedBooks(10*$recentFactor); 35 + $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 10*$recentFactor);
35 - $recentlyCreatedPages = $this->entityRepo->getRecentlyCreatedPages(5); 36 + $recentlyCreatedPages = $this->entityRepo->getRecentlyCreated('page', 5);
36 - $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdatedPages(5); 37 + $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 5);
37 return view('home', [ 38 return view('home', [
38 'activity' => $activity, 39 'activity' => $activity,
39 'recents' => $recents, 40 'recents' => $recents,
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use BookStack\Exceptions\ImageUploadException; 3 use BookStack\Exceptions\ImageUploadException;
4 +use BookStack\Repos\EntityRepo;
4 use BookStack\Repos\ImageRepo; 5 use BookStack\Repos\ImageRepo;
5 use Illuminate\Filesystem\Filesystem as File; 6 use Illuminate\Filesystem\Filesystem as File;
6 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
...@@ -150,12 +151,12 @@ class ImageController extends Controller ...@@ -150,12 +151,12 @@ class ImageController extends Controller
150 151
151 /** 152 /**
152 * Deletes an image and all thumbnail/image files 153 * Deletes an image and all thumbnail/image files
153 - * @param PageRepo $pageRepo 154 + * @param EntityRepo $entityRepo
154 * @param Request $request 155 * @param Request $request
155 * @param int $id 156 * @param int $id
156 * @return \Illuminate\Http\JsonResponse 157 * @return \Illuminate\Http\JsonResponse
157 */ 158 */
158 - public function destroy(PageRepo $pageRepo, Request $request, $id) 159 + public function destroy(EntityRepo $entityRepo, Request $request, $id)
159 { 160 {
160 $image = $this->imageRepo->getById($id); 161 $image = $this->imageRepo->getById($id);
161 $this->checkOwnablePermission('image-delete', $image); 162 $this->checkOwnablePermission('image-delete', $image);
...@@ -163,7 +164,7 @@ class ImageController extends Controller ...@@ -163,7 +164,7 @@ class ImageController extends Controller
163 // Check if this image is used on any pages 164 // Check if this image is used on any pages
164 $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true); 165 $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true);
165 if (!$isForced) { 166 if (!$isForced) {
166 - $pageSearch = $pageRepo->searchForImage($image->url); 167 + $pageSearch = $entityRepo->searchForImage($image->url);
167 if ($pageSearch !== false) { 168 if ($pageSearch !== false) {
168 return response()->json($pageSearch, 400); 169 return response()->json($pageSearch, 400);
169 } 170 }
......
...@@ -2,41 +2,31 @@ ...@@ -2,41 +2,31 @@
2 2
3 use Activity; 3 use Activity;
4 use BookStack\Exceptions\NotFoundException; 4 use BookStack\Exceptions\NotFoundException;
5 +use BookStack\Repos\EntityRepo;
5 use BookStack\Repos\UserRepo; 6 use BookStack\Repos\UserRepo;
6 use BookStack\Services\ExportService; 7 use BookStack\Services\ExportService;
7 use Carbon\Carbon; 8 use Carbon\Carbon;
8 use Illuminate\Http\Request; 9 use Illuminate\Http\Request;
9 -use BookStack\Http\Requests;
10 -use BookStack\Repos\BookRepo;
11 -use BookStack\Repos\ChapterRepo;
12 -use BookStack\Repos\PageRepo;
13 use Illuminate\Http\Response; 10 use Illuminate\Http\Response;
14 -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
15 use Views; 11 use Views;
16 use GatherContent\Htmldiff\Htmldiff; 12 use GatherContent\Htmldiff\Htmldiff;
17 13
18 class PageController extends Controller 14 class PageController extends Controller
19 { 15 {
20 16
21 - protected $pageRepo; 17 + protected $entityRepo;
22 - protected $bookRepo;
23 - protected $chapterRepo;
24 protected $exportService; 18 protected $exportService;
25 protected $userRepo; 19 protected $userRepo;
26 20
27 /** 21 /**
28 * PageController constructor. 22 * PageController constructor.
29 - * @param PageRepo $pageRepo 23 + * @param EntityRepo $entityRepo
30 - * @param BookRepo $bookRepo
31 - * @param ChapterRepo $chapterRepo
32 * @param ExportService $exportService 24 * @param ExportService $exportService
33 * @param UserRepo $userRepo 25 * @param UserRepo $userRepo
34 */ 26 */
35 - public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService, UserRepo $userRepo) 27 + public function __construct(EntityRepo $entityRepo, ExportService $exportService, UserRepo $userRepo)
36 { 28 {
37 - $this->pageRepo = $pageRepo; 29 + $this->entityRepo = $entityRepo;
38 - $this->bookRepo = $bookRepo;
39 - $this->chapterRepo = $chapterRepo;
40 $this->exportService = $exportService; 30 $this->exportService = $exportService;
41 $this->userRepo = $userRepo; 31 $this->userRepo = $userRepo;
42 parent::__construct(); 32 parent::__construct();
...@@ -51,14 +41,14 @@ class PageController extends Controller ...@@ -51,14 +41,14 @@ class PageController extends Controller
51 */ 41 */
52 public function create($bookSlug, $chapterSlug = null) 42 public function create($bookSlug, $chapterSlug = null)
53 { 43 {
54 - $book = $this->bookRepo->getBySlug($bookSlug); 44 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
55 - $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null; 45 + $chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
56 $parent = $chapter ? $chapter : $book; 46 $parent = $chapter ? $chapter : $book;
57 $this->checkOwnablePermission('page-create', $parent); 47 $this->checkOwnablePermission('page-create', $parent);
58 48
59 // Redirect to draft edit screen if signed in 49 // Redirect to draft edit screen if signed in
60 if ($this->signedIn) { 50 if ($this->signedIn) {
61 - $draft = $this->pageRepo->getDraftPage($book, $chapter); 51 + $draft = $this->entityRepo->getDraftPage($book, $chapter);
62 return redirect($draft->getUrl()); 52 return redirect($draft->getUrl());
63 } 53 }
64 54
...@@ -81,13 +71,13 @@ class PageController extends Controller ...@@ -81,13 +71,13 @@ class PageController extends Controller
81 'name' => 'required|string|max:255' 71 'name' => 'required|string|max:255'
82 ]); 72 ]);
83 73
84 - $book = $this->bookRepo->getBySlug($bookSlug); 74 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
85 - $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null; 75 + $chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
86 $parent = $chapter ? $chapter : $book; 76 $parent = $chapter ? $chapter : $book;
87 $this->checkOwnablePermission('page-create', $parent); 77 $this->checkOwnablePermission('page-create', $parent);
88 78
89 - $page = $this->pageRepo->getDraftPage($book, $chapter); 79 + $page = $this->entityRepo->getDraftPage($book, $chapter);
90 - $this->pageRepo->publishDraft($page, [ 80 + $this->entityRepo->publishPageDraft($page, [
91 'name' => $request->get('name'), 81 'name' => $request->get('name'),
92 'html' => '' 82 'html' => ''
93 ]); 83 ]);
...@@ -102,15 +92,14 @@ class PageController extends Controller ...@@ -102,15 +92,14 @@ class PageController extends Controller
102 */ 92 */
103 public function editDraft($bookSlug, $pageId) 93 public function editDraft($bookSlug, $pageId)
104 { 94 {
105 - $book = $this->bookRepo->getBySlug($bookSlug); 95 + $draft = $this->entityRepo->getById('page', $pageId, true);
106 - $draft = $this->pageRepo->getById($pageId, true); 96 + $this->checkOwnablePermission('page-create', $draft->book);
107 - $this->checkOwnablePermission('page-create', $book);
108 $this->setPageTitle(trans('entities.pages_edit_draft')); 97 $this->setPageTitle(trans('entities.pages_edit_draft'));
109 98
110 $draftsEnabled = $this->signedIn; 99 $draftsEnabled = $this->signedIn;
111 return view('pages/edit', [ 100 return view('pages/edit', [
112 'page' => $draft, 101 'page' => $draft,
113 - 'book' => $book, 102 + 'book' => $draft->book,
114 'isDraft' => true, 103 'isDraft' => true,
115 'draftsEnabled' => $draftsEnabled 104 'draftsEnabled' => $draftsEnabled
116 ]); 105 ]);
...@@ -130,21 +119,21 @@ class PageController extends Controller ...@@ -130,21 +119,21 @@ class PageController extends Controller
130 ]); 119 ]);
131 120
132 $input = $request->all(); 121 $input = $request->all();
133 - $book = $this->bookRepo->getBySlug($bookSlug); 122 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
134 123
135 - $draftPage = $this->pageRepo->getById($pageId, true); 124 + $draftPage = $this->entityRepo->getById('page', $pageId, true);
136 125
137 $chapterId = intval($draftPage->chapter_id); 126 $chapterId = intval($draftPage->chapter_id);
138 - $parent = $chapterId !== 0 ? $this->chapterRepo->getById($chapterId) : $book; 127 + $parent = $chapterId !== 0 ? $this->entityRepo->getById('chapter', $chapterId) : $book;
139 $this->checkOwnablePermission('page-create', $parent); 128 $this->checkOwnablePermission('page-create', $parent);
140 129
141 if ($parent->isA('chapter')) { 130 if ($parent->isA('chapter')) {
142 - $input['priority'] = $this->chapterRepo->getNewPriority($parent); 131 + $input['priority'] = $this->entityRepo->getNewChapterPriority($parent);
143 } else { 132 } else {
144 - $input['priority'] = $this->bookRepo->getNewPriority($parent); 133 + $input['priority'] = $this->entityRepo->getNewBookPriority($parent);
145 } 134 }
146 135
147 - $page = $this->pageRepo->publishDraft($draftPage, $input); 136 + $page = $this->entityRepo->publishPageDraft($draftPage, $input);
148 137
149 Activity::add($page, 'page_create', $book->id); 138 Activity::add($page, 'page_create', $book->id);
150 return redirect($page->getUrl()); 139 return redirect($page->getUrl());
...@@ -152,32 +141,29 @@ class PageController extends Controller ...@@ -152,32 +141,29 @@ class PageController extends Controller
152 141
153 /** 142 /**
154 * Display the specified page. 143 * Display the specified page.
155 - * If the page is not found via the slug the 144 + * If the page is not found via the slug the revisions are searched for a match.
156 - * revisions are searched for a match.
157 * @param string $bookSlug 145 * @param string $bookSlug
158 * @param string $pageSlug 146 * @param string $pageSlug
159 * @return Response 147 * @return Response
160 */ 148 */
161 public function show($bookSlug, $pageSlug) 149 public function show($bookSlug, $pageSlug)
162 { 150 {
163 - $book = $this->bookRepo->getBySlug($bookSlug);
164 -
165 try { 151 try {
166 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 152 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
167 } catch (NotFoundException $e) { 153 } catch (NotFoundException $e) {
168 - $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); 154 + $page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
169 if ($page === null) abort(404); 155 if ($page === null) abort(404);
170 return redirect($page->getUrl()); 156 return redirect($page->getUrl());
171 } 157 }
172 158
173 $this->checkOwnablePermission('page-view', $page); 159 $this->checkOwnablePermission('page-view', $page);
174 160
175 - $sidebarTree = $this->bookRepo->getChildren($book); 161 + $sidebarTree = $this->entityRepo->getBookChildren($page->book);
176 - $pageNav = $this->pageRepo->getPageNav($page); 162 + $pageNav = $this->entityRepo->getPageNav($page);
177 163
178 Views::add($page); 164 Views::add($page);
179 $this->setPageTitle($page->getShortName()); 165 $this->setPageTitle($page->getShortName());
180 - return view('pages/show', ['page' => $page, 'book' => $book, 166 + return view('pages/show', ['page' => $page, 'book' => $page->book,
181 'current' => $page, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav]); 167 'current' => $page, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav]);
182 } 168 }
183 169
...@@ -188,7 +174,7 @@ class PageController extends Controller ...@@ -188,7 +174,7 @@ class PageController extends Controller
188 */ 174 */
189 public function getPageAjax($pageId) 175 public function getPageAjax($pageId)
190 { 176 {
191 - $page = $this->pageRepo->getById($pageId); 177 + $page = $this->entityRepo->getById('page', $pageId);
192 return response()->json($page); 178 return response()->json($page);
193 } 179 }
194 180
...@@ -200,26 +186,25 @@ class PageController extends Controller ...@@ -200,26 +186,25 @@ class PageController extends Controller
200 */ 186 */
201 public function edit($bookSlug, $pageSlug) 187 public function edit($bookSlug, $pageSlug)
202 { 188 {
203 - $book = $this->bookRepo->getBySlug($bookSlug); 189 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
204 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
205 $this->checkOwnablePermission('page-update', $page); 190 $this->checkOwnablePermission('page-update', $page);
206 $this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()])); 191 $this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()]));
207 $page->isDraft = false; 192 $page->isDraft = false;
208 193
209 // Check for active editing 194 // Check for active editing
210 $warnings = []; 195 $warnings = [];
211 - if ($this->pageRepo->isPageEditingActive($page, 60)) { 196 + if ($this->entityRepo->isPageEditingActive($page, 60)) {
212 - $warnings[] = $this->pageRepo->getPageEditingActiveMessage($page, 60); 197 + $warnings[] = $this->entityRepo->getPageEditingActiveMessage($page, 60);
213 } 198 }
214 199
215 // Check for a current draft version for this user 200 // Check for a current draft version for this user
216 - if ($this->pageRepo->hasUserGotPageDraft($page, $this->currentUser->id)) { 201 + if ($this->entityRepo->hasUserGotPageDraft($page, $this->currentUser->id)) {
217 - $draft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id); 202 + $draft = $this->entityRepo->getUserPageDraft($page, $this->currentUser->id);
218 $page->name = $draft->name; 203 $page->name = $draft->name;
219 $page->html = $draft->html; 204 $page->html = $draft->html;
220 $page->markdown = $draft->markdown; 205 $page->markdown = $draft->markdown;
221 $page->isDraft = true; 206 $page->isDraft = true;
222 - $warnings [] = $this->pageRepo->getUserPageDraftMessage($draft); 207 + $warnings [] = $this->entityRepo->getUserPageDraftMessage($draft);
223 } 208 }
224 209
225 if (count($warnings) > 0) session()->flash('warning', implode("\n", $warnings)); 210 if (count($warnings) > 0) session()->flash('warning', implode("\n", $warnings));
...@@ -227,7 +212,7 @@ class PageController extends Controller ...@@ -227,7 +212,7 @@ class PageController extends Controller
227 $draftsEnabled = $this->signedIn; 212 $draftsEnabled = $this->signedIn;
228 return view('pages/edit', [ 213 return view('pages/edit', [
229 'page' => $page, 214 'page' => $page,
230 - 'book' => $book, 215 + 'book' => $page->book,
231 'current' => $page, 216 'current' => $page,
232 'draftsEnabled' => $draftsEnabled 217 'draftsEnabled' => $draftsEnabled
233 ]); 218 ]);
...@@ -245,11 +230,10 @@ class PageController extends Controller ...@@ -245,11 +230,10 @@ class PageController extends Controller
245 $this->validate($request, [ 230 $this->validate($request, [
246 'name' => 'required|string|max:255' 231 'name' => 'required|string|max:255'
247 ]); 232 ]);
248 - $book = $this->bookRepo->getBySlug($bookSlug); 233 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
249 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
250 $this->checkOwnablePermission('page-update', $page); 234 $this->checkOwnablePermission('page-update', $page);
251 - $this->pageRepo->updatePage($page, $book->id, $request->all()); 235 + $this->entityRepo->updatePage($page, $page->book->id, $request->all());
252 - Activity::add($page, 'page_update', $book->id); 236 + Activity::add($page, 'page_update', $page->book->id);
253 return redirect($page->getUrl()); 237 return redirect($page->getUrl());
254 } 238 }
255 239
...@@ -261,7 +245,7 @@ class PageController extends Controller ...@@ -261,7 +245,7 @@ class PageController extends Controller
261 */ 245 */
262 public function saveDraft(Request $request, $pageId) 246 public function saveDraft(Request $request, $pageId)
263 { 247 {
264 - $page = $this->pageRepo->getById($pageId, true); 248 + $page = $this->entityRepo->getById('page', $pageId, true);
265 $this->checkOwnablePermission('page-update', $page); 249 $this->checkOwnablePermission('page-update', $page);
266 250
267 if (!$this->signedIn) { 251 if (!$this->signedIn) {
...@@ -271,11 +255,7 @@ class PageController extends Controller ...@@ -271,11 +255,7 @@ class PageController extends Controller
271 ], 500); 255 ], 500);
272 } 256 }
273 257
274 - if ($page->draft) { 258 + $draft = $this->entityRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
275 - $draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html', 'markdown']));
276 - } else {
277 - $draft = $this->pageRepo->saveUpdateDraft($page, $request->only(['name', 'html', 'markdown']));
278 - }
279 259
280 $updateTime = $draft->updated_at->timestamp; 260 $updateTime = $draft->updated_at->timestamp;
281 $utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset; 261 $utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset;
...@@ -294,7 +274,7 @@ class PageController extends Controller ...@@ -294,7 +274,7 @@ class PageController extends Controller
294 */ 274 */
295 public function redirectFromLink($pageId) 275 public function redirectFromLink($pageId)
296 { 276 {
297 - $page = $this->pageRepo->getById($pageId); 277 + $page = $this->entityRepo->getById('page', $pageId);
298 return redirect($page->getUrl()); 278 return redirect($page->getUrl());
299 } 279 }
300 280
...@@ -306,11 +286,10 @@ class PageController extends Controller ...@@ -306,11 +286,10 @@ class PageController extends Controller
306 */ 286 */
307 public function showDelete($bookSlug, $pageSlug) 287 public function showDelete($bookSlug, $pageSlug)
308 { 288 {
309 - $book = $this->bookRepo->getBySlug($bookSlug); 289 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
310 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
311 $this->checkOwnablePermission('page-delete', $page); 290 $this->checkOwnablePermission('page-delete', $page);
312 $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()])); 291 $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
313 - return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); 292 + return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
314 } 293 }
315 294
316 295
...@@ -323,11 +302,10 @@ class PageController extends Controller ...@@ -323,11 +302,10 @@ class PageController extends Controller
323 */ 302 */
324 public function showDeleteDraft($bookSlug, $pageId) 303 public function showDeleteDraft($bookSlug, $pageId)
325 { 304 {
326 - $book = $this->bookRepo->getBySlug($bookSlug); 305 + $page = $this->entityRepo->getById('page', $pageId, true);
327 - $page = $this->pageRepo->getById($pageId, true);
328 $this->checkOwnablePermission('page-update', $page); 306 $this->checkOwnablePermission('page-update', $page);
329 $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()])); 307 $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
330 - return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); 308 + return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
331 } 309 }
332 310
333 /** 311 /**
...@@ -339,12 +317,12 @@ class PageController extends Controller ...@@ -339,12 +317,12 @@ class PageController extends Controller
339 */ 317 */
340 public function destroy($bookSlug, $pageSlug) 318 public function destroy($bookSlug, $pageSlug)
341 { 319 {
342 - $book = $this->bookRepo->getBySlug($bookSlug); 320 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
343 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 321 + $book = $page->book;
344 $this->checkOwnablePermission('page-delete', $page); 322 $this->checkOwnablePermission('page-delete', $page);
345 Activity::addMessage('page_delete', $book->id, $page->name); 323 Activity::addMessage('page_delete', $book->id, $page->name);
346 session()->flash('success', trans('entities.pages_delete_success')); 324 session()->flash('success', trans('entities.pages_delete_success'));
347 - $this->pageRepo->destroy($page); 325 + $this->entityRepo->destroyPage($page);
348 return redirect($book->getUrl()); 326 return redirect($book->getUrl());
349 } 327 }
350 328
...@@ -357,11 +335,11 @@ class PageController extends Controller ...@@ -357,11 +335,11 @@ class PageController extends Controller
357 */ 335 */
358 public function destroyDraft($bookSlug, $pageId) 336 public function destroyDraft($bookSlug, $pageId)
359 { 337 {
360 - $book = $this->bookRepo->getBySlug($bookSlug); 338 + $page = $this->entityRepo->getById('page', $pageId, true);
361 - $page = $this->pageRepo->getById($pageId, true); 339 + $book = $page->book;
362 $this->checkOwnablePermission('page-update', $page); 340 $this->checkOwnablePermission('page-update', $page);
363 session()->flash('success', trans('entities.pages_delete_draft_success')); 341 session()->flash('success', trans('entities.pages_delete_draft_success'));
364 - $this->pageRepo->destroy($page); 342 + $this->entityRepo->destroyPage($page);
365 return redirect($book->getUrl()); 343 return redirect($book->getUrl());
366 } 344 }
367 345
...@@ -373,10 +351,9 @@ class PageController extends Controller ...@@ -373,10 +351,9 @@ class PageController extends Controller
373 */ 351 */
374 public function showRevisions($bookSlug, $pageSlug) 352 public function showRevisions($bookSlug, $pageSlug)
375 { 353 {
376 - $book = $this->bookRepo->getBySlug($bookSlug); 354 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
377 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
378 $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()])); 355 $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
379 - return view('pages/revisions', ['page' => $page, 'book' => $book, 'current' => $page]); 356 + return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
380 } 357 }
381 358
382 /** 359 /**
...@@ -388,16 +365,15 @@ class PageController extends Controller ...@@ -388,16 +365,15 @@ class PageController extends Controller
388 */ 365 */
389 public function showRevision($bookSlug, $pageSlug, $revisionId) 366 public function showRevision($bookSlug, $pageSlug, $revisionId)
390 { 367 {
391 - $book = $this->bookRepo->getBySlug($bookSlug); 368 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
392 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 369 + $revision = $this->entityRepo->getById('page_revision', $revisionId, false);
393 - $revision = $this->pageRepo->getRevisionById($revisionId);
394 370
395 $page->fill($revision->toArray()); 371 $page->fill($revision->toArray());
396 $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()])); 372 $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
397 373
398 return view('pages/revision', [ 374 return view('pages/revision', [
399 'page' => $page, 375 'page' => $page,
400 - 'book' => $book, 376 + 'book' => $page->book,
401 ]); 377 ]);
402 } 378 }
403 379
...@@ -410,9 +386,8 @@ class PageController extends Controller ...@@ -410,9 +386,8 @@ class PageController extends Controller
410 */ 386 */
411 public function showRevisionChanges($bookSlug, $pageSlug, $revisionId) 387 public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
412 { 388 {
413 - $book = $this->bookRepo->getBySlug($bookSlug); 389 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
414 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 390 + $revision = $this->entityRepo->getById('page_revision', $revisionId);
415 - $revision = $this->pageRepo->getRevisionById($revisionId);
416 391
417 $prev = $revision->getPrevious(); 392 $prev = $revision->getPrevious();
418 $prevContent = ($prev === null) ? '' : $prev->html; 393 $prevContent = ($prev === null) ? '' : $prev->html;
...@@ -423,7 +398,7 @@ class PageController extends Controller ...@@ -423,7 +398,7 @@ class PageController extends Controller
423 398
424 return view('pages/revision', [ 399 return view('pages/revision', [
425 'page' => $page, 400 'page' => $page,
426 - 'book' => $book, 401 + 'book' => $page->book,
427 'diff' => $diff, 402 'diff' => $diff,
428 ]); 403 ]);
429 } 404 }
...@@ -437,11 +412,10 @@ class PageController extends Controller ...@@ -437,11 +412,10 @@ class PageController extends Controller
437 */ 412 */
438 public function restoreRevision($bookSlug, $pageSlug, $revisionId) 413 public function restoreRevision($bookSlug, $pageSlug, $revisionId)
439 { 414 {
440 - $book = $this->bookRepo->getBySlug($bookSlug); 415 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
441 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
442 $this->checkOwnablePermission('page-update', $page); 416 $this->checkOwnablePermission('page-update', $page);
443 - $page = $this->pageRepo->restoreRevision($page, $book, $revisionId); 417 + $page = $this->entityRepo->restorePageRevision($page, $page->book, $revisionId);
444 - Activity::add($page, 'page_restore', $book->id); 418 + Activity::add($page, 'page_restore', $page->book->id);
445 return redirect($page->getUrl()); 419 return redirect($page->getUrl());
446 } 420 }
447 421
...@@ -454,8 +428,7 @@ class PageController extends Controller ...@@ -454,8 +428,7 @@ class PageController extends Controller
454 */ 428 */
455 public function exportPdf($bookSlug, $pageSlug) 429 public function exportPdf($bookSlug, $pageSlug)
456 { 430 {
457 - $book = $this->bookRepo->getBySlug($bookSlug); 431 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
458 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
459 $pdfContent = $this->exportService->pageToPdf($page); 432 $pdfContent = $this->exportService->pageToPdf($page);
460 return response()->make($pdfContent, 200, [ 433 return response()->make($pdfContent, 200, [
461 'Content-Type' => 'application/octet-stream', 434 'Content-Type' => 'application/octet-stream',
...@@ -471,8 +444,7 @@ class PageController extends Controller ...@@ -471,8 +444,7 @@ class PageController extends Controller
471 */ 444 */
472 public function exportHtml($bookSlug, $pageSlug) 445 public function exportHtml($bookSlug, $pageSlug)
473 { 446 {
474 - $book = $this->bookRepo->getBySlug($bookSlug); 447 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
475 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
476 $containedHtml = $this->exportService->pageToContainedHtml($page); 448 $containedHtml = $this->exportService->pageToContainedHtml($page);
477 return response()->make($containedHtml, 200, [ 449 return response()->make($containedHtml, 200, [
478 'Content-Type' => 'application/octet-stream', 450 'Content-Type' => 'application/octet-stream',
...@@ -488,8 +460,7 @@ class PageController extends Controller ...@@ -488,8 +460,7 @@ class PageController extends Controller
488 */ 460 */
489 public function exportPlainText($bookSlug, $pageSlug) 461 public function exportPlainText($bookSlug, $pageSlug)
490 { 462 {
491 - $book = $this->bookRepo->getBySlug($bookSlug); 463 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
492 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
493 $containedHtml = $this->exportService->pageToPlainText($page); 464 $containedHtml = $this->exportService->pageToPlainText($page);
494 return response()->make($containedHtml, 200, [ 465 return response()->make($containedHtml, 200, [
495 'Content-Type' => 'application/octet-stream', 466 'Content-Type' => 'application/octet-stream',
...@@ -503,7 +474,7 @@ class PageController extends Controller ...@@ -503,7 +474,7 @@ class PageController extends Controller
503 */ 474 */
504 public function showRecentlyCreated() 475 public function showRecentlyCreated()
505 { 476 {
506 - $pages = $this->pageRepo->getRecentlyCreatedPaginated(20)->setPath(baseUrl('/pages/recently-created')); 477 + $pages = $this->entityRepo->getRecentlyCreatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-created'));
507 return view('pages/detailed-listing', [ 478 return view('pages/detailed-listing', [
508 'title' => trans('entities.recently_created_pages'), 479 'title' => trans('entities.recently_created_pages'),
509 'pages' => $pages 480 'pages' => $pages
...@@ -516,7 +487,7 @@ class PageController extends Controller ...@@ -516,7 +487,7 @@ class PageController extends Controller
516 */ 487 */
517 public function showRecentlyUpdated() 488 public function showRecentlyUpdated()
518 { 489 {
519 - $pages = $this->pageRepo->getRecentlyUpdatedPaginated(20)->setPath(baseUrl('/pages/recently-updated')); 490 + $pages = $this->entityRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
520 return view('pages/detailed-listing', [ 491 return view('pages/detailed-listing', [
521 'title' => trans('entities.recently_updated_pages'), 492 'title' => trans('entities.recently_updated_pages'),
522 'pages' => $pages 493 'pages' => $pages
...@@ -531,8 +502,7 @@ class PageController extends Controller ...@@ -531,8 +502,7 @@ class PageController extends Controller
531 */ 502 */
532 public function showRestrict($bookSlug, $pageSlug) 503 public function showRestrict($bookSlug, $pageSlug)
533 { 504 {
534 - $book = $this->bookRepo->getBySlug($bookSlug); 505 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
535 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
536 $this->checkOwnablePermission('restrictions-manage', $page); 506 $this->checkOwnablePermission('restrictions-manage', $page);
537 $roles = $this->userRepo->getRestrictableRoles(); 507 $roles = $this->userRepo->getRestrictableRoles();
538 return view('pages/restrictions', [ 508 return view('pages/restrictions', [
...@@ -550,11 +520,10 @@ class PageController extends Controller ...@@ -550,11 +520,10 @@ class PageController extends Controller
550 */ 520 */
551 public function showMove($bookSlug, $pageSlug) 521 public function showMove($bookSlug, $pageSlug)
552 { 522 {
553 - $book = $this->bookRepo->getBySlug($bookSlug); 523 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
554 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
555 $this->checkOwnablePermission('page-update', $page); 524 $this->checkOwnablePermission('page-update', $page);
556 return view('pages/move', [ 525 return view('pages/move', [
557 - 'book' => $book, 526 + 'book' => $page->book,
558 'page' => $page 527 'page' => $page
559 ]); 528 ]);
560 } 529 }
...@@ -569,8 +538,7 @@ class PageController extends Controller ...@@ -569,8 +538,7 @@ class PageController extends Controller
569 */ 538 */
570 public function move($bookSlug, $pageSlug, Request $request) 539 public function move($bookSlug, $pageSlug, Request $request)
571 { 540 {
572 - $book = $this->bookRepo->getBySlug($bookSlug); 541 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
573 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
574 $this->checkOwnablePermission('page-update', $page); 542 $this->checkOwnablePermission('page-update', $page);
575 543
576 $entitySelection = $request->get('entity_selection', null); 544 $entitySelection = $request->get('entity_selection', null);
...@@ -582,20 +550,15 @@ class PageController extends Controller ...@@ -582,20 +550,15 @@ class PageController extends Controller
582 $entityType = $stringExploded[0]; 550 $entityType = $stringExploded[0];
583 $entityId = intval($stringExploded[1]); 551 $entityId = intval($stringExploded[1]);
584 552
585 - $parent = false;
586 553
587 - if ($entityType == 'chapter') { 554 + try {
588 - $parent = $this->chapterRepo->getById($entityId); 555 + $parent = $this->entityRepo->getById($entityType, $entityId);
589 - } else if ($entityType == 'book') { 556 + } catch (\Exception $e) {
590 - $parent = $this->bookRepo->getById($entityId);
591 - }
592 -
593 - if ($parent === false || $parent === null) {
594 session()->flash(trans('entities.selected_book_chapter_not_found')); 557 session()->flash(trans('entities.selected_book_chapter_not_found'));
595 return redirect()->back(); 558 return redirect()->back();
596 } 559 }
597 560
598 - $this->pageRepo->changePageParent($page, $parent); 561 + $this->entityRepo->changePageParent($page, $parent);
599 Activity::add($page, 'page_move', $page->book->id); 562 Activity::add($page, 'page_move', $page->book->id);
600 session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name])); 563 session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
601 564
...@@ -611,10 +574,9 @@ class PageController extends Controller ...@@ -611,10 +574,9 @@ class PageController extends Controller
611 */ 574 */
612 public function restrict($bookSlug, $pageSlug, Request $request) 575 public function restrict($bookSlug, $pageSlug, Request $request)
613 { 576 {
614 - $book = $this->bookRepo->getBySlug($bookSlug); 577 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
615 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
616 $this->checkOwnablePermission('restrictions-manage', $page); 578 $this->checkOwnablePermission('restrictions-manage', $page);
617 - $this->pageRepo->updateEntityPermissionsFromRequest($request, $page); 579 + $this->entityRepo->updateEntityPermissionsFromRequest($request, $page);
618 session()->flash('success', trans('entities.pages_permissions_success')); 580 session()->flash('success', trans('entities.pages_permissions_success'));
619 return redirect($page->getUrl()); 581 return redirect($page->getUrl());
620 } 582 }
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 +use BookStack\Repos\EntityRepo;
3 use BookStack\Services\ViewService; 4 use BookStack\Services\ViewService;
4 use Illuminate\Http\Request; 5 use Illuminate\Http\Request;
5 -use BookStack\Repos\BookRepo;
6 -use BookStack\Repos\ChapterRepo;
7 -use BookStack\Repos\PageRepo;
8 6
9 class SearchController extends Controller 7 class SearchController extends Controller
10 { 8 {
11 - protected $pageRepo; 9 + protected $entityRepo;
12 - protected $bookRepo;
13 - protected $chapterRepo;
14 protected $viewService; 10 protected $viewService;
15 11
16 /** 12 /**
17 * SearchController constructor. 13 * SearchController constructor.
18 - * @param PageRepo $pageRepo 14 + * @param EntityRepo $entityRepo
19 - * @param BookRepo $bookRepo
20 - * @param ChapterRepo $chapterRepo
21 * @param ViewService $viewService 15 * @param ViewService $viewService
22 */ 16 */
23 - public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ViewService $viewService) 17 + public function __construct(EntityRepo $entityRepo, ViewService $viewService)
24 { 18 {
25 - $this->pageRepo = $pageRepo; 19 + $this->entityRepo = $entityRepo;
26 - $this->bookRepo = $bookRepo;
27 - $this->chapterRepo = $chapterRepo;
28 $this->viewService = $viewService; 20 $this->viewService = $viewService;
29 parent::__construct(); 21 parent::__construct();
30 } 22 }
...@@ -42,9 +34,9 @@ class SearchController extends Controller ...@@ -42,9 +34,9 @@ class SearchController extends Controller
42 } 34 }
43 $searchTerm = $request->get('term'); 35 $searchTerm = $request->get('term');
44 $paginationAppends = $request->only('term'); 36 $paginationAppends = $request->only('term');
45 - $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); 37 + $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends);
46 - $books = $this->bookRepo->getBySearch($searchTerm, 10, $paginationAppends); 38 + $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 10, $paginationAppends);
47 - $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 10, $paginationAppends); 39 + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 10, $paginationAppends);
48 $this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm])); 40 $this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm]));
49 return view('search/all', [ 41 return view('search/all', [
50 'pages' => $pages, 42 'pages' => $pages,
...@@ -65,7 +57,7 @@ class SearchController extends Controller ...@@ -65,7 +57,7 @@ class SearchController extends Controller
65 57
66 $searchTerm = $request->get('term'); 58 $searchTerm = $request->get('term');
67 $paginationAppends = $request->only('term'); 59 $paginationAppends = $request->only('term');
68 - $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); 60 + $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends);
69 $this->setPageTitle(trans('entities.search_page_for_term', ['term' => $searchTerm])); 61 $this->setPageTitle(trans('entities.search_page_for_term', ['term' => $searchTerm]));
70 return view('search/entity-search-list', [ 62 return view('search/entity-search-list', [
71 'entities' => $pages, 63 'entities' => $pages,
...@@ -85,7 +77,7 @@ class SearchController extends Controller ...@@ -85,7 +77,7 @@ class SearchController extends Controller
85 77
86 $searchTerm = $request->get('term'); 78 $searchTerm = $request->get('term');
87 $paginationAppends = $request->only('term'); 79 $paginationAppends = $request->only('term');
88 - $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends); 80 + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 20, $paginationAppends);
89 $this->setPageTitle(trans('entities.search_chapter_for_term', ['term' => $searchTerm])); 81 $this->setPageTitle(trans('entities.search_chapter_for_term', ['term' => $searchTerm]));
90 return view('search/entity-search-list', [ 82 return view('search/entity-search-list', [
91 'entities' => $chapters, 83 'entities' => $chapters,
...@@ -105,7 +97,7 @@ class SearchController extends Controller ...@@ -105,7 +97,7 @@ class SearchController extends Controller
105 97
106 $searchTerm = $request->get('term'); 98 $searchTerm = $request->get('term');
107 $paginationAppends = $request->only('term'); 99 $paginationAppends = $request->only('term');
108 - $books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends); 100 + $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 20, $paginationAppends);
109 $this->setPageTitle(trans('entities.search_book_for_term', ['term' => $searchTerm])); 101 $this->setPageTitle(trans('entities.search_book_for_term', ['term' => $searchTerm]));
110 return view('search/entity-search-list', [ 102 return view('search/entity-search-list', [
111 'entities' => $books, 103 'entities' => $books,
...@@ -128,8 +120,8 @@ class SearchController extends Controller ...@@ -128,8 +120,8 @@ class SearchController extends Controller
128 } 120 }
129 $searchTerm = $request->get('term'); 121 $searchTerm = $request->get('term');
130 $searchWhereTerms = [['book_id', '=', $bookId]]; 122 $searchWhereTerms = [['book_id', '=', $bookId]];
131 - $pages = $this->pageRepo->getBySearch($searchTerm, $searchWhereTerms); 123 + $pages = $this->entityRepo->getBySearch('page', $searchTerm, $searchWhereTerms);
132 - $chapters = $this->chapterRepo->getBySearch($searchTerm, $searchWhereTerms); 124 + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, $searchWhereTerms);
133 return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); 125 return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
134 } 126 }
135 127
...@@ -148,9 +140,11 @@ class SearchController extends Controller ...@@ -148,9 +140,11 @@ class SearchController extends Controller
148 140
149 // Search for entities otherwise show most popular 141 // Search for entities otherwise show most popular
150 if ($searchTerm !== false) { 142 if ($searchTerm !== false) {
151 - if ($entityTypes->contains('page')) $entities = $entities->merge($this->pageRepo->getBySearch($searchTerm)->items()); 143 + foreach (['page', 'chapter', 'book'] as $entityType) {
152 - if ($entityTypes->contains('chapter')) $entities = $entities->merge($this->chapterRepo->getBySearch($searchTerm)->items()); 144 + if ($entityTypes->contains($entityType)) {
153 - if ($entityTypes->contains('book')) $entities = $entities->merge($this->bookRepo->getBySearch($searchTerm)->items()); 145 + $entities = $entities->merge($this->entityRepo->getBySearch($entityType, $searchTerm)->items());
146 + }
147 + }
154 $entities = $entities->sortByDesc('title_relevance'); 148 $entities = $entities->sortByDesc('title_relevance');
155 } else { 149 } else {
156 $entityNames = $entityTypes->map(function ($type) { 150 $entityNames = $entityTypes->map(function ($type) {
......
...@@ -4,8 +4,6 @@ namespace BookStack\Http\Middleware; ...@@ -4,8 +4,6 @@ namespace BookStack\Http\Middleware;
4 4
5 use Closure; 5 use Closure;
6 use Illuminate\Contracts\Auth\Guard; 6 use Illuminate\Contracts\Auth\Guard;
7 -use BookStack\Exceptions\UserRegistrationException;
8 -use Setting;
9 7
10 class Authenticate 8 class Authenticate
11 { 9 {
......
...@@ -7,6 +7,10 @@ class Page extends Entity ...@@ -7,6 +7,10 @@ class Page extends Entity
7 7
8 protected $simpleAttributes = ['name', 'id', 'slug']; 8 protected $simpleAttributes = ['name', 'id', 'slug'];
9 9
10 + protected $with = ['book'];
11 +
12 + protected $fieldsToSearch = ['name', 'text'];
13 +
10 /** 14 /**
11 * Converts this page into a simplified array. 15 * Converts this page into a simplified array.
12 * @return mixed 16 * @return mixed
......
1 -<?php namespace BookStack\Repos;
2 -
3 -use Alpha\B;
4 -use BookStack\Exceptions\NotFoundException;
5 -use Illuminate\Database\Eloquent\Collection;
6 -use Illuminate\Support\Str;
7 -use BookStack\Book;
8 -use Views;
9 -
10 -class BookRepo extends EntityRepo
11 -{
12 - protected $pageRepo;
13 - protected $chapterRepo;
14 -
15 - /**
16 - * BookRepo constructor.
17 - * @param PageRepo $pageRepo
18 - * @param ChapterRepo $chapterRepo
19 - */
20 - public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo)
21 - {
22 - $this->pageRepo = $pageRepo;
23 - $this->chapterRepo = $chapterRepo;
24 - parent::__construct();
25 - }
26 -
27 - /**
28 - * Base query for getting books.
29 - * Takes into account any restrictions.
30 - * @return mixed
31 - */
32 - private function bookQuery()
33 - {
34 - return $this->permissionService->enforceBookRestrictions($this->book, 'view');
35 - }
36 -
37 - /**
38 - * Get the book that has the given id.
39 - * @param $id
40 - * @return mixed
41 - */
42 - public function getById($id)
43 - {
44 - return $this->bookQuery()->findOrFail($id);
45 - }
46 -
47 - /**
48 - * Get all books, Limited by count.
49 - * @param int $count
50 - * @return mixed
51 - */
52 - public function getAll($count = 10)
53 - {
54 - $bookQuery = $this->bookQuery()->orderBy('name', 'asc');
55 - if (!$count) return $bookQuery->get();
56 - return $bookQuery->take($count)->get();
57 - }
58 -
59 - /**
60 - * Get all books paginated.
61 - * @param int $count
62 - * @return mixed
63 - */
64 - public function getAllPaginated($count = 10)
65 - {
66 - return $this->bookQuery()
67 - ->orderBy('name', 'asc')->paginate($count);
68 - }
69 -
70 -
71 - /**
72 - * Get the latest books.
73 - * @param int $count
74 - * @return mixed
75 - */
76 - public function getLatest($count = 10)
77 - {
78 - return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get();
79 - }
80 -
81 - /**
82 - * Gets the most recently viewed for a user.
83 - * @param int $count
84 - * @param int $page
85 - * @return mixed
86 - */
87 - public function getRecentlyViewed($count = 10, $page = 0)
88 - {
89 - return Views::getUserRecentlyViewed($count, $page, $this->book);
90 - }
91 -
92 - /**
93 - * Gets the most viewed books.
94 - * @param int $count
95 - * @param int $page
96 - * @return mixed
97 - */
98 - public function getPopular($count = 10, $page = 0)
99 - {
100 - return Views::getPopular($count, $page, $this->book);
101 - }
102 -
103 - /**
104 - * Get a book by slug
105 - * @param $slug
106 - * @return mixed
107 - * @throws NotFoundException
108 - */
109 - public function getBySlug($slug)
110 - {
111 - $book = $this->bookQuery()->where('slug', '=', $slug)->first();
112 - if ($book === null) throw new NotFoundException(trans('errors.book_not_found'));
113 - return $book;
114 - }
115 -
116 - /**
117 - * Checks if a book exists.
118 - * @param $id
119 - * @return bool
120 - */
121 - public function exists($id)
122 - {
123 - return $this->bookQuery()->where('id', '=', $id)->exists();
124 - }
125 -
126 - /**
127 - * Get a new book instance from request input.
128 - * @param array $input
129 - * @return Book
130 - */
131 - public function createFromInput($input)
132 - {
133 - $book = $this->book->newInstance($input);
134 - $book->slug = $this->findSuitableSlug($book->name);
135 - $book->created_by = user()->id;
136 - $book->updated_by = user()->id;
137 - $book->save();
138 - $this->permissionService->buildJointPermissionsForEntity($book);
139 - return $book;
140 - }
141 -
142 - /**
143 - * Update the given book from user input.
144 - * @param Book $book
145 - * @param $input
146 - * @return Book
147 - */
148 - public function updateFromInput(Book $book, $input)
149 - {
150 - if ($book->name !== $input['name']) {
151 - $book->slug = $this->findSuitableSlug($input['name'], $book->id);
152 - }
153 - $book->fill($input);
154 - $book->updated_by = user()->id;
155 - $book->save();
156 - $this->permissionService->buildJointPermissionsForEntity($book);
157 - return $book;
158 - }
159 -
160 - /**
161 - * Destroy the given book.
162 - * @param Book $book
163 - * @throws \Exception
164 - */
165 - public function destroy(Book $book)
166 - {
167 - foreach ($book->pages as $page) {
168 - $this->pageRepo->destroy($page);
169 - }
170 - foreach ($book->chapters as $chapter) {
171 - $this->chapterRepo->destroy($chapter);
172 - }
173 - $book->views()->delete();
174 - $book->permissions()->delete();
175 - $this->permissionService->deleteJointPermissionsForEntity($book);
176 - $book->delete();
177 - }
178 -
179 - /**
180 - * Get the next child element priority.
181 - * @param Book $book
182 - * @return int
183 - */
184 - public function getNewPriority($book)
185 - {
186 - $lastElem = $this->getChildren($book)->pop();
187 - return $lastElem ? $lastElem->priority + 1 : 0;
188 - }
189 -
190 - /**
191 - * @param string $slug
192 - * @param bool|false $currentId
193 - * @return bool
194 - */
195 - public function doesSlugExist($slug, $currentId = false)
196 - {
197 - $query = $this->book->where('slug', '=', $slug);
198 - if ($currentId) {
199 - $query = $query->where('id', '!=', $currentId);
200 - }
201 - return $query->count() > 0;
202 - }
203 -
204 - /**
205 - * Provides a suitable slug for the given book name.
206 - * Ensures the returned slug is unique in the system.
207 - * @param string $name
208 - * @param bool|false $currentId
209 - * @return string
210 - */
211 - public function findSuitableSlug($name, $currentId = false)
212 - {
213 - $slug = $this->nameToSlug($name);
214 - while ($this->doesSlugExist($slug, $currentId)) {
215 - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
216 - }
217 - return $slug;
218 - }
219 -
220 - /**
221 - * Get all child objects of a book.
222 - * Returns a sorted collection of Pages and Chapters.
223 - * Loads the book slug onto child elements to prevent access database access for getting the slug.
224 - * @param Book $book
225 - * @param bool $filterDrafts
226 - * @return mixed
227 - */
228 - public function getChildren(Book $book, $filterDrafts = false)
229 - {
230 - $pageQuery = $book->pages()->where('chapter_id', '=', 0);
231 - $pageQuery = $this->permissionService->enforcePageRestrictions($pageQuery, 'view');
232 -
233 - if ($filterDrafts) {
234 - $pageQuery = $pageQuery->where('draft', '=', false);
235 - }
236 -
237 - $pages = $pageQuery->get();
238 -
239 - $chapterQuery = $book->chapters()->with(['pages' => function ($query) use ($filterDrafts) {
240 - $this->permissionService->enforcePageRestrictions($query, 'view');
241 - if ($filterDrafts) $query->where('draft', '=', false);
242 - }]);
243 - $chapterQuery = $this->permissionService->enforceChapterRestrictions($chapterQuery, 'view');
244 - $chapters = $chapterQuery->get();
245 - $children = $pages->values();
246 - foreach ($chapters as $chapter) {
247 - $children->push($chapter);
248 - }
249 - $bookSlug = $book->slug;
250 -
251 - $children->each(function ($child) use ($bookSlug) {
252 - $child->setAttribute('bookSlug', $bookSlug);
253 - if ($child->isA('chapter')) {
254 - $child->pages->each(function ($page) use ($bookSlug) {
255 - $page->setAttribute('bookSlug', $bookSlug);
256 - });
257 - $child->pages = $child->pages->sortBy(function ($child, $key) {
258 - $score = $child->priority;
259 - if ($child->draft) $score -= 100;
260 - return $score;
261 - });
262 - }
263 - });
264 -
265 - // Sort items with drafts first then by priority.
266 - return $children->sortBy(function ($child, $key) {
267 - $score = $child->priority;
268 - if ($child->isA('page') && $child->draft) $score -= 100;
269 - return $score;
270 - });
271 - }
272 -
273 - /**
274 - * Get books by search term.
275 - * @param $term
276 - * @param int $count
277 - * @param array $paginationAppends
278 - * @return mixed
279 - */
280 - public function getBySearch($term, $count = 20, $paginationAppends = [])
281 - {
282 - $terms = $this->prepareSearchTerms($term);
283 - $bookQuery = $this->permissionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms));
284 - $bookQuery = $this->addAdvancedSearchQueries($bookQuery, $term);
285 - $books = $bookQuery->paginate($count)->appends($paginationAppends);
286 - $words = join('|', explode(' ', preg_quote(trim($term), '/')));
287 - foreach ($books as $book) {
288 - //highlight
289 - $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $book->getExcerpt(100));
290 - $book->searchSnippet = $result;
291 - }
292 - return $books;
293 - }
294 -
295 -}
...\ No newline at end of file ...\ No newline at end of file
1 -<?php namespace BookStack\Repos;
2 -
3 -
4 -use Activity;
5 -use BookStack\Book;
6 -use BookStack\Exceptions\NotFoundException;
7 -use Illuminate\Support\Str;
8 -use BookStack\Chapter;
9 -
10 -class ChapterRepo extends EntityRepo
11 -{
12 - protected $pageRepo;
13 -
14 - /**
15 - * ChapterRepo constructor.
16 - * @param $pageRepo
17 - */
18 - public function __construct(PageRepo $pageRepo)
19 - {
20 - $this->pageRepo = $pageRepo;
21 - parent::__construct();
22 - }
23 -
24 - /**
25 - * Base query for getting chapters, Takes permissions into account.
26 - * @return mixed
27 - */
28 - private function chapterQuery()
29 - {
30 - return $this->permissionService->enforceChapterRestrictions($this->chapter, 'view');
31 - }
32 -
33 - /**
34 - * Check if an id exists.
35 - * @param $id
36 - * @return bool
37 - */
38 - public function idExists($id)
39 - {
40 - return $this->chapterQuery()->where('id', '=', $id)->count() > 0;
41 - }
42 -
43 - /**
44 - * Get a chapter by a specific id.
45 - * @param $id
46 - * @return mixed
47 - */
48 - public function getById($id)
49 - {
50 - return $this->chapterQuery()->findOrFail($id);
51 - }
52 -
53 - /**
54 - * Get all chapters.
55 - * @return \Illuminate\Database\Eloquent\Collection|static[]
56 - */
57 - public function getAll()
58 - {
59 - return $this->chapterQuery()->all();
60 - }
61 -
62 - /**
63 - * Get a chapter that has the given slug within the given book.
64 - * @param $slug
65 - * @param $bookId
66 - * @return mixed
67 - * @throws NotFoundException
68 - */
69 - public function getBySlug($slug, $bookId)
70 - {
71 - $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
72 - if ($chapter === null) throw new NotFoundException(trans('errors.chapter_not_found'));
73 - return $chapter;
74 - }
75 -
76 - /**
77 - * Get the child items for a chapter
78 - * @param Chapter $chapter
79 - */
80 - public function getChildren(Chapter $chapter)
81 - {
82 - $pages = $this->permissionService->enforcePageRestrictions($chapter->pages())->get();
83 - // Sort items with drafts first then by priority.
84 - return $pages->sortBy(function ($child, $key) {
85 - $score = $child->priority;
86 - if ($child->draft) $score -= 100;
87 - return $score;
88 - });
89 - }
90 -
91 - /**
92 - * Create a new chapter from request input.
93 - * @param $input
94 - * @param Book $book
95 - * @return Chapter
96 - */
97 - public function createFromInput($input, Book $book)
98 - {
99 - $chapter = $this->chapter->newInstance($input);
100 - $chapter->slug = $this->findSuitableSlug($chapter->name, $book->id);
101 - $chapter->created_by = user()->id;
102 - $chapter->updated_by = user()->id;
103 - $chapter = $book->chapters()->save($chapter);
104 - $this->permissionService->buildJointPermissionsForEntity($chapter);
105 - return $chapter;
106 - }
107 -
108 - /**
109 - * Destroy a chapter and its relations by providing its slug.
110 - * @param Chapter $chapter
111 - */
112 - public function destroy(Chapter $chapter)
113 - {
114 - if (count($chapter->pages) > 0) {
115 - foreach ($chapter->pages as $page) {
116 - $page->chapter_id = 0;
117 - $page->save();
118 - }
119 - }
120 - Activity::removeEntity($chapter);
121 - $chapter->views()->delete();
122 - $chapter->permissions()->delete();
123 - $this->permissionService->deleteJointPermissionsForEntity($chapter);
124 - $chapter->delete();
125 - }
126 -
127 - /**
128 - * Check if a chapter's slug exists.
129 - * @param $slug
130 - * @param $bookId
131 - * @param bool|false $currentId
132 - * @return bool
133 - */
134 - public function doesSlugExist($slug, $bookId, $currentId = false)
135 - {
136 - $query = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId);
137 - if ($currentId) {
138 - $query = $query->where('id', '!=', $currentId);
139 - }
140 - return $query->count() > 0;
141 - }
142 -
143 - /**
144 - * Finds a suitable slug for the provided name.
145 - * Checks database to prevent duplicate slugs.
146 - * @param $name
147 - * @param $bookId
148 - * @param bool|false $currentId
149 - * @return string
150 - */
151 - public function findSuitableSlug($name, $bookId, $currentId = false)
152 - {
153 - $slug = $this->nameToSlug($name);
154 - while ($this->doesSlugExist($slug, $bookId, $currentId)) {
155 - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
156 - }
157 - return $slug;
158 - }
159 -
160 - /**
161 - * Get a new priority value for a new page to be added
162 - * to the given chapter.
163 - * @param Chapter $chapter
164 - * @return int
165 - */
166 - public function getNewPriority(Chapter $chapter)
167 - {
168 - $lastPage = $chapter->pages->last();
169 - return $lastPage !== null ? $lastPage->priority + 1 : 0;
170 - }
171 -
172 - /**
173 - * Get chapters by the given search term.
174 - * @param string $term
175 - * @param array $whereTerms
176 - * @param int $count
177 - * @param array $paginationAppends
178 - * @return mixed
179 - */
180 - public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
181 - {
182 - $terms = $this->prepareSearchTerms($term);
183 - $chapterQuery = $this->permissionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms));
184 - $chapterQuery = $this->addAdvancedSearchQueries($chapterQuery, $term);
185 - $chapters = $chapterQuery->paginate($count)->appends($paginationAppends);
186 - $words = join('|', explode(' ', preg_quote(trim($term), '/')));
187 - foreach ($chapters as $chapter) {
188 - //highlight
189 - $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $chapter->getExcerpt(100));
190 - $chapter->searchSnippet = $result;
191 - }
192 - return $chapters;
193 - }
194 -
195 - /**
196 - * Changes the book relation of this chapter.
197 - * @param $bookId
198 - * @param Chapter $chapter
199 - * @param bool $rebuildPermissions
200 - * @return Chapter
201 - */
202 - public function changeBook($bookId, Chapter $chapter, $rebuildPermissions = false)
203 - {
204 - $chapter->book_id = $bookId;
205 - // Update related activity
206 - foreach ($chapter->activity as $activity) {
207 - $activity->book_id = $bookId;
208 - $activity->save();
209 - }
210 - $chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id);
211 - $chapter->save();
212 - // Update all child pages
213 - foreach ($chapter->pages as $page) {
214 - $this->pageRepo->changeBook($bookId, $page);
215 - }
216 -
217 - // Update permissions if applicable
218 - if ($rebuildPermissions) {
219 - $chapter->load('book');
220 - $this->permissionService->buildJointPermissionsForEntity($chapter->book);
221 - }
222 -
223 - return $chapter;
224 - }
225 -
226 -}
...\ No newline at end of file ...\ No newline at end of file
...@@ -3,11 +3,16 @@ ...@@ -3,11 +3,16 @@
3 use BookStack\Book; 3 use BookStack\Book;
4 use BookStack\Chapter; 4 use BookStack\Chapter;
5 use BookStack\Entity; 5 use BookStack\Entity;
6 +use BookStack\Exceptions\NotFoundException;
6 use BookStack\Page; 7 use BookStack\Page;
8 +use BookStack\PageRevision;
9 +use BookStack\Services\AttachmentService;
7 use BookStack\Services\PermissionService; 10 use BookStack\Services\PermissionService;
8 -use BookStack\User; 11 +use BookStack\Services\ViewService;
12 +use Carbon\Carbon;
13 +use DOMDocument;
14 +use DOMXPath;
9 use Illuminate\Support\Collection; 15 use Illuminate\Support\Collection;
10 -use Illuminate\Support\Facades\Log;
11 16
12 class EntityRepo 17 class EntityRepo
13 { 18 {
...@@ -28,11 +33,32 @@ class EntityRepo ...@@ -28,11 +33,32 @@ class EntityRepo
28 public $page; 33 public $page;
29 34
30 /** 35 /**
36 + * @var PageRevision
37 + */
38 + protected $pageRevision;
39 +
40 + /**
41 + * Base entity instances keyed by type
42 + * @var []Entity
43 + */
44 + protected $entities;
45 +
46 + /**
31 * @var PermissionService 47 * @var PermissionService
32 */ 48 */
33 protected $permissionService; 49 protected $permissionService;
34 50
35 /** 51 /**
52 + * @var ViewService
53 + */
54 + protected $viewService;
55 +
56 + /**
57 + * @var TagRepo
58 + */
59 + protected $tagRepo;
60 +
61 + /**
36 * Acceptable operators to be used in a query 62 * Acceptable operators to be used in a query
37 * @var array 63 * @var array
38 */ 64 */
...@@ -40,72 +66,181 @@ class EntityRepo ...@@ -40,72 +66,181 @@ class EntityRepo
40 66
41 /** 67 /**
42 * EntityService constructor. 68 * EntityService constructor.
69 + * @param Book $book
70 + * @param Chapter $chapter
71 + * @param Page $page
72 + * @param PageRevision $pageRevision
73 + * @param ViewService $viewService
74 + * @param PermissionService $permissionService
75 + * @param TagRepo $tagRepo
43 */ 76 */
44 - public function __construct() 77 + public function __construct(
78 + Book $book, Chapter $chapter, Page $page, PageRevision $pageRevision,
79 + ViewService $viewService, PermissionService $permissionService, TagRepo $tagRepo
80 + )
45 { 81 {
46 - $this->book = app(Book::class); 82 + $this->book = $book;
47 - $this->chapter = app(Chapter::class); 83 + $this->chapter = $chapter;
48 - $this->page = app(Page::class); 84 + $this->page = $page;
49 - $this->permissionService = app(PermissionService::class); 85 + $this->pageRevision = $pageRevision;
86 + $this->entities = [
87 + 'page' => $this->page,
88 + 'chapter' => $this->chapter,
89 + 'book' => $this->book,
90 + 'page_revision' => $this->pageRevision
91 + ];
92 + $this->viewService = $viewService;
93 + $this->permissionService = $permissionService;
94 + $this->tagRepo = $tagRepo;
50 } 95 }
51 96
52 /** 97 /**
53 - * Get the latest books added to the system. 98 + * Get an entity instance via type.
54 - * @param int $count 99 + * @param $type
55 - * @param int $page 100 + * @return Entity
56 - * @param bool $additionalQuery
57 - * @return
58 */ 101 */
59 - public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false) 102 + protected function getEntity($type)
60 { 103 {
61 - $query = $this->permissionService->enforceBookRestrictions($this->book) 104 + return $this->entities[strtolower($type)];
62 - ->orderBy('created_at', 'desc');
63 - if ($additionalQuery !== false && is_callable($additionalQuery)) {
64 - $additionalQuery($query);
65 } 105 }
66 - return $query->skip($page * $count)->take($count)->get(); 106 +
107 + /**
108 + * Base query for searching entities via permission system
109 + * @param string $type
110 + * @param bool $allowDrafts
111 + * @return \Illuminate\Database\Query\Builder
112 + */
113 + protected function entityQuery($type, $allowDrafts = false)
114 + {
115 + $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), 'view');
116 + if (strtolower($type) === 'page' && !$allowDrafts) {
117 + $q = $q->where('draft', '=', false);
118 + }
119 + return $q;
67 } 120 }
68 121
69 /** 122 /**
70 - * Get the most recently updated books. 123 + * Check if an entity with the given id exists.
71 - * @param $count 124 + * @param $type
72 - * @param int $page 125 + * @param $id
73 - * @return mixed 126 + * @return bool
74 */ 127 */
75 - public function getRecentlyUpdatedBooks($count = 20, $page = 0) 128 + public function exists($type, $id)
76 { 129 {
77 - return $this->permissionService->enforceBookRestrictions($this->book) 130 + return $this->entityQuery($type)->where('id', '=', $id)->exists();
78 - ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get();
79 } 131 }
80 132
81 /** 133 /**
82 - * Get the latest pages added to the system. 134 + * Get an entity by ID
135 + * @param string $type
136 + * @param integer $id
137 + * @param bool $allowDrafts
138 + * @return Entity
139 + */
140 + public function getById($type, $id, $allowDrafts = false)
141 + {
142 + return $this->entityQuery($type, $allowDrafts)->findOrFail($id);
143 + }
144 +
145 + /**
146 + * Get an entity by its url slug.
147 + * @param string $type
148 + * @param string $slug
149 + * @param string|bool $bookSlug
150 + * @return Entity
151 + * @throws NotFoundException
152 + */
153 + public function getBySlug($type, $slug, $bookSlug = false)
154 + {
155 + $q = $this->entityQuery($type)->where('slug', '=', $slug);
156 +
157 + if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
158 + $q = $q->where('book_id', '=', function($query) use ($bookSlug) {
159 + $query->select('id')
160 + ->from($this->book->getTable())
161 + ->where('slug', '=', $bookSlug)->limit(1);
162 + });
163 + }
164 + $entity = $q->first();
165 + if ($entity === null) throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
166 + return $entity;
167 + }
168 +
169 +
170 + /**
171 + * Search through page revisions and retrieve the last page in the
172 + * current book that has a slug equal to the one given.
173 + * @param string $pageSlug
174 + * @param string $bookSlug
175 + * @return null|Page
176 + */
177 + public function getPageByOldSlug($pageSlug, $bookSlug)
178 + {
179 + $revision = $this->pageRevision->where('slug', '=', $pageSlug)
180 + ->whereHas('page', function ($query) {
181 + $this->permissionService->enforceEntityRestrictions('page', $query);
182 + })
183 + ->where('type', '=', 'version')
184 + ->where('book_slug', '=', $bookSlug)
185 + ->orderBy('created_at', 'desc')
186 + ->with('page')->first();
187 + return $revision !== null ? $revision->page : null;
188 + }
189 +
190 + /**
191 + * Get all entities of a type limited by count unless count if false.
192 + * @param string $type
193 + * @param integer|bool $count
194 + * @return Collection
195 + */
196 + public function getAll($type, $count = 20)
197 + {
198 + $q = $this->entityQuery($type)->orderBy('name', 'asc');
199 + if ($count !== false) $q = $q->take($count);
200 + return $q->get();
201 + }
202 +
203 + /**
204 + * Get all entities in a paginated format
205 + * @param $type
206 + * @param int $count
207 + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
208 + */
209 + public function getAllPaginated($type, $count = 10)
210 + {
211 + return $this->entityQuery($type)->orderBy('name', 'asc')->paginate($count);
212 + }
213 +
214 + /**
215 + * Get the most recently created entities of the given type.
216 + * @param string $type
83 * @param int $count 217 * @param int $count
84 * @param int $page 218 * @param int $page
85 - * @param bool $additionalQuery 219 + * @param bool|callable $additionalQuery
86 - * @return
87 */ 220 */
88 - public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false) 221 + public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false)
89 { 222 {
90 - $query = $this->permissionService->enforcePageRestrictions($this->page) 223 + $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
91 - ->orderBy('created_at', 'desc')->where('draft', '=', false); 224 + ->orderBy('created_at', 'desc');
225 + if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
92 if ($additionalQuery !== false && is_callable($additionalQuery)) { 226 if ($additionalQuery !== false && is_callable($additionalQuery)) {
93 $additionalQuery($query); 227 $additionalQuery($query);
94 } 228 }
95 - return $query->with('book')->skip($page * $count)->take($count)->get(); 229 + return $query->skip($page * $count)->take($count)->get();
96 } 230 }
97 231
98 /** 232 /**
99 - * Get the latest chapters added to the system. 233 + * Get the most recently updated entities of the given type.
234 + * @param string $type
100 * @param int $count 235 * @param int $count
101 * @param int $page 236 * @param int $page
102 - * @param bool $additionalQuery 237 + * @param bool|callable $additionalQuery
103 - * @return
104 */ 238 */
105 - public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false) 239 + public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false)
106 { 240 {
107 - $query = $this->permissionService->enforceChapterRestrictions($this->chapter) 241 + $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
108 - ->orderBy('created_at', 'desc'); 242 + ->orderBy('updated_at', 'desc');
243 + if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
109 if ($additionalQuery !== false && is_callable($additionalQuery)) { 244 if ($additionalQuery !== false && is_callable($additionalQuery)) {
110 $additionalQuery($query); 245 $additionalQuery($query);
111 } 246 }
...@@ -113,16 +248,51 @@ class EntityRepo ...@@ -113,16 +248,51 @@ class EntityRepo
113 } 248 }
114 249
115 /** 250 /**
116 - * Get the most recently updated pages. 251 + * Get the most recently viewed entities.
117 - * @param $count 252 + * @param string|bool $type
253 + * @param int $count
118 * @param int $page 254 * @param int $page
119 * @return mixed 255 * @return mixed
120 */ 256 */
121 - public function getRecentlyUpdatedPages($count = 20, $page = 0) 257 + public function getRecentlyViewed($type, $count = 10, $page = 0)
122 { 258 {
123 - return $this->permissionService->enforcePageRestrictions($this->page) 259 + $filter = is_bool($type) ? false : $this->getEntity($type);
124 - ->where('draft', '=', false) 260 + return $this->viewService->getUserRecentlyViewed($count, $page, $filter);
125 - ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get(); 261 + }
262 +
263 + /**
264 + * Get the latest pages added to the system with pagination.
265 + * @param string $type
266 + * @param int $count
267 + * @return mixed
268 + */
269 + public function getRecentlyCreatedPaginated($type, $count = 20)
270 + {
271 + return $this->entityQuery($type)->orderBy('created_at', 'desc')->paginate($count);
272 + }
273 +
274 + /**
275 + * Get the latest pages added to the system with pagination.
276 + * @param string $type
277 + * @param int $count
278 + * @return mixed
279 + */
280 + public function getRecentlyUpdatedPaginated($type, $count = 20)
281 + {
282 + return $this->entityQuery($type)->orderBy('updated_at', 'desc')->paginate($count);
283 + }
284 +
285 + /**
286 + * Get the most popular entities base on all views.
287 + * @param string|bool $type
288 + * @param int $count
289 + * @param int $page
290 + * @return mixed
291 + */
292 + public function getPopular($type, $count = 10, $page = 0)
293 + {
294 + $filter = is_bool($type) ? false : $this->getEntity($type);
295 + return $this->viewService->getPopular($count, $page, $filter);
126 } 296 }
127 297
128 /** 298 /**
...@@ -139,6 +309,163 @@ class EntityRepo ...@@ -139,6 +309,163 @@ class EntityRepo
139 } 309 }
140 310
141 /** 311 /**
312 + * Get all child objects of a book.
313 + * Returns a sorted collection of Pages and Chapters.
314 + * Loads the book slug onto child elements to prevent access database access for getting the slug.
315 + * @param Book $book
316 + * @param bool $filterDrafts
317 + * @return mixed
318 + */
319 + public function getBookChildren(Book $book, $filterDrafts = false)
320 + {
321 + $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts);
322 + $entities = [];
323 + $parents = [];
324 + $tree = [];
325 +
326 + foreach ($q as $index => $rawEntity) {
327 + if ($rawEntity->entity_type === 'Bookstack\\Page') {
328 + $entities[$index] = $this->page->newFromBuilder($rawEntity);
329 + } else if ($rawEntity->entity_type === 'Bookstack\\Chapter') {
330 + $entities[$index] = $this->chapter->newFromBuilder($rawEntity);
331 + $key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
332 + $parents[$key] = $entities[$index];
333 + $parents[$key]->setAttribute('pages', collect());
334 + }
335 + if ($entities[$index]->chapter_id === 0) $tree[] = $entities[$index];
336 + $entities[$index]->book = $book;
337 + }
338 +
339 + foreach ($entities as $entity) {
340 + if ($entity->chapter_id === 0) continue;
341 + $parentKey = 'Bookstack\\Chapter:' . $entity->chapter_id;
342 + $chapter = $parents[$parentKey];
343 + $chapter->pages->push($entity);
344 + }
345 +
346 + return collect($tree);
347 + }
348 +
349 + /**
350 + * Get the child items for a chapter sorted by priority but
351 + * with draft items floated to the top.
352 + * @param Chapter $chapter
353 + */
354 + public function getChapterChildren(Chapter $chapter)
355 + {
356 + return $this->permissionService->enforceEntityRestrictions('page', $chapter->pages())
357 + ->orderBy('draft', 'DESC')->orderBy('priority', 'ASC')->get();
358 + }
359 +
360 + /**
361 + * Search entities of a type via a given query.
362 + * @param string $type
363 + * @param string $term
364 + * @param array $whereTerms
365 + * @param int $count
366 + * @param array $paginationAppends
367 + * @return mixed
368 + */
369 + public function getBySearch($type, $term, $whereTerms = [], $count = 20, $paginationAppends = [])
370 + {
371 + $terms = $this->prepareSearchTerms($term);
372 + $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type)->fullTextSearchQuery($terms, $whereTerms));
373 + $q = $this->addAdvancedSearchQueries($q, $term);
374 + $entities = $q->paginate($count)->appends($paginationAppends);
375 + $words = join('|', explode(' ', preg_quote(trim($term), '/')));
376 +
377 + // Highlight page content
378 + if ($type === 'page') {
379 + //lookahead/behind assertions ensures cut between words
380 + $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
381 +
382 + foreach ($entities as $page) {
383 + preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
384 + //delimiter between occurrences
385 + $results = [];
386 + foreach ($matches as $line) {
387 + $results[] = htmlspecialchars($line[0], 0, 'UTF-8');
388 + }
389 + $matchLimit = 6;
390 + if (count($results) > $matchLimit) $results = array_slice($results, 0, $matchLimit);
391 + $result = join('... ', $results);
392 +
393 + //highlight
394 + $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $result);
395 + if (strlen($result) < 5) $result = $page->getExcerpt(80);
396 +
397 + $page->searchSnippet = $result;
398 + }
399 + return $entities;
400 + }
401 +
402 + // Highlight chapter/book content
403 + foreach ($entities as $entity) {
404 + //highlight
405 + $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $entity->getExcerpt(100));
406 + $entity->searchSnippet = $result;
407 + }
408 + return $entities;
409 + }
410 +
411 + /**
412 + * Get the next sequential priority for a new child element in the given book.
413 + * @param Book $book
414 + * @return int
415 + */
416 + public function getNewBookPriority(Book $book)
417 + {
418 + $lastElem = $this->getBookChildren($book)->pop();
419 + return $lastElem ? $lastElem->priority + 1 : 0;
420 + }
421 +
422 + /**
423 + * Get a new priority for a new page to be added to the given chapter.
424 + * @param Chapter $chapter
425 + * @return int
426 + */
427 + public function getNewChapterPriority(Chapter $chapter)
428 + {
429 + $lastPage = $chapter->pages('DESC')->first();
430 + return $lastPage !== null ? $lastPage->priority + 1 : 0;
431 + }
432 +
433 + /**
434 + * Find a suitable slug for an entity.
435 + * @param string $type
436 + * @param string $name
437 + * @param bool|integer $currentId
438 + * @param bool|integer $bookId Only pass if type is not a book
439 + * @return string
440 + */
441 + public function findSuitableSlug($type, $name, $currentId = false, $bookId = false)
442 + {
443 + $slug = $this->nameToSlug($name);
444 + while ($this->slugExists($type, $slug, $currentId, $bookId)) {
445 + $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
446 + }
447 + return $slug;
448 + }
449 +
450 + /**
451 + * Check if a slug already exists in the database.
452 + * @param string $type
453 + * @param string $slug
454 + * @param bool|integer $currentId
455 + * @param bool|integer $bookId
456 + * @return bool
457 + */
458 + protected function slugExists($type, $slug, $currentId = false, $bookId = false)
459 + {
460 + $query = $this->getEntity($type)->where('slug', '=', $slug);
461 + if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
462 + $query = $query->where('book_id', '=', $bookId);
463 + }
464 + if ($currentId) $query = $query->where('id', '!=', $currentId);
465 + return $query->count() > 0;
466 + }
467 +
468 + /**
142 * Updates entity restrictions from a request 469 * Updates entity restrictions from a request
143 * @param $request 470 * @param $request
144 * @param Entity $entity 471 * @param Entity $entity
...@@ -261,6 +588,81 @@ class EntityRepo ...@@ -261,6 +588,81 @@ class EntityRepo
261 } 588 }
262 589
263 /** 590 /**
591 + * Create a new entity from request input.
592 + * Used for books and chapters.
593 + * @param string $type
594 + * @param array $input
595 + * @param bool|Book $book
596 + * @return Entity
597 + */
598 + public function createFromInput($type, $input = [], $book = false)
599 + {
600 + $isChapter = strtolower($type) === 'chapter';
601 + $entity = $this->getEntity($type)->newInstance($input);
602 + $entity->slug = $this->findSuitableSlug($type, $entity->name, false, $isChapter ? $book->id : false);
603 + $entity->created_by = user()->id;
604 + $entity->updated_by = user()->id;
605 + $isChapter ? $book->chapters()->save($entity) : $entity->save();
606 + $this->permissionService->buildJointPermissionsForEntity($entity);
607 + return $entity;
608 + }
609 +
610 + /**
611 + * Update entity details from request input.
612 + * Use for books and chapters
613 + * @param string $type
614 + * @param Entity $entityModel
615 + * @param array $input
616 + * @return Entity
617 + */
618 + public function updateFromInput($type, Entity $entityModel, $input = [])
619 + {
620 + if ($entityModel->name !== $input['name']) {
621 + $entityModel->slug = $this->findSuitableSlug($type, $input['name'], $entityModel->id);
622 + }
623 + $entityModel->fill($input);
624 + $entityModel->updated_by = user()->id;
625 + $entityModel->save();
626 + $this->permissionService->buildJointPermissionsForEntity($entityModel);
627 + return $entityModel;
628 + }
629 +
630 + /**
631 + * Change the book that an entity belongs to.
632 + * @param string $type
633 + * @param integer $newBookId
634 + * @param Entity $entity
635 + * @param bool $rebuildPermissions
636 + * @return Entity
637 + */
638 + public function changeBook($type, $newBookId, Entity $entity, $rebuildPermissions = false)
639 + {
640 + $entity->book_id = $newBookId;
641 + // Update related activity
642 + foreach ($entity->activity as $activity) {
643 + $activity->book_id = $newBookId;
644 + $activity->save();
645 + }
646 + $entity->slug = $this->findSuitableSlug($type, $entity->name, $entity->id, $newBookId);
647 + $entity->save();
648 +
649 + // Update all child pages if a chapter
650 + if (strtolower($type) === 'chapter') {
651 + foreach ($entity->pages as $page) {
652 + $this->changeBook('page', $newBookId, $page, false);
653 + }
654 + }
655 +
656 + // Update permissions if applicable
657 + if ($rebuildPermissions) {
658 + $entity->load('book');
659 + $this->permissionService->buildJointPermissionsForEntity($entity->book);
660 + }
661 +
662 + return $entity;
663 + }
664 +
665 + /**
264 * Alias method to update the book jointPermissions in the PermissionService. 666 * Alias method to update the book jointPermissions in the PermissionService.
265 * @param Collection $collection collection on entities 667 * @param Collection $collection collection on entities
266 */ 668 */
...@@ -282,6 +684,463 @@ class EntityRepo ...@@ -282,6 +684,463 @@ class EntityRepo
282 return $slug; 684 return $slug;
283 } 685 }
284 686
687 + /**
688 + * Publish a draft page to make it a normal page.
689 + * Sets the slug and updates the content.
690 + * @param Page $draftPage
691 + * @param array $input
692 + * @return Page
693 + */
694 + public function publishPageDraft(Page $draftPage, array $input)
695 + {
696 + $draftPage->fill($input);
697 +
698 + // Save page tags if present
699 + if (isset($input['tags'])) {
700 + $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
701 + }
702 +
703 + $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
704 + $draftPage->html = $this->formatHtml($input['html']);
705 + $draftPage->text = strip_tags($draftPage->html);
706 + $draftPage->draft = false;
707 +
708 + $draftPage->save();
709 + $this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
710 +
711 + return $draftPage;
712 + }
713 +
714 + /**
715 + * Saves a page revision into the system.
716 + * @param Page $page
717 + * @param null|string $summary
718 + * @return PageRevision
719 + */
720 + public function savePageRevision(Page $page, $summary = null)
721 + {
722 + $revision = $this->pageRevision->newInstance($page->toArray());
723 + if (setting('app-editor') !== 'markdown') $revision->markdown = '';
724 + $revision->page_id = $page->id;
725 + $revision->slug = $page->slug;
726 + $revision->book_slug = $page->book->slug;
727 + $revision->created_by = user()->id;
728 + $revision->created_at = $page->updated_at;
729 + $revision->type = 'version';
730 + $revision->summary = $summary;
731 + $revision->save();
732 +
733 + // Clear old revisions
734 + if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
735 + $this->pageRevision->where('page_id', '=', $page->id)
736 + ->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
737 + }
738 +
739 + return $revision;
740 + }
741 +
742 + /**
743 + * Formats a page's html to be tagged correctly
744 + * within the system.
745 + * @param string $htmlText
746 + * @return string
747 + */
748 + protected function formatHtml($htmlText)
749 + {
750 + if ($htmlText == '') return $htmlText;
751 + libxml_use_internal_errors(true);
752 + $doc = new DOMDocument();
753 + $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
754 +
755 + $container = $doc->documentElement;
756 + $body = $container->childNodes->item(0);
757 + $childNodes = $body->childNodes;
758 +
759 + // Ensure no duplicate ids are used
760 + $idArray = [];
761 +
762 + foreach ($childNodes as $index => $childNode) {
763 + /** @var \DOMElement $childNode */
764 + if (get_class($childNode) !== 'DOMElement') continue;
765 +
766 + // Overwrite id if not a BookStack custom id
767 + if ($childNode->hasAttribute('id')) {
768 + $id = $childNode->getAttribute('id');
769 + if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
770 + $idArray[] = $id;
771 + continue;
772 + };
773 + }
774 +
775 + // Create an unique id for the element
776 + // Uses the content as a basis to ensure output is the same every time
777 + // the same content is passed through.
778 + $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
779 + $newId = urlencode($contentId);
780 + $loopIndex = 0;
781 + while (in_array($newId, $idArray)) {
782 + $newId = urlencode($contentId . '-' . $loopIndex);
783 + $loopIndex++;
784 + }
785 +
786 + $childNode->setAttribute('id', $newId);
787 + $idArray[] = $newId;
788 + }
789 +
790 + // Generate inner html as a string
791 + $html = '';
792 + foreach ($childNodes as $childNode) {
793 + $html .= $doc->saveHTML($childNode);
794 + }
795 +
796 + return $html;
797 + }
798 +
799 + /**
800 + * Get a new draft page instance.
801 + * @param Book $book
802 + * @param Chapter|bool $chapter
803 + * @return Page
804 + */
805 + public function getDraftPage(Book $book, $chapter = false)
806 + {
807 + $page = $this->page->newInstance();
808 + $page->name = trans('entities.pages_initial_name');
809 + $page->created_by = user()->id;
810 + $page->updated_by = user()->id;
811 + $page->draft = true;
812 +
813 + if ($chapter) $page->chapter_id = $chapter->id;
814 +
815 + $book->pages()->save($page);
816 + $this->permissionService->buildJointPermissionsForEntity($page);
817 + return $page;
818 + }
819 +
820 + /**
821 + * Search for image usage within page content.
822 + * @param $imageString
823 + * @return mixed
824 + */
825 + public function searchForImage($imageString)
826 + {
827 + $pages = $this->entityQuery('page')->where('html', 'like', '%' . $imageString . '%')->get();
828 + foreach ($pages as $page) {
829 + $page->url = $page->getUrl();
830 + $page->html = '';
831 + $page->text = '';
832 + }
833 + return count($pages) > 0 ? $pages : false;
834 + }
835 +
836 + /**
837 + * Parse the headers on the page to get a navigation menu
838 + * @param Page $page
839 + * @return array
840 + */
841 + public function getPageNav(Page $page)
842 + {
843 + if ($page->html == '') return null;
844 + libxml_use_internal_errors(true);
845 + $doc = new DOMDocument();
846 + $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8'));
847 + $xPath = new DOMXPath($doc);
848 + $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
849 +
850 + if (is_null($headers)) return null;
851 +
852 + $tree = [];
853 + foreach ($headers as $header) {
854 + $text = $header->nodeValue;
855 + $tree[] = [
856 + 'nodeName' => strtolower($header->nodeName),
857 + 'level' => intval(str_replace('h', '', $header->nodeName)),
858 + 'link' => '#' . $header->getAttribute('id'),
859 + 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
860 + ];
861 + }
862 + return $tree;
863 + }
864 +
865 + /**
866 + * Updates a page with any fillable data and saves it into the database.
867 + * @param Page $page
868 + * @param int $book_id
869 + * @param array $input
870 + * @return Page
871 + */
872 + public function updatePage(Page $page, $book_id, $input)
873 + {
874 + // Hold the old details to compare later
875 + $oldHtml = $page->html;
876 + $oldName = $page->name;
877 +
878 + // Prevent slug being updated if no name change
879 + if ($page->name !== $input['name']) {
880 + $page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id);
881 + }
882 +
883 + // Save page tags if present
884 + if (isset($input['tags'])) {
885 + $this->tagRepo->saveTagsToEntity($page, $input['tags']);
886 + }
887 +
888 + // Update with new details
889 + $userId = user()->id;
890 + $page->fill($input);
891 + $page->html = $this->formatHtml($input['html']);
892 + $page->text = strip_tags($page->html);
893 + if (setting('app-editor') !== 'markdown') $page->markdown = '';
894 + $page->updated_by = $userId;
895 + $page->save();
896 +
897 + // Remove all update drafts for this user & page.
898 + $this->userUpdatePageDraftsQuery($page, $userId)->delete();
899 +
900 + // Save a revision after updating
901 + if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
902 + $this->savePageRevision($page, $input['summary']);
903 + }
904 +
905 + return $page;
906 + }
907 +
908 + /**
909 + * The base query for getting user update drafts.
910 + * @param Page $page
911 + * @param $userId
912 + * @return mixed
913 + */
914 + protected function userUpdatePageDraftsQuery(Page $page, $userId)
915 + {
916 + return $this->pageRevision->where('created_by', '=', $userId)
917 + ->where('type', 'update_draft')
918 + ->where('page_id', '=', $page->id)
919 + ->orderBy('created_at', 'desc');
920 + }
921 +
922 + /**
923 + * Checks whether a user has a draft version of a particular page or not.
924 + * @param Page $page
925 + * @param $userId
926 + * @return bool
927 + */
928 + public function hasUserGotPageDraft(Page $page, $userId)
929 + {
930 + return $this->userUpdatePageDraftsQuery($page, $userId)->count() > 0;
931 + }
932 +
933 + /**
934 + * Get the latest updated draft revision for a particular page and user.
935 + * @param Page $page
936 + * @param $userId
937 + * @return mixed
938 + */
939 + public function getUserPageDraft(Page $page, $userId)
940 + {
941 + return $this->userUpdatePageDraftsQuery($page, $userId)->first();
942 + }
943 +
944 + /**
945 + * Get the notification message that informs the user that they are editing a draft page.
946 + * @param PageRevision $draft
947 + * @return string
948 + */
949 + public function getUserPageDraftMessage(PageRevision $draft)
950 + {
951 + $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
952 + if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) return $message;
953 + return $message . "\n" . trans('entities.pages_draft_edited_notification');
954 + }
955 +
956 + /**
957 + * Check if a page is being actively editing.
958 + * Checks for edits since last page updated.
959 + * Passing in a minuted range will check for edits
960 + * within the last x minutes.
961 + * @param Page $page
962 + * @param null $minRange
963 + * @return bool
964 + */
965 + public function isPageEditingActive(Page $page, $minRange = null)
966 + {
967 + $draftSearch = $this->activePageEditingQuery($page, $minRange);
968 + return $draftSearch->count() > 0;
969 + }
970 +
971 + /**
972 + * A query to check for active update drafts on a particular page.
973 + * @param Page $page
974 + * @param null $minRange
975 + * @return mixed
976 + */
977 + protected function activePageEditingQuery(Page $page, $minRange = null)
978 + {
979 + $query = $this->pageRevision->where('type', '=', 'update_draft')
980 + ->where('page_id', '=', $page->id)
981 + ->where('updated_at', '>', $page->updated_at)
982 + ->where('created_by', '!=', user()->id)
983 + ->with('createdBy');
984 +
985 + if ($minRange !== null) {
986 + $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
987 + }
988 +
989 + return $query;
990 + }
991 +
992 + /**
993 + * Restores a revision's content back into a page.
994 + * @param Page $page
995 + * @param Book $book
996 + * @param int $revisionId
997 + * @return Page
998 + */
999 + public function restorePageRevision(Page $page, Book $book, $revisionId)
1000 + {
1001 + $this->savePageRevision($page);
1002 + $revision = $this->getById('page_revision', $revisionId);
1003 + $page->fill($revision->toArray());
1004 + $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
1005 + $page->text = strip_tags($page->html);
1006 + $page->updated_by = user()->id;
1007 + $page->save();
1008 + return $page;
1009 + }
1010 +
1011 +
1012 + /**
1013 + * Save a page update draft.
1014 + * @param Page $page
1015 + * @param array $data
1016 + * @return PageRevision|Page
1017 + */
1018 + public function updatePageDraft(Page $page, $data = [])
1019 + {
1020 + // If the page itself is a draft simply update that
1021 + if ($page->draft) {
1022 + $page->fill($data);
1023 + if (isset($data['html'])) {
1024 + $page->text = strip_tags($data['html']);
1025 + }
1026 + $page->save();
1027 + return $page;
1028 + }
1029 +
1030 + // Otherwise save the data to a revision
1031 + $userId = user()->id;
1032 + $drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get();
1033 +
1034 + if ($drafts->count() > 0) {
1035 + $draft = $drafts->first();
1036 + } else {
1037 + $draft = $this->pageRevision->newInstance();
1038 + $draft->page_id = $page->id;
1039 + $draft->slug = $page->slug;
1040 + $draft->book_slug = $page->book->slug;
1041 + $draft->created_by = $userId;
1042 + $draft->type = 'update_draft';
1043 + }
1044 +
1045 + $draft->fill($data);
1046 + if (setting('app-editor') !== 'markdown') $draft->markdown = '';
1047 +
1048 + $draft->save();
1049 + return $draft;
1050 + }
1051 +
1052 + /**
1053 + * Get a notification message concerning the editing activity on a particular page.
1054 + * @param Page $page
1055 + * @param null $minRange
1056 + * @return string
1057 + */
1058 + public function getPageEditingActiveMessage(Page $page, $minRange = null)
1059 + {
1060 + $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
1061 +
1062 + $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
1063 + $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
1064 + return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
1065 + }
1066 +
1067 + /**
1068 + * Change the page's parent to the given entity.
1069 + * @param Page $page
1070 + * @param Entity $parent
1071 + */
1072 + public function changePageParent(Page $page, Entity $parent)
1073 + {
1074 + $book = $parent->isA('book') ? $parent : $parent->book;
1075 + $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
1076 + $page->save();
1077 + if ($page->book->id !== $book->id) {
1078 + $page = $this->changeBook('page', $book->id, $page);
1079 + }
1080 + $page->load('book');
1081 + $this->permissionService->buildJointPermissionsForEntity($book);
1082 + }
1083 +
1084 + /**
1085 + * Destroy the provided book and all its child entities.
1086 + * @param Book $book
1087 + */
1088 + public function destroyBook(Book $book)
1089 + {
1090 + foreach ($book->pages as $page) {
1091 + $this->destroyPage($page);
1092 + }
1093 + foreach ($book->chapters as $chapter) {
1094 + $this->destroyChapter($chapter);
1095 + }
1096 + \Activity::removeEntity($book);
1097 + $book->views()->delete();
1098 + $book->permissions()->delete();
1099 + $this->permissionService->deleteJointPermissionsForEntity($book);
1100 + $book->delete();
1101 + }
1102 +
1103 + /**
1104 + * Destroy a chapter and its relations.
1105 + * @param Chapter $chapter
1106 + */
1107 + public function destroyChapter(Chapter $chapter)
1108 + {
1109 + if (count($chapter->pages) > 0) {
1110 + foreach ($chapter->pages as $page) {
1111 + $page->chapter_id = 0;
1112 + $page->save();
1113 + }
1114 + }
1115 + \Activity::removeEntity($chapter);
1116 + $chapter->views()->delete();
1117 + $chapter->permissions()->delete();
1118 + $this->permissionService->deleteJointPermissionsForEntity($chapter);
1119 + $chapter->delete();
1120 + }
1121 +
1122 + /**
1123 + * Destroy a given page along with its dependencies.
1124 + * @param Page $page
1125 + */
1126 + public function destroyPage(Page $page)
1127 + {
1128 + \Activity::removeEntity($page);
1129 + $page->views()->delete();
1130 + $page->tags()->delete();
1131 + $page->revisions()->delete();
1132 + $page->permissions()->delete();
1133 + $this->permissionService->deleteJointPermissionsForEntity($page);
1134 +
1135 + // Delete Attached Files
1136 + $attachmentService = app(AttachmentService::class);
1137 + foreach ($page->attachments as $attachment) {
1138 + $attachmentService->deleteFile($attachment);
1139 + }
1140 +
1141 + $page->delete();
1142 + }
1143 +
285 } 1144 }
286 1145
287 1146
......
1 -<?php namespace BookStack\Repos;
2 -
3 -use Activity;
4 -use BookStack\Book;
5 -use BookStack\Chapter;
6 -use BookStack\Entity;
7 -use BookStack\Exceptions\NotFoundException;
8 -use BookStack\Services\AttachmentService;
9 -use Carbon\Carbon;
10 -use DOMDocument;
11 -use DOMXPath;
12 -use Illuminate\Support\Str;
13 -use BookStack\Page;
14 -use BookStack\PageRevision;
15 -
16 -class PageRepo extends EntityRepo
17 -{
18 -
19 - protected $pageRevision;
20 - protected $tagRepo;
21 -
22 - /**
23 - * PageRepo constructor.
24 - * @param PageRevision $pageRevision
25 - * @param TagRepo $tagRepo
26 - */
27 - public function __construct(PageRevision $pageRevision, TagRepo $tagRepo)
28 - {
29 - $this->pageRevision = $pageRevision;
30 - $this->tagRepo = $tagRepo;
31 - parent::__construct();
32 - }
33 -
34 - /**
35 - * Base query for getting pages, Takes restrictions into account.
36 - * @param bool $allowDrafts
37 - * @return mixed
38 - */
39 - private function pageQuery($allowDrafts = false)
40 - {
41 - $query = $this->permissionService->enforcePageRestrictions($this->page, 'view');
42 - if (!$allowDrafts) {
43 - $query = $query->where('draft', '=', false);
44 - }
45 - return $query;
46 - }
47 -
48 - /**
49 - * Get a page via a specific ID.
50 - * @param $id
51 - * @param bool $allowDrafts
52 - * @return Page
53 - */
54 - public function getById($id, $allowDrafts = false)
55 - {
56 - return $this->pageQuery($allowDrafts)->findOrFail($id);
57 - }
58 -
59 - /**
60 - * Get a page identified by the given slug.
61 - * @param $slug
62 - * @param $bookId
63 - * @return Page
64 - * @throws NotFoundException
65 - */
66 - public function getBySlug($slug, $bookId)
67 - {
68 - $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
69 - if ($page === null) throw new NotFoundException(trans('errors.page_not_found'));
70 - return $page;
71 - }
72 -
73 - /**
74 - * Search through page revisions and retrieve
75 - * the last page in the current book that
76 - * has a slug equal to the one given.
77 - * @param $pageSlug
78 - * @param $bookSlug
79 - * @return null | Page
80 - */
81 - public function findPageUsingOldSlug($pageSlug, $bookSlug)
82 - {
83 - $revision = $this->pageRevision->where('slug', '=', $pageSlug)
84 - ->whereHas('page', function ($query) {
85 - $this->permissionService->enforcePageRestrictions($query);
86 - })
87 - ->where('type', '=', 'version')
88 - ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc')
89 - ->with('page')->first();
90 - return $revision !== null ? $revision->page : null;
91 - }
92 -
93 - /**
94 - * Get a new Page instance from the given input.
95 - * @param $input
96 - * @return Page
97 - */
98 - public function newFromInput($input)
99 - {
100 - $page = $this->page->fill($input);
101 - return $page;
102 - }
103 -
104 - /**
105 - * Count the pages with a particular slug within a book.
106 - * @param $slug
107 - * @param $bookId
108 - * @return mixed
109 - */
110 - public function countBySlug($slug, $bookId)
111 - {
112 - return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count();
113 - }
114 -
115 - /**
116 - * Publish a draft page to make it a normal page.
117 - * Sets the slug and updates the content.
118 - * @param Page $draftPage
119 - * @param array $input
120 - * @return Page
121 - */
122 - public function publishDraft(Page $draftPage, array $input)
123 - {
124 - $draftPage->fill($input);
125 -
126 - // Save page tags if present
127 - if (isset($input['tags'])) {
128 - $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
129 - }
130 -
131 - $draftPage->slug = $this->findSuitableSlug($draftPage->name, $draftPage->book->id);
132 - $draftPage->html = $this->formatHtml($input['html']);
133 - $draftPage->text = strip_tags($draftPage->html);
134 - $draftPage->draft = false;
135 -
136 - $draftPage->save();
137 - $this->saveRevision($draftPage, trans('entities.pages_initial_revision'));
138 -
139 - return $draftPage;
140 - }
141 -
142 - /**
143 - * Get a new draft page instance.
144 - * @param Book $book
145 - * @param Chapter|bool $chapter
146 - * @return Page
147 - */
148 - public function getDraftPage(Book $book, $chapter = false)
149 - {
150 - $page = $this->page->newInstance();
151 - $page->name = trans('entities.pages_initial_name');
152 - $page->created_by = user()->id;
153 - $page->updated_by = user()->id;
154 - $page->draft = true;
155 -
156 - if ($chapter) $page->chapter_id = $chapter->id;
157 -
158 - $book->pages()->save($page);
159 - $this->permissionService->buildJointPermissionsForEntity($page);
160 - return $page;
161 - }
162 -
163 - /**
164 - * Parse te headers on the page to get a navigation menu
165 - * @param Page $page
166 - * @return array
167 - */
168 - public function getPageNav(Page $page)
169 - {
170 - if ($page->html == '') return null;
171 - libxml_use_internal_errors(true);
172 - $doc = new DOMDocument();
173 - $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8'));
174 - $xPath = new DOMXPath($doc);
175 - $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
176 -
177 - if (is_null($headers)) return null;
178 -
179 - $tree = [];
180 - foreach ($headers as $header) {
181 - $text = $header->nodeValue;
182 - $tree[] = [
183 - 'nodeName' => strtolower($header->nodeName),
184 - 'level' => intval(str_replace('h', '', $header->nodeName)),
185 - 'link' => '#' . $header->getAttribute('id'),
186 - 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
187 - ];
188 - }
189 - return $tree;
190 - }
191 -
192 - /**
193 - * Formats a page's html to be tagged correctly
194 - * within the system.
195 - * @param string $htmlText
196 - * @return string
197 - */
198 - protected function formatHtml($htmlText)
199 - {
200 - if ($htmlText == '') return $htmlText;
201 - libxml_use_internal_errors(true);
202 - $doc = new DOMDocument();
203 - $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
204 -
205 - $container = $doc->documentElement;
206 - $body = $container->childNodes->item(0);
207 - $childNodes = $body->childNodes;
208 -
209 - // Ensure no duplicate ids are used
210 - $idArray = [];
211 -
212 - foreach ($childNodes as $index => $childNode) {
213 - /** @var \DOMElement $childNode */
214 - if (get_class($childNode) !== 'DOMElement') continue;
215 -
216 - // Overwrite id if not a BookStack custom id
217 - if ($childNode->hasAttribute('id')) {
218 - $id = $childNode->getAttribute('id');
219 - if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
220 - $idArray[] = $id;
221 - continue;
222 - };
223 - }
224 -
225 - // Create an unique id for the element
226 - // Uses the content as a basis to ensure output is the same every time
227 - // the same content is passed through.
228 - $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
229 - $newId = urlencode($contentId);
230 - $loopIndex = 0;
231 - while (in_array($newId, $idArray)) {
232 - $newId = urlencode($contentId . '-' . $loopIndex);
233 - $loopIndex++;
234 - }
235 -
236 - $childNode->setAttribute('id', $newId);
237 - $idArray[] = $newId;
238 - }
239 -
240 - // Generate inner html as a string
241 - $html = '';
242 - foreach ($childNodes as $childNode) {
243 - $html .= $doc->saveHTML($childNode);
244 - }
245 -
246 - return $html;
247 - }
248 -
249 -
250 - /**
251 - * Gets pages by a search term.
252 - * Highlights page content for showing in results.
253 - * @param string $term
254 - * @param array $whereTerms
255 - * @param int $count
256 - * @param array $paginationAppends
257 - * @return mixed
258 - */
259 - public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
260 - {
261 - $terms = $this->prepareSearchTerms($term);
262 - $pageQuery = $this->permissionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms));
263 - $pageQuery = $this->addAdvancedSearchQueries($pageQuery, $term);
264 - $pages = $pageQuery->paginate($count)->appends($paginationAppends);
265 -
266 - // Add highlights to page text.
267 - $words = join('|', explode(' ', preg_quote(trim($term), '/')));
268 - //lookahead/behind assertions ensures cut between words
269 - $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
270 -
271 - foreach ($pages as $page) {
272 - preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
273 - //delimiter between occurrences
274 - $results = [];
275 - foreach ($matches as $line) {
276 - $results[] = htmlspecialchars($line[0], 0, 'UTF-8');
277 - }
278 - $matchLimit = 6;
279 - if (count($results) > $matchLimit) {
280 - $results = array_slice($results, 0, $matchLimit);
281 - }
282 - $result = join('... ', $results);
283 -
284 - //highlight
285 - $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $result);
286 - if (strlen($result) < 5) {
287 - $result = $page->getExcerpt(80);
288 - }
289 - $page->searchSnippet = $result;
290 - }
291 - return $pages;
292 - }
293 -
294 - /**
295 - * Search for image usage.
296 - * @param $imageString
297 - * @return mixed
298 - */
299 - public function searchForImage($imageString)
300 - {
301 - $pages = $this->pageQuery()->where('html', 'like', '%' . $imageString . '%')->get();
302 - foreach ($pages as $page) {
303 - $page->url = $page->getUrl();
304 - $page->html = '';
305 - $page->text = '';
306 - }
307 - return count($pages) > 0 ? $pages : false;
308 - }
309 -
310 - /**
311 - * Updates a page with any fillable data and saves it into the database.
312 - * @param Page $page
313 - * @param int $book_id
314 - * @param string $input
315 - * @return Page
316 - */
317 - public function updatePage(Page $page, $book_id, $input)
318 - {
319 - // Hold the old details to compare later
320 - $oldHtml = $page->html;
321 - $oldName = $page->name;
322 -
323 - // Prevent slug being updated if no name change
324 - if ($page->name !== $input['name']) {
325 - $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id);
326 - }
327 -
328 - // Save page tags if present
329 - if (isset($input['tags'])) {
330 - $this->tagRepo->saveTagsToEntity($page, $input['tags']);
331 - }
332 -
333 - // Update with new details
334 - $userId = user()->id;
335 - $page->fill($input);
336 - $page->html = $this->formatHtml($input['html']);
337 - $page->text = strip_tags($page->html);
338 - if (setting('app-editor') !== 'markdown') $page->markdown = '';
339 - $page->updated_by = $userId;
340 - $page->save();
341 -
342 - // Remove all update drafts for this user & page.
343 - $this->userUpdateDraftsQuery($page, $userId)->delete();
344 -
345 - // Save a revision after updating
346 - if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
347 - $this->saveRevision($page, $input['summary']);
348 - }
349 -
350 - return $page;
351 - }
352 -
353 - /**
354 - * Restores a revision's content back into a page.
355 - * @param Page $page
356 - * @param Book $book
357 - * @param int $revisionId
358 - * @return Page
359 - */
360 - public function restoreRevision(Page $page, Book $book, $revisionId)
361 - {
362 - $this->saveRevision($page);
363 - $revision = $this->getRevisionById($revisionId);
364 - $page->fill($revision->toArray());
365 - $page->slug = $this->findSuitableSlug($page->name, $book->id, $page->id);
366 - $page->text = strip_tags($page->html);
367 - $page->updated_by = user()->id;
368 - $page->save();
369 - return $page;
370 - }
371 -
372 - /**
373 - * Saves a page revision into the system.
374 - * @param Page $page
375 - * @param null|string $summary
376 - * @return $this
377 - */
378 - public function saveRevision(Page $page, $summary = null)
379 - {
380 - $revision = $this->pageRevision->newInstance($page->toArray());
381 - if (setting('app-editor') !== 'markdown') $revision->markdown = '';
382 - $revision->page_id = $page->id;
383 - $revision->slug = $page->slug;
384 - $revision->book_slug = $page->book->slug;
385 - $revision->created_by = user()->id;
386 - $revision->created_at = $page->updated_at;
387 - $revision->type = 'version';
388 - $revision->summary = $summary;
389 - $revision->save();
390 -
391 - // Clear old revisions
392 - if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
393 - $this->pageRevision->where('page_id', '=', $page->id)
394 - ->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
395 - }
396 -
397 - return $revision;
398 - }
399 -
400 - /**
401 - * Save a page update draft.
402 - * @param Page $page
403 - * @param array $data
404 - * @return PageRevision
405 - */
406 - public function saveUpdateDraft(Page $page, $data = [])
407 - {
408 - $userId = user()->id;
409 - $drafts = $this->userUpdateDraftsQuery($page, $userId)->get();
410 -
411 - if ($drafts->count() > 0) {
412 - $draft = $drafts->first();
413 - } else {
414 - $draft = $this->pageRevision->newInstance();
415 - $draft->page_id = $page->id;
416 - $draft->slug = $page->slug;
417 - $draft->book_slug = $page->book->slug;
418 - $draft->created_by = $userId;
419 - $draft->type = 'update_draft';
420 - }
421 -
422 - $draft->fill($data);
423 - if (setting('app-editor') !== 'markdown') $draft->markdown = '';
424 -
425 - $draft->save();
426 - return $draft;
427 - }
428 -
429 - /**
430 - * Update a draft page.
431 - * @param Page $page
432 - * @param array $data
433 - * @return Page
434 - */
435 - public function updateDraftPage(Page $page, $data = [])
436 - {
437 - $page->fill($data);
438 -
439 - if (isset($data['html'])) {
440 - $page->text = strip_tags($data['html']);
441 - }
442 -
443 - $page->save();
444 - return $page;
445 - }
446 -
447 - /**
448 - * The base query for getting user update drafts.
449 - * @param Page $page
450 - * @param $userId
451 - * @return mixed
452 - */
453 - private function userUpdateDraftsQuery(Page $page, $userId)
454 - {
455 - return $this->pageRevision->where('created_by', '=', $userId)
456 - ->where('type', 'update_draft')
457 - ->where('page_id', '=', $page->id)
458 - ->orderBy('created_at', 'desc');
459 - }
460 -
461 - /**
462 - * Checks whether a user has a draft version of a particular page or not.
463 - * @param Page $page
464 - * @param $userId
465 - * @return bool
466 - */
467 - public function hasUserGotPageDraft(Page $page, $userId)
468 - {
469 - return $this->userUpdateDraftsQuery($page, $userId)->count() > 0;
470 - }
471 -
472 - /**
473 - * Get the latest updated draft revision for a particular page and user.
474 - * @param Page $page
475 - * @param $userId
476 - * @return mixed
477 - */
478 - public function getUserPageDraft(Page $page, $userId)
479 - {
480 - return $this->userUpdateDraftsQuery($page, $userId)->first();
481 - }
482 -
483 - /**
484 - * Get the notification message that informs the user that they are editing a draft page.
485 - * @param PageRevision $draft
486 - * @return string
487 - */
488 - public function getUserPageDraftMessage(PageRevision $draft)
489 - {
490 - $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
491 - if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) return $message;
492 - return $message . "\n" . trans('entities.pages_draft_edited_notification');
493 - }
494 -
495 - /**
496 - * Check if a page is being actively editing.
497 - * Checks for edits since last page updated.
498 - * Passing in a minuted range will check for edits
499 - * within the last x minutes.
500 - * @param Page $page
501 - * @param null $minRange
502 - * @return bool
503 - */
504 - public function isPageEditingActive(Page $page, $minRange = null)
505 - {
506 - $draftSearch = $this->activePageEditingQuery($page, $minRange);
507 - return $draftSearch->count() > 0;
508 - }
509 -
510 - /**
511 - * Get a notification message concerning the editing activity on
512 - * a particular page.
513 - * @param Page $page
514 - * @param null $minRange
515 - * @return string
516 - */
517 - public function getPageEditingActiveMessage(Page $page, $minRange = null)
518 - {
519 - $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
520 -
521 - $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
522 - $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
523 - return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
524 - }
525 -
526 - /**
527 - * A query to check for active update drafts on a particular page.
528 - * @param Page $page
529 - * @param null $minRange
530 - * @return mixed
531 - */
532 - private function activePageEditingQuery(Page $page, $minRange = null)
533 - {
534 - $query = $this->pageRevision->where('type', '=', 'update_draft')
535 - ->where('page_id', '=', $page->id)
536 - ->where('updated_at', '>', $page->updated_at)
537 - ->where('created_by', '!=', user()->id)
538 - ->with('createdBy');
539 -
540 - if ($minRange !== null) {
541 - $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
542 - }
543 -
544 - return $query;
545 - }
546 -
547 - /**
548 - * Gets a single revision via it's id.
549 - * @param $id
550 - * @return PageRevision
551 - */
552 - public function getRevisionById($id)
553 - {
554 - return $this->pageRevision->findOrFail($id);
555 - }
556 -
557 - /**
558 - * Checks if a slug exists within a book already.
559 - * @param $slug
560 - * @param $bookId
561 - * @param bool|false $currentId
562 - * @return bool
563 - */
564 - public function doesSlugExist($slug, $bookId, $currentId = false)
565 - {
566 - $query = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId);
567 - if ($currentId) $query = $query->where('id', '!=', $currentId);
568 - return $query->count() > 0;
569 - }
570 -
571 - /**
572 - * Changes the related book for the specified page.
573 - * Changes the book id of any relations to the page that store the book id.
574 - * @param int $bookId
575 - * @param Page $page
576 - * @return Page
577 - */
578 - public function changeBook($bookId, Page $page)
579 - {
580 - $page->book_id = $bookId;
581 - foreach ($page->activity as $activity) {
582 - $activity->book_id = $bookId;
583 - $activity->save();
584 - }
585 - $page->slug = $this->findSuitableSlug($page->name, $bookId, $page->id);
586 - $page->save();
587 - return $page;
588 - }
589 -
590 -
591 - /**
592 - * Change the page's parent to the given entity.
593 - * @param Page $page
594 - * @param Entity $parent
595 - */
596 - public function changePageParent(Page $page, Entity $parent)
597 - {
598 - $book = $parent->isA('book') ? $parent : $parent->book;
599 - $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
600 - $page->save();
601 - $page = $this->changeBook($book->id, $page);
602 - $page->load('book');
603 - $this->permissionService->buildJointPermissionsForEntity($book);
604 - }
605 -
606 - /**
607 - * Gets a suitable slug for the resource
608 - * @param string $name
609 - * @param int $bookId
610 - * @param bool|false $currentId
611 - * @return string
612 - */
613 - public function findSuitableSlug($name, $bookId, $currentId = false)
614 - {
615 - $slug = $this->nameToSlug($name);
616 - while ($this->doesSlugExist($slug, $bookId, $currentId)) {
617 - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
618 - }
619 - return $slug;
620 - }
621 -
622 - /**
623 - * Destroy a given page along with its dependencies.
624 - * @param $page
625 - */
626 - public function destroy(Page $page)
627 - {
628 - Activity::removeEntity($page);
629 - $page->views()->delete();
630 - $page->tags()->delete();
631 - $page->revisions()->delete();
632 - $page->permissions()->delete();
633 - $this->permissionService->deleteJointPermissionsForEntity($page);
634 -
635 - // Delete AttachedFiles
636 - $attachmentService = app(AttachmentService::class);
637 - foreach ($page->attachments as $attachment) {
638 - $attachmentService->deleteFile($attachment);
639 - }
640 -
641 - $page->delete();
642 - }
643 -
644 - /**
645 - * Get the latest pages added to the system.
646 - * @param $count
647 - * @return mixed
648 - */
649 - public function getRecentlyCreatedPaginated($count = 20)
650 - {
651 - return $this->pageQuery()->orderBy('created_at', 'desc')->paginate($count);
652 - }
653 -
654 - /**
655 - * Get the latest pages added to the system.
656 - * @param $count
657 - * @return mixed
658 - */
659 - public function getRecentlyUpdatedPaginated($count = 20)
660 - {
661 - return $this->pageQuery()->orderBy('updated_at', 'desc')->paginate($count);
662 - }
663 -
664 -}
...@@ -38,7 +38,7 @@ class TagRepo ...@@ -38,7 +38,7 @@ class TagRepo
38 { 38 {
39 $entityInstance = $this->entity->getEntityInstance($entityType); 39 $entityInstance = $this->entity->getEntityInstance($entityType);
40 $searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags'); 40 $searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags');
41 - $searchQuery = $this->permissionService->enforceEntityRestrictions($searchQuery, $action); 41 + $searchQuery = $this->permissionService->enforceEntityRestrictions($entityType, $searchQuery, $action);
42 return $searchQuery->first(); 42 return $searchQuery->first();
43 } 43 }
44 44
......
...@@ -168,13 +168,13 @@ class UserRepo ...@@ -168,13 +168,13 @@ class UserRepo
168 public function getRecentlyCreated(User $user, $count = 20) 168 public function getRecentlyCreated(User $user, $count = 20)
169 { 169 {
170 return [ 170 return [
171 - 'pages' => $this->entityRepo->getRecentlyCreatedPages($count, 0, function ($query) use ($user) { 171 + 'pages' => $this->entityRepo->getRecentlyCreated('page', $count, 0, function ($query) use ($user) {
172 $query->where('created_by', '=', $user->id); 172 $query->where('created_by', '=', $user->id);
173 }), 173 }),
174 - 'chapters' => $this->entityRepo->getRecentlyCreatedChapters($count, 0, function ($query) use ($user) { 174 + 'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, function ($query) use ($user) {
175 $query->where('created_by', '=', $user->id); 175 $query->where('created_by', '=', $user->id);
176 }), 176 }),
177 - 'books' => $this->entityRepo->getRecentlyCreatedBooks($count, 0, function ($query) use ($user) { 177 + 'books' => $this->entityRepo->getRecentlyCreated('book', $count, 0, function ($query) use ($user) {
178 $query->where('created_by', '=', $user->id); 178 $query->where('created_by', '=', $user->id);
179 }) 179 })
180 ]; 180 ];
......
...@@ -114,7 +114,7 @@ class ActivityService ...@@ -114,7 +114,7 @@ class ActivityService
114 114
115 $activity = $this->permissionService 115 $activity = $this->permissionService
116 ->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type') 116 ->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type')
117 - ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); 117 + ->orderBy('created_at', 'desc')->with(['entity', 'user.avatar'])->skip($count * $page)->take($count)->get();
118 118
119 return $this->filterSimilar($activity); 119 return $this->filterSimilar($activity);
120 } 120 }
......
...@@ -8,8 +8,9 @@ use BookStack\Ownable; ...@@ -8,8 +8,9 @@ use BookStack\Ownable;
8 use BookStack\Page; 8 use BookStack\Page;
9 use BookStack\Role; 9 use BookStack\Role;
10 use BookStack\User; 10 use BookStack\User;
11 +use Illuminate\Database\Connection;
12 +use Illuminate\Database\Eloquent\Builder;
11 use Illuminate\Support\Collection; 13 use Illuminate\Support\Collection;
12 -use Illuminate\Support\Facades\Log;
13 14
14 class PermissionService 15 class PermissionService
15 { 16 {
...@@ -23,6 +24,8 @@ class PermissionService ...@@ -23,6 +24,8 @@ class PermissionService
23 public $chapter; 24 public $chapter;
24 public $page; 25 public $page;
25 26
27 + protected $db;
28 +
26 protected $jointPermission; 29 protected $jointPermission;
27 protected $role; 30 protected $role;
28 31
...@@ -31,18 +34,21 @@ class PermissionService ...@@ -31,18 +34,21 @@ class PermissionService
31 /** 34 /**
32 * PermissionService constructor. 35 * PermissionService constructor.
33 * @param JointPermission $jointPermission 36 * @param JointPermission $jointPermission
37 + * @param Connection $db
34 * @param Book $book 38 * @param Book $book
35 * @param Chapter $chapter 39 * @param Chapter $chapter
36 * @param Page $page 40 * @param Page $page
37 * @param Role $role 41 * @param Role $role
38 */ 42 */
39 - public function __construct(JointPermission $jointPermission, Book $book, Chapter $chapter, Page $page, Role $role) 43 + public function __construct(JointPermission $jointPermission, Connection $db, Book $book, Chapter $chapter, Page $page, Role $role)
40 { 44 {
45 + $this->db = $db;
41 $this->jointPermission = $jointPermission; 46 $this->jointPermission = $jointPermission;
42 $this->role = $role; 47 $this->role = $role;
43 $this->book = $book; 48 $this->book = $book;
44 $this->chapter = $chapter; 49 $this->chapter = $chapter;
45 $this->page = $page; 50 $this->page = $page;
51 + // TODO - Update so admin still goes through filters
46 } 52 }
47 53
48 /** 54 /**
...@@ -302,6 +308,10 @@ class PermissionService ...@@ -302,6 +308,10 @@ class PermissionService
302 $explodedAction = explode('-', $action); 308 $explodedAction = explode('-', $action);
303 $restrictionAction = end($explodedAction); 309 $restrictionAction = end($explodedAction);
304 310
311 + if ($role->system_name === 'admin') {
312 + return $this->createJointPermissionDataArray($entity, $role, $action, true, true);
313 + }
314 +
305 if ($entity->isA('book')) { 315 if ($entity->isA('book')) {
306 316
307 if (!$entity->restricted) { 317 if (!$entity->restricted) {
...@@ -461,14 +471,61 @@ class PermissionService ...@@ -461,14 +471,61 @@ class PermissionService
461 return $q; 471 return $q;
462 } 472 }
463 473
474 + public function bookChildrenQuery($book_id, $filterDrafts = false) {
475 +
476 + // Draft setup
477 + $params = [
478 + 'userId' => $this->currentUser()->id,
479 + 'bookIdPage' => $book_id,
480 + 'bookIdChapter' => $book_id
481 + ];
482 + if (!$filterDrafts) {
483 + $params['userIdDrafts'] = $this->currentUser()->id;
484 + }
485 + // Role setup
486 + $userRoles = $this->getRoles();
487 + $roleBindings = [];
488 + $roleValues = [];
489 + foreach ($userRoles as $index => $roleId) {
490 + $roleBindings[':role'.$index] = $roleId;
491 + $roleValues['role'.$index] = $roleId;
492 + }
493 + // TODO - Clean this up, Maybe extract into a nice class for doing these kind of manual things
494 + // Something which will handle the above role crap in a nice clean way
495 + $roleBindingString = implode(',', array_keys($roleBindings));
496 + $query = "SELECT * from (
497 +(SELECT 'Bookstack\\\Page' as entity_type, id, slug, name, text, '' as description, book_id, priority, chapter_id, draft FROM {$this->page->getTable()}
498 + where book_id = :bookIdPage AND ". ($filterDrafts ? '(draft = 0)' : '(draft = 0 OR (draft = 1 AND created_by = :userIdDrafts))') .")
499 +UNION
500 +(SELECT 'Bookstack\\\Chapter' as entity_type, id, slug, name, '' as text, description, book_id, priority, 0 as chapter_id, 0 as draft FROM {$this->chapter->getTable()} WHERE book_id = :bookIdChapter)
501 +) as U WHERE (
502 + SELECT COUNT(*) FROM {$this->jointPermission->getTable()} jp
503 + WHERE
504 + jp.entity_id=U.id AND
505 + jp.entity_type=U.entity_type AND
506 + jp.action = 'view' AND
507 + jp.role_id IN ({$roleBindingString}) AND
508 + (
509 + jp.has_permission = 1 OR
510 + (jp.has_permission_own = 1 AND jp.created_by = :userId)
511 + )
512 +) > 0
513 +ORDER BY draft desc, priority asc";
514 +
515 + $this->clean();
516 + return $this->db->select($query, array_replace($roleValues, $params));
517 + }
518 +
464 /** 519 /**
465 - * Add restrictions for a page query 520 + * Add restrictions for a generic entity
466 - * @param $query 521 + * @param string $entityType
522 + * @param Builder|Entity $query
467 * @param string $action 523 * @param string $action
468 * @return mixed 524 * @return mixed
469 */ 525 */
470 - public function enforcePageRestrictions($query, $action = 'view') 526 + public function enforceEntityRestrictions($entityType, $query, $action = 'view')
471 { 527 {
528 + if (strtolower($entityType) === 'page') {
472 // Prevent drafts being visible to others. 529 // Prevent drafts being visible to others.
473 $query = $query->where(function ($query) { 530 $query = $query->where(function ($query) {
474 $query->where('draft', '=', false); 531 $query->where('draft', '=', false);
...@@ -478,44 +535,13 @@ class PermissionService ...@@ -478,44 +535,13 @@ class PermissionService
478 }); 535 });
479 } 536 }
480 }); 537 });
481 -
482 - return $this->enforceEntityRestrictions($query, $action);
483 - }
484 -
485 - /**
486 - * Add on permission restrictions to a chapter query.
487 - * @param $query
488 - * @param string $action
489 - * @return mixed
490 - */
491 - public function enforceChapterRestrictions($query, $action = 'view')
492 - {
493 - return $this->enforceEntityRestrictions($query, $action);
494 - }
495 -
496 - /**
497 - * Add restrictions to a book query.
498 - * @param $query
499 - * @param string $action
500 - * @return mixed
501 - */
502 - public function enforceBookRestrictions($query, $action = 'view')
503 - {
504 - return $this->enforceEntityRestrictions($query, $action);
505 } 538 }
506 539
507 - /**
508 - * Add restrictions for a generic entity
509 - * @param $query
510 - * @param string $action
511 - * @return mixed
512 - */
513 - public function enforceEntityRestrictions($query, $action = 'view')
514 - {
515 if ($this->isAdmin()) { 540 if ($this->isAdmin()) {
516 $this->clean(); 541 $this->clean();
517 return $query; 542 return $query;
518 } 543 }
544 +
519 $this->currentAction = $action; 545 $this->currentAction = $action;
520 return $this->entityRestrictionQuery($query); 546 return $this->entityRestrictionQuery($query);
521 } 547 }
...@@ -601,7 +627,7 @@ class PermissionService ...@@ -601,7 +627,7 @@ class PermissionService
601 private function isAdmin() 627 private function isAdmin()
602 { 628 {
603 if ($this->isAdminUser === null) { 629 if ($this->isAdminUser === null) {
604 - $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasRole('admin') : false; 630 + $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasSystemRole('admin') : false;
605 } 631 }
606 632
607 return $this->isAdminUser; 633 return $this->isAdminUser;
......
...@@ -5,9 +5,7 @@ use BookStack\View; ...@@ -5,9 +5,7 @@ use BookStack\View;
5 5
6 class ViewService 6 class ViewService
7 { 7 {
8 -
9 protected $view; 8 protected $view;
10 - protected $user;
11 protected $permissionService; 9 protected $permissionService;
12 10
13 /** 11 /**
...@@ -18,7 +16,6 @@ class ViewService ...@@ -18,7 +16,6 @@ class ViewService
18 public function __construct(View $view, PermissionService $permissionService) 16 public function __construct(View $view, PermissionService $permissionService)
19 { 17 {
20 $this->view = $view; 18 $this->view = $view;
21 - $this->user = user();
22 $this->permissionService = $permissionService; 19 $this->permissionService = $permissionService;
23 } 20 }
24 21
...@@ -29,8 +26,9 @@ class ViewService ...@@ -29,8 +26,9 @@ class ViewService
29 */ 26 */
30 public function add(Entity $entity) 27 public function add(Entity $entity)
31 { 28 {
32 - if ($this->user === null) return 0; 29 + $user = user();
33 - $view = $entity->views()->where('user_id', '=', $this->user->id)->first(); 30 + if ($user === null || $user->isDefault()) return 0;
31 + $view = $entity->views()->where('user_id', '=', $user->id)->first();
34 // Add view if model exists 32 // Add view if model exists
35 if ($view) { 33 if ($view) {
36 $view->increment('views'); 34 $view->increment('views');
...@@ -39,7 +37,7 @@ class ViewService ...@@ -39,7 +37,7 @@ class ViewService
39 37
40 // Otherwise create new view count 38 // Otherwise create new view count
41 $entity->views()->save($this->view->create([ 39 $entity->views()->save($this->view->create([
42 - 'user_id' => $this->user->id, 40 + 'user_id' => $user->id,
43 'views' => 1 41 'views' => 1
44 ])); 42 ]));
45 43
...@@ -78,13 +76,14 @@ class ViewService ...@@ -78,13 +76,14 @@ class ViewService
78 */ 76 */
79 public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false) 77 public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false)
80 { 78 {
81 - if ($this->user === null) return collect(); 79 + $user = user();
80 + if ($user === null || $user->isDefault()) return collect();
82 81
83 $query = $this->permissionService 82 $query = $this->permissionService
84 ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type'); 83 ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
85 84
86 if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel)); 85 if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel));
87 - $query = $query->where('user_id', '=', user()->id); 86 + $query = $query->where('user_id', '=', $user->id);
88 87
89 $viewables = $query->with('viewable')->orderBy('updated_at', 'desc') 88 $viewables = $query->with('viewable')->orderBy('updated_at', 'desc')
90 ->skip($count * $page)->take($count)->get()->pluck('viewable'); 89 ->skip($count * $page)->take($count)->get()->pluck('viewable');
......
...@@ -75,6 +75,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -75,6 +75,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
75 } 75 }
76 76
77 /** 77 /**
78 + * Check if the user has a role.
79 + * @param $role
80 + * @return mixed
81 + */
82 + public function hasSystemRole($role)
83 + {
84 + return $this->roles->pluck('system_name')->contains('admin');
85 + }
86 +
87 + /**
78 * Get all permissions belonging to a the current user. 88 * Get all permissions belonging to a the current user.
79 * @param bool $cache 89 * @param bool $cache
80 * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough 90 * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
......
...@@ -168,7 +168,7 @@ class EntityTest extends TestCase ...@@ -168,7 +168,7 @@ class EntityTest extends TestCase
168 $entities = $this->createEntityChainBelongingToUser($creator, $updater); 168 $entities = $this->createEntityChainBelongingToUser($creator, $updater);
169 $this->actingAs($creator); 169 $this->actingAs($creator);
170 app('BookStack\Repos\UserRepo')->destroy($creator); 170 app('BookStack\Repos\UserRepo')->destroy($creator);
171 - app('BookStack\Repos\PageRepo')->saveRevision($entities['page']); 171 + app('BookStack\Repos\EntityRepo')->savePageRevision($entities['page']);
172 172
173 $this->checkEntitiesViewable($entities); 173 $this->checkEntitiesViewable($entities);
174 } 174 }
...@@ -181,7 +181,7 @@ class EntityTest extends TestCase ...@@ -181,7 +181,7 @@ class EntityTest extends TestCase
181 $entities = $this->createEntityChainBelongingToUser($creator, $updater); 181 $entities = $this->createEntityChainBelongingToUser($creator, $updater);
182 $this->actingAs($updater); 182 $this->actingAs($updater);
183 app('BookStack\Repos\UserRepo')->destroy($updater); 183 app('BookStack\Repos\UserRepo')->destroy($updater);
184 - app('BookStack\Repos\PageRepo')->saveRevision($entities['page']); 184 + app('BookStack\Repos\EntityRepo')->savePageRevision($entities['page']);
185 185
186 $this->checkEntitiesViewable($entities); 186 $this->checkEntitiesViewable($entities);
187 } 187 }
......
...@@ -4,13 +4,13 @@ ...@@ -4,13 +4,13 @@
4 class PageDraftTest extends TestCase 4 class PageDraftTest extends TestCase
5 { 5 {
6 protected $page; 6 protected $page;
7 - protected $pageRepo; 7 + protected $entityRepo;
8 8
9 public function setUp() 9 public function setUp()
10 { 10 {
11 parent::setUp(); 11 parent::setUp();
12 $this->page = \BookStack\Page::first(); 12 $this->page = \BookStack\Page::first();
13 - $this->pageRepo = app('\BookStack\Repos\PageRepo'); 13 + $this->entityRepo = app('\BookStack\Repos\EntityRepo');
14 } 14 }
15 15
16 public function test_draft_content_shows_if_available() 16 public function test_draft_content_shows_if_available()
...@@ -20,7 +20,7 @@ class PageDraftTest extends TestCase ...@@ -20,7 +20,7 @@ class PageDraftTest extends TestCase
20 ->dontSeeInField('html', $addedContent); 20 ->dontSeeInField('html', $addedContent);
21 21
22 $newContent = $this->page->html . $addedContent; 22 $newContent = $this->page->html . $addedContent;
23 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]); 23 + $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
24 $this->asAdmin()->visit($this->page->getUrl() . '/edit') 24 $this->asAdmin()->visit($this->page->getUrl() . '/edit')
25 ->seeInField('html', $newContent); 25 ->seeInField('html', $newContent);
26 } 26 }
...@@ -33,7 +33,7 @@ class PageDraftTest extends TestCase ...@@ -33,7 +33,7 @@ class PageDraftTest extends TestCase
33 33
34 $newContent = $this->page->html . $addedContent; 34 $newContent = $this->page->html . $addedContent;
35 $newUser = $this->getEditor(); 35 $newUser = $this->getEditor();
36 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]); 36 + $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
37 $this->actingAs($newUser)->visit($this->page->getUrl() . '/edit') 37 $this->actingAs($newUser)->visit($this->page->getUrl() . '/edit')
38 ->dontSeeInField('html', $newContent); 38 ->dontSeeInField('html', $newContent);
39 } 39 }
...@@ -41,7 +41,7 @@ class PageDraftTest extends TestCase ...@@ -41,7 +41,7 @@ class PageDraftTest extends TestCase
41 public function test_alert_message_shows_if_editing_draft() 41 public function test_alert_message_shows_if_editing_draft()
42 { 42 {
43 $this->asAdmin(); 43 $this->asAdmin();
44 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => 'test content']); 44 + $this->entityRepo->updatePageDraft($this->page, ['html' => 'test content']);
45 $this->asAdmin()->visit($this->page->getUrl() . '/edit') 45 $this->asAdmin()->visit($this->page->getUrl() . '/edit')
46 ->see('You are currently editing a draft'); 46 ->see('You are currently editing a draft');
47 } 47 }
...@@ -55,7 +55,7 @@ class PageDraftTest extends TestCase ...@@ -55,7 +55,7 @@ class PageDraftTest extends TestCase
55 55
56 $newContent = $this->page->html . $addedContent; 56 $newContent = $this->page->html . $addedContent;
57 $newUser = $this->getEditor(); 57 $newUser = $this->getEditor();
58 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]); 58 + $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
59 59
60 $this->actingAs($newUser) 60 $this->actingAs($newUser)
61 ->visit($this->page->getUrl() . '/edit') 61 ->visit($this->page->getUrl() . '/edit')
......
...@@ -13,8 +13,8 @@ class SortTest extends TestCase ...@@ -13,8 +13,8 @@ class SortTest extends TestCase
13 public function test_drafts_do_not_show_up() 13 public function test_drafts_do_not_show_up()
14 { 14 {
15 $this->asAdmin(); 15 $this->asAdmin();
16 - $pageRepo = app('\BookStack\Repos\PageRepo'); 16 + $entityRepo = app('\BookStack\Repos\EntityRepo');
17 - $draft = $pageRepo->getDraftPage($this->book); 17 + $draft = $entityRepo->getDraftPage($this->book);
18 18
19 $this->visit($this->book->getUrl()) 19 $this->visit($this->book->getUrl())
20 ->see($draft->name) 20 ->see($draft->name)
......
...@@ -90,7 +90,7 @@ class ImageTest extends TestCase ...@@ -90,7 +90,7 @@ class ImageTest extends TestCase
90 'type' => 'gallery' 90 'type' => 'gallery'
91 ]); 91 ]);
92 92
93 - $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has been deleted'); 93 + $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has not been deleted as expected');
94 } 94 }
95 95
96 } 96 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -65,9 +65,9 @@ class RestrictionsTest extends TestCase ...@@ -65,9 +65,9 @@ class RestrictionsTest extends TestCase
65 $this->forceVisit($bookUrl) 65 $this->forceVisit($bookUrl)
66 ->see('Book not found'); 66 ->see('Book not found');
67 $this->forceVisit($bookPage->getUrl()) 67 $this->forceVisit($bookPage->getUrl())
68 - ->see('Book not found'); 68 + ->see('Page not found');
69 $this->forceVisit($bookChapter->getUrl()) 69 $this->forceVisit($bookChapter->getUrl())
70 - ->see('Book not found'); 70 + ->see('Chapter not found');
71 71
72 $this->setEntityRestrictions($book, ['view']); 72 $this->setEntityRestrictions($book, ['view']);
73 73
......