1 <?php namespace BookStack\Repos;
7 use BookStack\Services\PermissionService;
9 use Illuminate\Support\Collection;
10 use Illuminate\Support\Facades\Log;
31 * @var PermissionService
33 protected $permissionService;
36 * Acceptable operators to be used in a query
39 protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
42 * EntityService constructor.
44 public function __construct()
46 $this->book = app(Book::class);
47 $this->chapter = app(Chapter::class);
48 $this->page = app(Page::class);
49 $this->permissionService = app(PermissionService::class);
53 * Get the latest books added to the system.
56 * @param bool $additionalQuery
59 public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false)
61 $query = $this->permissionService->enforceBookRestrictions($this->book)
62 ->orderBy('created_at', 'desc');
63 if ($additionalQuery !== false && is_callable($additionalQuery)) {
64 $additionalQuery($query);
66 return $query->skip($page * $count)->take($count)->get();
70 * Get the most recently updated books.
75 public function getRecentlyUpdatedBooks($count = 20, $page = 0)
77 return $this->permissionService->enforceBookRestrictions($this->book)
78 ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get();
82 * Get the latest pages added to the system.
85 * @param bool $additionalQuery
88 public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false)
90 $query = $this->permissionService->enforcePageRestrictions($this->page)
91 ->orderBy('created_at', 'desc')->where('draft', '=', false);
92 if ($additionalQuery !== false && is_callable($additionalQuery)) {
93 $additionalQuery($query);
95 return $query->with('book')->skip($page * $count)->take($count)->get();
99 * Get the latest chapters added to the system.
102 * @param bool $additionalQuery
105 public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false)
107 $query = $this->permissionService->enforceChapterRestrictions($this->chapter)
108 ->orderBy('created_at', 'desc');
109 if ($additionalQuery !== false && is_callable($additionalQuery)) {
110 $additionalQuery($query);
112 return $query->skip($page * $count)->take($count)->get();
116 * Get the most recently updated pages.
121 public function getRecentlyUpdatedPages($count = 20, $page = 0)
123 return $this->permissionService->enforcePageRestrictions($this->page)
124 ->where('draft', '=', false)
125 ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get();
129 * Get draft pages owned by the current user.
133 public function getUserDraftPages($count = 20, $page = 0)
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 public function prepareSearchTerms($termString)
173 $termString = $this->cleanSearchTermString($termString);
174 preg_match_all('/(".*?")/', $termString, $matches);
176 if (count($matches[1]) > 0) {
177 foreach ($matches[1] as $match) {
180 $termString = trim(preg_replace('/"(.*?)"/', '', $termString));
182 if (!empty($termString)) $terms = array_merge($terms, explode(' ', $termString));
187 * Removes any special search notation that should not
188 * be used in a full-text search.
192 protected function cleanSearchTermString($termString)
194 // Strip tag searches
195 $termString = preg_replace('/\[.*?\]/', '', $termString);
196 // Reduced multiple spacing into single spacing
197 $termString = preg_replace("/\s{2,}/", " ", $termString);
202 * Get the available query operators as a regex escaped list.
205 protected function getRegexEscapedOperators()
207 $escapedOperators = [];
208 foreach ($this->queryOperators as $operator) {
209 $escapedOperators[] = preg_quote($operator);
211 return join('|', $escapedOperators);
215 * Parses advanced search notations and adds them to the db query.
220 protected function addAdvancedSearchQueries($query, $termString)
222 $escapedOperators = $this->getRegexEscapedOperators();
223 // Look for tag searches
224 preg_match_all("/\[(.*?)((${escapedOperators})(.*?))?\]/", $termString, $tags);
225 if (count($tags[0]) > 0) {
226 $this->applyTagSearches($query, $tags);
233 * Apply extracted tag search terms onto a entity query.
238 protected function applyTagSearches($query, $tags) {
239 $query->where(function($query) use ($tags) {
240 foreach ($tags[1] as $index => $tagName) {
241 $query->whereHas('tags', function($query) use ($tags, $index, $tagName) {
242 $tagOperator = $tags[3][$index];
243 $tagValue = $tags[4][$index];
244 if (!empty($tagOperator) && !empty($tagValue) && in_array($tagOperator, $this->queryOperators)) {
245 if (is_numeric($tagValue) && $tagOperator !== 'like') {
246 // We have to do a raw sql query for this since otherwise PDO will quote the value and MySQL will
247 // search the value as a string which prevents being able to do number-based operations
248 // on the tag values. We ensure it has a numeric value and then cast it just to be sure.
249 $tagValue = (float) trim($query->getConnection()->getPdo()->quote($tagValue), "'");
250 $query->where('name', '=', $tagName)->whereRaw("value ${tagOperator} ${tagValue}");
252 $query->where('name', '=', $tagName)->where('value', $tagOperator, $tagValue);
255 $query->where('name', '=', $tagName);
264 * Alias method to update the book jointPermissions in the PermissionService.
265 * @param Collection $collection collection on entities
267 public function buildJointPermissions(Collection $collection)
269 $this->permissionService->buildJointPermissionsForEntities($collection);