]> BookStack Code Mirror - bookstack/blob - app/Repos/BookRepo.php
Updated all application urls to allow path prefix.
[bookstack] / app / Repos / BookRepo.php
1 <?php namespace BookStack\Repos;
2
3 use Alpha\B;
4 use BookStack\Exceptions\NotFoundException;
5 use Illuminate\Support\Str;
6 use BookStack\Book;
7 use Views;
8
9 class BookRepo extends EntityRepo
10 {
11     protected $pageRepo;
12     protected $chapterRepo;
13
14     /**
15      * BookRepo constructor.
16      * @param PageRepo $pageRepo
17      * @param ChapterRepo $chapterRepo
18      */
19     public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo)
20     {
21         $this->pageRepo = $pageRepo;
22         $this->chapterRepo = $chapterRepo;
23         parent::__construct();
24     }
25
26     /**
27      * Base query for getting books.
28      * Takes into account any restrictions.
29      * @return mixed
30      */
31     private function bookQuery()
32     {
33         return $this->permissionService->enforceBookRestrictions($this->book, 'view');
34     }
35
36     /**
37      * Get the book that has the given id.
38      * @param $id
39      * @return mixed
40      */
41     public function getById($id)
42     {
43         return $this->bookQuery()->findOrFail($id);
44     }
45
46     /**
47      * Get all books, Limited by count.
48      * @param int $count
49      * @return mixed
50      */
51     public function getAll($count = 10)
52     {
53         $bookQuery = $this->bookQuery()->orderBy('name', 'asc');
54         if (!$count) return $bookQuery->get();
55         return $bookQuery->take($count)->get();
56     }
57
58     /**
59      * Get all books paginated.
60      * @param int $count
61      * @return mixed
62      */
63     public function getAllPaginated($count = 10)
64     {
65         return $this->bookQuery()
66             ->orderBy('name', 'asc')->paginate($count);
67     }
68
69
70     /**
71      * Get the latest books.
72      * @param int $count
73      * @return mixed
74      */
75     public function getLatest($count = 10)
76     {
77         return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get();
78     }
79
80     /**
81      * Gets the most recently viewed for a user.
82      * @param int $count
83      * @param int $page
84      * @return mixed
85      */
86     public function getRecentlyViewed($count = 10, $page = 0)
87     {
88         return Views::getUserRecentlyViewed($count, $page, $this->book);
89     }
90
91     /**
92      * Gets the most viewed books.
93      * @param int $count
94      * @param int $page
95      * @return mixed
96      */
97     public function getPopular($count = 10, $page = 0)
98     {
99         return Views::getPopular($count, $page, $this->book);
100     }
101
102     /**
103      * Get a book by slug
104      * @param $slug
105      * @return mixed
106      * @throws NotFoundException
107      */
108     public function getBySlug($slug)
109     {
110         $book = $this->bookQuery()->where('slug', '=', $slug)->first();
111         if ($book === null) throw new NotFoundException('Book not found');
112         return $book;
113     }
114
115     /**
116      * Checks if a book exists.
117      * @param $id
118      * @return bool
119      */
120     public function exists($id)
121     {
122         return $this->bookQuery()->where('id', '=', $id)->exists();
123     }
124
125     /**
126      * Get a new book instance from request input.
127      * @param array $input
128      * @return Book
129      */
130     public function createFromInput($input)
131     {
132         $book = $this->book->newInstance($input);
133         $book->slug = $this->findSuitableSlug($book->name);
134         $book->created_by = auth()->user()->id;
135         $book->updated_by = auth()->user()->id;
136         $book->save();
137         $this->permissionService->buildJointPermissionsForEntity($book);
138         return $book;
139     }
140
141     /**
142      * Update the given book from user input.
143      * @param Book $book
144      * @param $input
145      * @return Book
146      */
147     public function updateFromInput(Book $book, $input)
148     {
149         $book->fill($input);
150         $book->slug = $this->findSuitableSlug($book->name, $book->id);
151         $book->updated_by = auth()->user()->id;
152         $book->save();
153         $this->permissionService->buildJointPermissionsForEntity($book);
154         return $book;
155     }
156
157     /**
158      * Destroy the given book.
159      * @param Book $book
160      * @throws \Exception
161      */
162     public function destroy(Book $book)
163     {
164         foreach ($book->pages as $page) {
165             $this->pageRepo->destroy($page);
166         }
167         foreach ($book->chapters as $chapter) {
168             $this->chapterRepo->destroy($chapter);
169         }
170         $book->views()->delete();
171         $book->permissions()->delete();
172         $this->permissionService->deleteJointPermissionsForEntity($book);
173         $book->delete();
174     }
175
176     /**
177      * Alias method to update the book jointPermissions in the PermissionService.
178      * @param Book $book
179      */
180     public function updateBookPermissions(Book $book)
181     {
182         $this->permissionService->buildJointPermissionsForEntity($book);
183     }
184
185     /**
186      * Get the next child element priority.
187      * @param Book $book
188      * @return int
189      */
190     public function getNewPriority($book)
191     {
192         $lastElem = $this->getChildren($book)->pop();
193         return $lastElem ? $lastElem->priority + 1 : 0;
194     }
195
196     /**
197      * @param string $slug
198      * @param bool|false $currentId
199      * @return bool
200      */
201     public function doesSlugExist($slug, $currentId = false)
202     {
203         $query = $this->book->where('slug', '=', $slug);
204         if ($currentId) {
205             $query = $query->where('id', '!=', $currentId);
206         }
207         return $query->count() > 0;
208     }
209
210     /**
211      * Provides a suitable slug for the given book name.
212      * Ensures the returned slug is unique in the system.
213      * @param string $name
214      * @param bool|false $currentId
215      * @return string
216      */
217     public function findSuitableSlug($name, $currentId = false)
218     {
219         $slug = Str::slug($name);
220         if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
221         while ($this->doesSlugExist($slug, $currentId)) {
222             $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
223         }
224         return $slug;
225     }
226
227     /**
228      * Get all child objects of a book.
229      * Returns a sorted collection of Pages and Chapters.
230      * Loads the book slug onto child elements to prevent access database access for getting the slug.
231      * @param Book $book
232      * @param bool $filterDrafts
233      * @return mixed
234      */
235     public function getChildren(Book $book, $filterDrafts = false)
236     {
237         $pageQuery = $book->pages()->where('chapter_id', '=', 0);
238         $pageQuery = $this->permissionService->enforcePageRestrictions($pageQuery, 'view');
239
240         if ($filterDrafts) {
241             $pageQuery = $pageQuery->where('draft', '=', false);
242         }
243
244         $pages = $pageQuery->get();
245
246         $chapterQuery = $book->chapters()->with(['pages' => function ($query) use ($filterDrafts) {
247             $this->permissionService->enforcePageRestrictions($query, 'view');
248             if ($filterDrafts) $query->where('draft', '=', false);
249         }]);
250         $chapterQuery = $this->permissionService->enforceChapterRestrictions($chapterQuery, 'view');
251         $chapters = $chapterQuery->get();
252         $children = $pages->values();
253         foreach ($chapters as $chapter) {
254             $children->push($chapter);
255         }
256         $bookSlug = $book->slug;
257
258         $children->each(function ($child) use ($bookSlug) {
259             $child->setAttribute('bookSlug', $bookSlug);
260             if ($child->isA('chapter')) {
261                 $child->pages->each(function ($page) use ($bookSlug) {
262                     $page->setAttribute('bookSlug', $bookSlug);
263                 });
264                 $child->pages = $child->pages->sortBy(function ($child, $key) {
265                     $score = $child->priority;
266                     if ($child->draft) $score -= 100;
267                     return $score;
268                 });
269             }
270         });
271
272         // Sort items with drafts first then by priority.
273         return $children->sortBy(function ($child, $key) {
274             $score = $child->priority;
275             if ($child->isA('page') && $child->draft) $score -= 100;
276             return $score;
277         });
278     }
279
280     /**
281      * Get books by search term.
282      * @param $term
283      * @param int $count
284      * @param array $paginationAppends
285      * @return mixed
286      */
287     public function getBySearch($term, $count = 20, $paginationAppends = [])
288     {
289         $terms = $this->prepareSearchTerms($term);
290         $bookQuery = $this->permissionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms));
291         $bookQuery = $this->addAdvancedSearchQueries($bookQuery, $term);
292         $books = $bookQuery->paginate($count)->appends($paginationAppends);
293         $words = join('|', explode(' ', preg_quote(trim($term), '/')));
294         foreach ($books as $book) {
295             //highlight
296             $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $book->getExcerpt(100));
297             $book->searchSnippet = $result;
298         }
299         return $books;
300     }
301
302 }