=', '=', '<', '>', 'like', '!=']; /** * EntityService constructor. */ public function __construct() { // TODO - Redo this to come via injection $this->book = app(Book::class); $this->chapter = app(Chapter::class); $this->page = app(Page::class); $this->entities = [ 'page' => $this->page, 'chapter' => $this->chapter, 'book' => $this->book ]; $this->viewService = app(ViewService::class); $this->permissionService = app(PermissionService::class); } /** * Get an entity instance via type. * @param $type * @return Entity */ protected function getEntity($type) { return $this->entities[strtolower($type)]; } /** * Base query for searching entities via permission system * @param string $type * @param bool $allowDrafts * @return \Illuminate\Database\Query\Builder */ protected function entityQuery($type, $allowDrafts = false) { $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), 'view'); if (strtolower($type) === 'page' && !$allowDrafts) { $q = $q->where('draft', '=', false); } return $q; } /** * Check if an entity with the given id exists. * @param $type * @param $id * @return bool */ public function exists($type, $id) { return $this->entityQuery($type)->where('id', '=', $id)->exists(); } /** * Get an entity by ID * @param string $type * @param integer $id * @param bool $allowDrafts * @return Entity */ public function getById($type, $id, $allowDrafts = false) { return $this->entityQuery($type, $allowDrafts)->findOrFail($id); } /** * Get an entity by its url slug. * @param string $type * @param string $slug * @param string|bool $bookSlug * @return Entity * @throws NotFoundException */ public function getBySlug($type, $slug, $bookSlug = false) { $q = $this->entityQuery($type)->where('slug', '=', $slug); if (strtolower($type) === 'chapter' || strtolower($type) === 'page') { $q = $q->where('book_id', '=', function($query) use ($bookSlug) { $query->select('id') ->from($this->book->getTable()) ->where('slug', '=', $bookSlug)->limit(1); }); } $entity = $q->first(); if ($entity === null) throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found')); return $entity; } /** * Get all entities of a type limited by count unless count if false. * @param string $type * @param integer|bool $count * @return Collection */ public function getAll($type, $count = 20) { $q = $this->entityQuery($type)->orderBy('name', 'asc'); if ($count !== false) $q = $q->take($count); return $q->get(); } /** * Get all entities in a paginated format * @param $type * @param int $count * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ public function getAllPaginated($type, $count = 10) { return $this->entityQuery($type)->orderBy('name', 'asc')->paginate($count); } /** * Get the most recently created entities of the given type. * @param string $type * @param int $count * @param int $page * @param bool|callable $additionalQuery */ public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false) { $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type)) ->orderBy('created_at', 'desc'); if (strtolower($type) === 'page') $query = $query->where('draft', '=', false); if ($additionalQuery !== false && is_callable($additionalQuery)) { $additionalQuery($query); } return $query->skip($page * $count)->take($count)->get(); } /** * Get the most recently updated entities of the given type. * @param string $type * @param int $count * @param int $page * @param bool|callable $additionalQuery */ public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false) { $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type)) ->orderBy('updated_at', 'desc'); if (strtolower($type) === 'page') $query = $query->where('draft', '=', false); if ($additionalQuery !== false && is_callable($additionalQuery)) { $additionalQuery($query); } return $query->skip($page * $count)->take($count)->get(); } /** * Get the most recently viewed entities. * @param string|bool $type * @param int $count * @param int $page * @return mixed */ public function getRecentlyViewed($type, $count = 10, $page = 0) { $filter = is_bool($type) ? false : $this->getEntity($type); return $this->viewService->getUserRecentlyViewed($count, $page, $filter); } /** * Get the most popular entities base on all views. * @param string|bool $type * @param int $count * @param int $page * @return mixed */ public function getPopular($type, $count = 10, $page = 0) { $filter = is_bool($type) ? false : $this->getEntity($type); return $this->viewService->getPopular($count, $page, $filter); } /** * Get draft pages owned by the current user. * @param int $count * @param int $page */ public function getUserDraftPages($count = 20, $page = 0) { return $this->page->where('draft', '=', true) ->where('created_by', '=', user()->id) ->orderBy('updated_at', 'desc') ->skip($count * $page)->take($count)->get(); } /** * Updates entity restrictions from a request * @param $request * @param Entity $entity */ public function updateEntityPermissionsFromRequest($request, Entity $entity) { $entity->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; $entity->permissions()->delete(); if ($request->has('restrictions')) { foreach ($request->get('restrictions') as $roleId => $restrictions) { foreach ($restrictions as $action => $value) { $entity->permissions()->create([ 'role_id' => $roleId, 'action' => strtolower($action) ]); } } } $entity->save(); $this->permissionService->buildJointPermissionsForEntity($entity); } /** * Prepare a string of search terms by turning * it into an array of terms. * Keeps quoted terms together. * @param $termString * @return array */ public function prepareSearchTerms($termString) { $termString = $this->cleanSearchTermString($termString); preg_match_all('/(".*?")/', $termString, $matches); $terms = []; if (count($matches[1]) > 0) { foreach ($matches[1] as $match) { $terms[] = $match; } $termString = trim(preg_replace('/"(.*?)"/', '', $termString)); } if (!empty($termString)) $terms = array_merge($terms, explode(' ', $termString)); return $terms; } /** * Removes any special search notation that should not * be used in a full-text search. * @param $termString * @return mixed */ protected function cleanSearchTermString($termString) { // Strip tag searches $termString = preg_replace('/\[.*?\]/', '', $termString); // Reduced multiple spacing into single spacing $termString = preg_replace("/\s{2,}/", " ", $termString); return $termString; } /** * Get the available query operators as a regex escaped list. * @return mixed */ protected function getRegexEscapedOperators() { $escapedOperators = []; foreach ($this->queryOperators as $operator) { $escapedOperators[] = preg_quote($operator); } return join('|', $escapedOperators); } /** * Parses advanced search notations and adds them to the db query. * @param $query * @param $termString * @return mixed */ protected function addAdvancedSearchQueries($query, $termString) { $escapedOperators = $this->getRegexEscapedOperators(); // Look for tag searches preg_match_all("/\[(.*?)((${escapedOperators})(.*?))?\]/", $termString, $tags); if (count($tags[0]) > 0) { $this->applyTagSearches($query, $tags); } return $query; } /** * Apply extracted tag search terms onto a entity query. * @param $query * @param $tags * @return mixed */ protected function applyTagSearches($query, $tags) { $query->where(function($query) use ($tags) { foreach ($tags[1] as $index => $tagName) { $query->whereHas('tags', function($query) use ($tags, $index, $tagName) { $tagOperator = $tags[3][$index]; $tagValue = $tags[4][$index]; if (!empty($tagOperator) && !empty($tagValue) && in_array($tagOperator, $this->queryOperators)) { if (is_numeric($tagValue) && $tagOperator !== 'like') { // We have to do a raw sql query for this since otherwise PDO will quote the value and MySQL will // search the value as a string which prevents being able to do number-based operations // on the tag values. We ensure it has a numeric value and then cast it just to be sure. $tagValue = (float) trim($query->getConnection()->getPdo()->quote($tagValue), "'"); $query->where('name', '=', $tagName)->whereRaw("value ${tagOperator} ${tagValue}"); } else { $query->where('name', '=', $tagName)->where('value', $tagOperator, $tagValue); } } else { $query->where('name', '=', $tagName); } }); } }); return $query; } /** * Alias method to update the book jointPermissions in the PermissionService. * @param Collection $collection collection on entities */ public function buildJointPermissions(Collection $collection) { $this->permissionService->buildJointPermissionsForEntities($collection); } /** * Format a name as a url slug. * @param $name * @return string */ protected function nameToSlug($name) { $slug = str_replace(' ', '-', strtolower($name)); $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', $slug); if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5); return $slug; } }