]> BookStack Code Mirror - bookstack/blob - app/Repos/EntityRepo.php
Updated all application urls to allow path prefix.
[bookstack] / app / Repos / EntityRepo.php
1 <?php namespace BookStack\Repos;
2
3 use BookStack\Book;
4 use BookStack\Chapter;
5 use BookStack\Entity;
6 use BookStack\Page;
7 use BookStack\Services\PermissionService;
8 use BookStack\User;
9 use Illuminate\Support\Facades\Log;
10
11 class EntityRepo
12 {
13
14     /**
15      * @var Book $book
16      */
17     public $book;
18
19     /**
20      * @var Chapter
21      */
22     public $chapter;
23
24     /**
25      * @var Page
26      */
27     public $page;
28
29     /**
30      * @var PermissionService
31      */
32     protected $permissionService;
33
34     /**
35      * Acceptable operators to be used in a query
36      * @var array
37      */
38     protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
39
40     /**
41      * EntityService constructor.
42      */
43     public function __construct()
44     {
45         $this->book = app(Book::class);
46         $this->chapter = app(Chapter::class);
47         $this->page = app(Page::class);
48         $this->permissionService = app(PermissionService::class);
49     }
50
51     /**
52      * Get the latest books added to the system.
53      * @param int $count
54      * @param int $page
55      * @param bool $additionalQuery
56      * @return
57      */
58     public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false)
59     {
60         $query = $this->permissionService->enforceBookRestrictions($this->book)
61             ->orderBy('created_at', 'desc');
62         if ($additionalQuery !== false && is_callable($additionalQuery)) {
63             $additionalQuery($query);
64         }
65         return $query->skip($page * $count)->take($count)->get();
66     }
67
68     /**
69      * Get the most recently updated books.
70      * @param $count
71      * @param int $page
72      * @return mixed
73      */
74     public function getRecentlyUpdatedBooks($count = 20, $page = 0)
75     {
76         return $this->permissionService->enforceBookRestrictions($this->book)
77             ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get();
78     }
79
80     /**
81      * Get the latest pages added to the system.
82      * @param int $count
83      * @param int $page
84      * @param bool $additionalQuery
85      * @return
86      */
87     public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false)
88     {
89         $query = $this->permissionService->enforcePageRestrictions($this->page)
90             ->orderBy('created_at', 'desc')->where('draft', '=', false);
91         if ($additionalQuery !== false && is_callable($additionalQuery)) {
92             $additionalQuery($query);
93         }
94         return $query->with('book')->skip($page * $count)->take($count)->get();
95     }
96
97     /**
98      * Get the latest chapters added to the system.
99      * @param int $count
100      * @param int $page
101      * @param bool $additionalQuery
102      * @return
103      */
104     public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false)
105     {
106         $query = $this->permissionService->enforceChapterRestrictions($this->chapter)
107             ->orderBy('created_at', 'desc');
108         if ($additionalQuery !== false && is_callable($additionalQuery)) {
109             $additionalQuery($query);
110         }
111         return $query->skip($page * $count)->take($count)->get();
112     }
113
114     /**
115      * Get the most recently updated pages.
116      * @param $count
117      * @param int $page
118      * @return mixed
119      */
120     public function getRecentlyUpdatedPages($count = 20, $page = 0)
121     {
122         return $this->permissionService->enforcePageRestrictions($this->page)
123             ->where('draft', '=', false)
124             ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get();
125     }
126
127     /**
128      * Get draft pages owned by the current user.
129      * @param int $count
130      * @param int $page
131      */
132     public function getUserDraftPages($count = 20, $page = 0)
133     {
134         $user = auth()->user();
135         return $this->page->where('draft', '=', true)
136             ->where('created_by', '=', $user->id)
137             ->orderBy('updated_at', 'desc')
138             ->skip($count * $page)->take($count)->get();
139     }
140
141     /**
142      * Updates entity restrictions from a request
143      * @param $request
144      * @param Entity $entity
145      */
146     public function updateEntityPermissionsFromRequest($request, Entity $entity)
147     {
148         $entity->restricted = $request->has('restricted') && $request->get('restricted') === 'true';
149         $entity->permissions()->delete();
150         if ($request->has('restrictions')) {
151             foreach ($request->get('restrictions') as $roleId => $restrictions) {
152                 foreach ($restrictions as $action => $value) {
153                     $entity->permissions()->create([
154                         'role_id' => $roleId,
155                         'action'  => strtolower($action)
156                     ]);
157                 }
158             }
159         }
160         $entity->save();
161         $this->permissionService->buildJointPermissionsForEntity($entity);
162     }
163
164     /**
165      * Prepare a string of search terms by turning
166      * it into an array of terms.
167      * Keeps quoted terms together.
168      * @param $termString
169      * @return array
170      */
171     protected function prepareSearchTerms($termString)
172     {
173         $termString = $this->cleanSearchTermString($termString);
174         preg_match_all('/"(.*?)"/', $termString, $matches);
175         if (count($matches[1]) > 0) {
176             $terms = $matches[1];
177             $termString = trim(preg_replace('/"(.*?)"/', '', $termString));
178         } else {
179             $terms = [];
180         }
181         if (!empty($termString)) $terms = array_merge($terms, explode(' ', $termString));
182         return $terms;
183     }
184
185     /**
186      * Removes any special search notation that should not
187      * be used in a full-text search.
188      * @param $termString
189      * @return mixed
190      */
191     protected function cleanSearchTermString($termString)
192     {
193         // Strip tag searches
194         $termString = preg_replace('/\[.*?\]/', '', $termString);
195         // Reduced multiple spacing into single spacing
196         $termString = preg_replace("/\s{2,}/", " ", $termString);
197         return $termString;
198     }
199
200     /**
201      * Get the available query operators as a regex escaped list.
202      * @return mixed
203      */
204     protected function getRegexEscapedOperators()
205     {
206         $escapedOperators = [];
207         foreach ($this->queryOperators as $operator) {
208             $escapedOperators[] = preg_quote($operator);
209         }
210         return join('|', $escapedOperators);
211     }
212
213     /**
214      * Parses advanced search notations and adds them to the db query.
215      * @param $query
216      * @param $termString
217      * @return mixed
218      */
219     protected function addAdvancedSearchQueries($query, $termString)
220     {
221         $escapedOperators = $this->getRegexEscapedOperators();
222         // Look for tag searches
223         preg_match_all("/\[(.*?)((${escapedOperators})(.*?))?\]/", $termString, $tags);
224         if (count($tags[0]) > 0) {
225             $this->applyTagSearches($query, $tags);
226         }
227
228         return $query;
229     }
230
231     /**
232      * Apply extracted tag search terms onto a entity query.
233      * @param $query
234      * @param $tags
235      * @return mixed
236      */
237     protected function applyTagSearches($query, $tags) {
238         $query->where(function($query) use ($tags) {
239             foreach ($tags[1] as $index => $tagName) {
240                 $query->whereHas('tags', function($query) use ($tags, $index, $tagName) {
241                     $tagOperator = $tags[3][$index];
242                     $tagValue = $tags[4][$index];
243                     if (!empty($tagOperator) && !empty($tagValue) && in_array($tagOperator, $this->queryOperators)) {
244                         if (is_numeric($tagValue) && $tagOperator !== 'like') {
245                             // We have to do a raw sql query for this since otherwise PDO will quote the value and MySQL will
246                             // search the value as a string which prevents being able to do number-based operations
247                             // on the tag values. We ensure it has a numeric value and then cast it just to be sure.
248                             $tagValue = (float) trim($query->getConnection()->getPdo()->quote($tagValue), "'");
249                             $query->where('name', '=', $tagName)->whereRaw("value ${tagOperator} ${tagValue}");
250                         } else {
251                             $query->where('name', '=', $tagName)->where('value', $tagOperator, $tagValue);
252                         }
253                     } else {
254                         $query->where('name', '=', $tagName);
255                     }
256                 });
257             }
258         });
259         return $query;
260     }
261
262 }
263
264
265
266
267
268
269
270
271
272
273
274