1 <?php namespace BookStack\Repos;
7 use BookStack\Services\PermissionService;
9 use Illuminate\Support\Facades\Log;
30 * @var PermissionService
32 protected $permissionService;
35 * Acceptable operators to be used in a query
38 protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
41 * EntityService constructor.
43 public function __construct()
45 $this->book = app(Book::class);
46 $this->chapter = app(Chapter::class);
47 $this->page = app(Page::class);
48 $this->permissionService = app(PermissionService::class);
52 * Get the latest books added to the system.
55 * @param bool $additionalQuery
58 public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false)
60 $query = $this->permissionService->enforceBookRestrictions($this->book)
61 ->orderBy('created_at', 'desc');
62 if ($additionalQuery !== false && is_callable($additionalQuery)) {
63 $additionalQuery($query);
65 return $query->skip($page * $count)->take($count)->get();
69 * Get the most recently updated books.
74 public function getRecentlyUpdatedBooks($count = 20, $page = 0)
76 return $this->permissionService->enforceBookRestrictions($this->book)
77 ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get();
81 * Get the latest pages added to the system.
84 * @param bool $additionalQuery
87 public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false)
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);
94 return $query->with('book')->skip($page * $count)->take($count)->get();
98 * Get the latest chapters added to the system.
101 * @param bool $additionalQuery
104 public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false)
106 $query = $this->permissionService->enforceChapterRestrictions($this->chapter)
107 ->orderBy('created_at', 'desc');
108 if ($additionalQuery !== false && is_callable($additionalQuery)) {
109 $additionalQuery($query);
111 return $query->skip($page * $count)->take($count)->get();
115 * Get the most recently updated pages.
120 public function getRecentlyUpdatedPages($count = 20, $page = 0)
122 return $this->permissionService->enforcePageRestrictions($this->page)
123 ->where('draft', '=', false)
124 ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get();
128 * Get draft pages owned by the current user.
132 public function getUserDraftPages($count = 20, $page = 0)
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();
142 * Updates entity restrictions from a request
144 * @param Entity $entity
146 public function updateEntityPermissionsFromRequest($request, Entity $entity)
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)
161 $this->permissionService->buildJointPermissionsForEntity($entity);
165 * Prepare a string of search terms by turning
166 * it into an array of terms.
167 * Keeps quoted terms together.
171 protected function prepareSearchTerms($termString)
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));
181 if (!empty($termString)) $terms = array_merge($terms, explode(' ', $termString));
186 * Removes any special search notation that should not
187 * be used in a full-text search.
191 protected function cleanSearchTermString($termString)
193 // Strip tag searches
194 $termString = preg_replace('/\[.*?\]/', '', $termString);
195 // Reduced multiple spacing into single spacing
196 $termString = preg_replace("/\s{2,}/", " ", $termString);
201 * Get the available query operators as a regex escaped list.
204 protected function getRegexEscapedOperators()
206 $escapedOperators = [];
207 foreach ($this->queryOperators as $operator) {
208 $escapedOperators[] = preg_quote($operator);
210 return join('|', $escapedOperators);
214 * Parses advanced search notations and adds them to the db query.
219 protected function addAdvancedSearchQueries($query, $termString)
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);
232 * Apply extracted tag search terms onto a entity query.
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}");
251 $query->where('name', '=', $tagName)->where('value', $tagOperator, $tagValue);
254 $query->where('name', '=', $tagName);