Focused base Entity class cleanup

Removed some common functions from other entities.
Aligned implementation of getUrl()
Cleaned phpdocs and added typehinting.
Also extracted sibling search logic out of controller.
This commit is contained in:
Dan Brown 2020-11-22 01:20:38 +00:00
parent ef1b98019a
commit a042e22481
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
10 changed files with 102 additions and 189 deletions

View File

@ -42,21 +42,14 @@ class EntityProvider
*/ */
public $pageRevision; public $pageRevision;
/**
* EntityProvider constructor. public function __construct()
*/ {
public function __construct( $this->bookshelf = new Bookshelf();
Bookshelf $bookshelf, $this->book = new Book();
Book $book, $this->chapter = new Chapter();
Chapter $chapter, $this->page = new Page();
Page $page, $this->pageRevision = new PageRevision();
PageRevision $pageRevision
) {
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
$this->pageRevision = $pageRevision;
} }
/** /**

View File

@ -1,10 +1,5 @@
<?php namespace BookStack\Entities\Models; <?php namespace BookStack\Entities\Models;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Entities\Models\Page;
use BookStack\Uploads\Image; use BookStack\Uploads\Image;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -27,15 +22,10 @@ class Book extends Entity implements HasCoverImage
/** /**
* Get the url for this book. * Get the url for this book.
* @param string|bool $path
* @return string
*/ */
public function getUrl($path = false) public function getUrl(string $path = ''): string
{ {
if ($path !== false) { return url('/books/' . implode('/', [urlencode($this->slug), trim($path, '/')]));
return url('/books/' . urlencode($this->slug) . '/' . trim($path, '/'));
}
return url('/books/' . urlencode($this->slug));
} }
/** /**
@ -121,15 +111,4 @@ class Book extends Entity implements HasCoverImage
$chapters = $this->chapters()->visible()->get(); $chapters = $this->chapters()->visible()->get();
return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft'); return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft');
} }
/**
* Get an excerpt of this book's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt(int $length = 100)
{
$description = $this->description;
return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
}
} }

View File

@ -1,8 +1,5 @@
<?php namespace BookStack\Entities\Models; <?php namespace BookStack\Entities\Models;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Entities\Models\Book;
use BookStack\Uploads\Image; use BookStack\Uploads\Image;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -39,15 +36,10 @@ class Bookshelf extends Entity implements HasCoverImage
/** /**
* Get the url for this bookshelf. * Get the url for this bookshelf.
* @param string|bool $path
* @return string
*/ */
public function getUrl($path = false) public function getUrl(string $path = ''): string
{ {
if ($path !== false) { return url('/shelves/' . implode('/', [urlencode($this->slug), trim($path, '/')]));
return url('/shelves/' . urlencode($this->slug) . '/' . trim($path, '/'));
}
return url('/shelves/' . urlencode($this->slug));
} }
/** /**
@ -88,17 +80,6 @@ class Bookshelf extends Entity implements HasCoverImage
return 'cover_shelf'; return 'cover_shelf';
} }
/**
* Get an excerpt of this book's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt(int $length = 100)
{
$description = $this->description;
return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
}
/** /**
* Check if this shelf contains the given book. * Check if this shelf contains the given book.
* @param Book $book * @param Book $book

View File

@ -1,7 +1,5 @@
<?php namespace BookStack\Entities\Models; <?php namespace BookStack\Entities\Models;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Page;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
@ -27,30 +25,18 @@ class Chapter extends BookChild
/** /**
* Get the url of this chapter. * Get the url of this chapter.
* @param string|bool $path
* @return string
*/ */
public function getUrl($path = false) public function getUrl($path = ''): string
{ {
$bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; $parts = [
$fullPath = '/books/' . urlencode($bookSlug) . '/chapter/' . urlencode($this->slug); 'books',
urlencode($this->getAttribute('bookSlug') ?? $this->book->slug),
'chapter',
urlencode($this->slug),
trim($path, '/'),
];
if ($path !== false) { return url('/' . implode('/', $parts));
$fullPath .= '/' . trim($path, '/');
}
return url($fullPath);
}
/**
* Get an excerpt of this chapter's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt(int $length = 100)
{
$description = $this->text ?? $this->description;
return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
} }
/** /**

View File

@ -35,7 +35,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static Builder withLastView() * @method static Builder withLastView()
* @method static Builder withViewCount() * @method static Builder withViewCount()
*/ */
class Entity extends Ownable abstract class Entity extends Ownable
{ {
use SoftDeletes; use SoftDeletes;
@ -52,7 +52,7 @@ class Entity extends Ownable
/** /**
* Get the entities that are visible to the current user. * Get the entities that are visible to the current user.
*/ */
public function scopeVisible(Builder $query) public function scopeVisible(Builder $query): Builder
{ {
return $this->scopeHasPermission($query, 'view'); return $this->scopeHasPermission($query, 'view');
} }
@ -94,24 +94,18 @@ class Entity extends Ownable
/** /**
* Compares this entity to another given entity. * Compares this entity to another given entity.
* Matches by comparing class and id. * Matches by comparing class and id.
* @param $entity
* @return bool
*/ */
public function matches($entity) public function matches(Entity $entity): bool
{ {
return [get_class($this), $this->id] === [get_class($entity), $entity->id]; return [get_class($this), $this->id] === [get_class($entity), $entity->id];
} }
/** /**
* Checks if an entity matches or contains another given entity. * Checks if the current entity matches or contains the given.
* @param Entity $entity
* @return bool
*/ */
public function matchesOrContains(Entity $entity) public function matchesOrContains(Entity $entity): bool
{ {
$matches = [get_class($this), $this->id] === [get_class($entity), $entity->id]; if ($this->matches($entity)) {
if ($matches) {
return true; return true;
} }
@ -128,9 +122,8 @@ class Entity extends Ownable
/** /**
* Gets the activity objects for this entity. * Gets the activity objects for this entity.
* @return MorphMany
*/ */
public function activity() public function activity(): MorphMany
{ {
return $this->morphMany(Activity::class, 'entity') return $this->morphMany(Activity::class, 'entity')
->orderBy('created_at', 'desc'); ->orderBy('created_at', 'desc');
@ -139,26 +132,23 @@ class Entity extends Ownable
/** /**
* Get View objects for this entity. * Get View objects for this entity.
*/ */
public function views() public function views(): MorphMany
{ {
return $this->morphMany(View::class, 'viewable'); return $this->morphMany(View::class, 'viewable');
} }
/** /**
* Get the Tag models that have been user assigned to this entity. * Get the Tag models that have been user assigned to this entity.
* @return MorphMany
*/ */
public function tags() public function tags(): MorphMany
{ {
return $this->morphMany(Tag::class, 'entity')->orderBy('order', 'asc'); return $this->morphMany(Tag::class, 'entity')->orderBy('order', 'asc');
} }
/** /**
* Get the comments for an entity * Get the comments for an entity
* @param bool $orderByCreated
* @return MorphMany
*/ */
public function comments($orderByCreated = true) public function comments(bool $orderByCreated = true): MorphMany
{ {
$query = $this->morphMany(Comment::class, 'entity'); $query = $this->morphMany(Comment::class, 'entity');
return $orderByCreated ? $query->orderBy('created_at', 'asc') : $query; return $orderByCreated ? $query->orderBy('created_at', 'asc') : $query;
@ -166,9 +156,8 @@ class Entity extends Ownable
/** /**
* Get the related search terms. * Get the related search terms.
* @return MorphMany
*/ */
public function searchTerms() public function searchTerms(): MorphMany
{ {
return $this->morphMany(SearchTerm::class, 'entity'); return $this->morphMany(SearchTerm::class, 'entity');
} }
@ -176,18 +165,15 @@ class Entity extends Ownable
/** /**
* Get this entities restrictions. * Get this entities restrictions.
*/ */
public function permissions() public function permissions(): MorphMany
{ {
return $this->morphMany(EntityPermission::class, 'restrictable'); return $this->morphMany(EntityPermission::class, 'restrictable');
} }
/** /**
* Check if this entity has a specific restriction set against it. * Check if this entity has a specific restriction set against it.
* @param $role_id
* @param $action
* @return bool
*/ */
public function hasRestriction($role_id, $action) public function hasRestriction(int $role_id, string $action): bool
{ {
return $this->permissions()->where('role_id', '=', $role_id) return $this->permissions()->where('role_id', '=', $role_id)
->where('action', '=', $action)->count() > 0; ->where('action', '=', $action)->count() > 0;
@ -227,21 +213,6 @@ class Entity extends Ownable
return strtolower(static::getClassName()); return strtolower(static::getClassName());
} }
/**
* Get an instance of an entity of the given type.
* TODO - Refactor out
*/
public static function getEntityInstance(string $type): ?Entity
{
$types = ['Page', 'Book', 'Chapter', 'Bookshelf'];
$className = str_replace([' ', '-', '_'], '', ucwords($type));
if (!in_array($className, $types)) {
return null;
}
return app('BookStack\\Entities\\Models\\' . $className);
}
/** /**
* Gets a limited-length version of the entities name. * Gets a limited-length version of the entities name.
*/ */
@ -255,36 +226,30 @@ class Entity extends Ownable
/** /**
* Get the body text of this entity. * Get the body text of this entity.
* @return mixed
*/ */
public function getText() public function getText(): string
{ {
return $this->{$this->textField}; return $this->{$this->textField} ?? '';
} }
/** /**
* Get an excerpt of this entity's descriptive content to the specified length. * Get an excerpt of this entity's descriptive content to the specified length.
* @param int $length
* @return mixed
*/ */
public function getExcerpt(int $length = 100) public function getExcerpt(int $length = 100): string
{ {
$text = $this->getText(); $text = $this->getText();
if (mb_strlen($text) > $length) { if (mb_strlen($text) > $length) {
$text = mb_substr($text, 0, $length-3) . '...'; $text = mb_substr($text, 0, $length-3) . '...';
} }
return trim($text); return trim($text);
} }
/** /**
* Get the url of this entity * Get the url of this entity
* @param $path
* @return string
*/ */
public function getUrl($path = '/') abstract public function getUrl(string $path = '/'): string;
{
return $path;
}
/** /**
* Get the parent entity if existing. * Get the parent entity if existing.

View File

@ -1,8 +1,5 @@
<?php namespace BookStack\Entities\Models; <?php namespace BookStack\Entities\Models;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\PageRevision;
use BookStack\Uploads\Attachment; use BookStack\Uploads\Attachment;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -35,7 +32,7 @@ class Page extends BookChild
/** /**
* Get the entities that are visible to the current user. * Get the entities that are visible to the current user.
*/ */
public function scopeVisible(Builder $query) public function scopeVisible(Builder $query): Builder
{ {
$query = Permissions::enforceDraftVisiblityOnQuery($query); $query = Permissions::enforceDraftVisiblityOnQuery($query);
return parent::scopeVisible($query); return parent::scopeVisible($query);
@ -89,22 +86,19 @@ class Page extends BookChild
} }
/** /**
* Get the url for this page. * Get the url of this page.
* @param string|bool $path
* @return string
*/ */
public function getUrl($path = false) public function getUrl($path = ''): string
{ {
$bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; $parts = [
$midText = $this->draft ? '/draft/' : '/page/'; 'books',
$idComponent = $this->draft ? $this->id : urlencode($this->slug); urlencode($this->getAttribute('bookSlug') ?? $this->book->slug),
$this->draft ? 'draft' : 'page',
$this->draft ? $this->id : urlencode($this->slug),
trim($path, '/'),
];
$url = '/books/' . urlencode($bookSlug) . $midText . $idComponent; return url('/' . implode('/', $parts));
if ($path !== false) {
$url .= '/' . trim($path, '/');
}
return url($url);
} }
/** /**

View File

@ -31,7 +31,7 @@ class SearchIndex
{ {
$this->deleteEntityTerms($entity); $this->deleteEntityTerms($entity);
$nameTerms = $this->generateTermArrayFromText($entity->name, 5 * $entity->searchFactor); $nameTerms = $this->generateTermArrayFromText($entity->name, 5 * $entity->searchFactor);
$bodyTerms = $this->generateTermArrayFromText($entity->getText() ?? '', 1 * $entity->searchFactor); $bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1 * $entity->searchFactor);
$terms = array_merge($nameTerms, $bodyTerms); $terms = array_merge($nameTerms, $bodyTerms);
foreach ($terms as $index => $term) { foreach ($terms as $index => $term) {
$terms[$index]['entity_type'] = $entity->getMorphClass(); $terms[$index]['entity_type'] = $entity->getMorphClass();

View File

@ -0,0 +1,47 @@
<?php namespace BookStack\Entities\Tools;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use Illuminate\Support\Collection;
class SiblingFetcher
{
/**
* Search among the siblings of the entity of given type and id.
*/
public function fetch(string $entityType, int $entityId): Collection
{
$entity = (new EntityProvider)->get($entityType)->visible()->findOrFail($entityId);
$entities = [];
// Page in chapter
if ($entity->isA('page') && $entity->chapter) {
$entities = $entity->chapter->getVisiblePages();
}
// Page in book or chapter
if (($entity->isA('page') && !$entity->chapter) || $entity->isA('chapter')) {
$entities = $entity->book->getDirectChildren();
}
// Book
// Gets just the books in a shelf if shelf is in context
if ($entity->isA('book')) {
$contextShelf = (new ShelfContext)->getContextualShelfForBook($entity);
if ($contextShelf) {
$entities = $contextShelf->visibleBooks()->get();
} else {
$entities = Book::visible()->get();
}
}
// Shelve
if ($entity->isA('bookshelf')) {
$entities = Bookshelf::visible()->get();
}
return $entities;
}
}

View File

@ -168,11 +168,10 @@ class TrashCan
*/ */
public function getTrashedCounts(): array public function getTrashedCounts(): array
{ {
$provider = app(EntityProvider::class);
$counts = []; $counts = [];
/** @var Entity $instance */ /** @var Entity $instance */
foreach ($provider->all() as $key => $instance) { foreach ((new EntityProvider)->all() as $key => $instance) {
$counts[$key] = $instance->newQuery()->onlyTrashed()->count(); $counts[$key] = $instance->newQuery()->onlyTrashed()->count();
} }

View File

@ -7,6 +7,7 @@ use BookStack\Entities\Models\Entity;
use BookStack\Entities\Tools\SearchRunner; use BookStack\Entities\Tools\SearchRunner;
use BookStack\Entities\Tools\ShelfContext; use BookStack\Entities\Tools\ShelfContext;
use BookStack\Entities\Tools\SearchOptions; use BookStack\Entities\Tools\SearchOptions;
use BookStack\Entities\Tools\SiblingFetcher;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class SearchController extends Controller class SearchController extends Controller
@ -98,39 +99,7 @@ class SearchController extends Controller
$type = $request->get('entity_type', null); $type = $request->get('entity_type', null);
$id = $request->get('entity_id', null); $id = $request->get('entity_id', null);
$entity = Entity::getEntityInstance($type)->newQuery()->visible()->find($id); $entities = (new SiblingFetcher)->fetch($type, $id);
if (!$entity) {
return $this->jsonError(trans('errors.entity_not_found'), 404);
}
$entities = [];
// Page in chapter
if ($entity->isA('page') && $entity->chapter) {
$entities = $entity->chapter->getVisiblePages();
}
// Page in book or chapter
if (($entity->isA('page') && !$entity->chapter) || $entity->isA('chapter')) {
$entities = $entity->book->getDirectChildren();
}
// Book
// Gets just the books in a shelf if shelf is in context
if ($entity->isA('book')) {
$contextShelf = $this->entityContextManager->getContextualShelfForBook($entity);
if ($contextShelf) {
$entities = $contextShelf->visibleBooks()->get();
} else {
$entities = Book::visible()->get();
}
}
// Shelve
if ($entity->isA('bookshelf')) {
$entities = Bookshelf::visible()->get();
}
return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']); return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']);
} }
} }