]> BookStack Code Mirror - bookstack/blobdiff - app/Entities/Models/Entity.php
respective book and chapter structure added.
[bookstack] / app / Entities / Models / Entity.php
index 29853fe309760807d3b1fa17606e996e1754ca9d..0de83c93869332906842a6ad31fa28ad35ce27a2 100644 (file)
@@ -1,13 +1,28 @@
-<?php namespace BookStack\Entities;
-
-use BookStack\Actions\Activity;
-use BookStack\Actions\Comment;
-use BookStack\Actions\Tag;
-use BookStack\Actions\View;
-use BookStack\Auth\Permissions\EntityPermission;
-use BookStack\Auth\Permissions\JointPermission;
-use BookStack\Facades\Permissions;
-use BookStack\Ownable;
+<?php
+
+namespace BookStack\Entities\Models;
+
+use BookStack\Activity\Models\Activity;
+use BookStack\Activity\Models\Comment;
+use BookStack\Activity\Models\Favouritable;
+use BookStack\Activity\Models\Favourite;
+use BookStack\Activity\Models\Loggable;
+use BookStack\Activity\Models\Tag;
+use BookStack\Activity\Models\View;
+use BookStack\Activity\Models\Viewable;
+use BookStack\Activity\Models\Watch;
+use BookStack\App\Model;
+use BookStack\App\Sluggable;
+use BookStack\Entities\Tools\SlugGenerator;
+use BookStack\Permissions\JointPermissionBuilder;
+use BookStack\Permissions\Models\EntityPermission;
+use BookStack\Permissions\Models\JointPermission;
+use BookStack\Permissions\PermissionApplicator;
+use BookStack\References\Reference;
+use BookStack\Search\SearchIndex;
+use BookStack\Search\SearchTerm;
+use BookStack\Users\Models\HasCreatorAndUpdater;
+use BookStack\Users\Models\HasOwner;
 use Carbon\Carbon;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Collection;
@@ -19,48 +34,47 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * The base class for book-like items such as pages, chapters & books.
  * This is not a database model in itself but extended.
  *
- * @property int $id
- * @property string $name
- * @property string $slug
- * @property Carbon $created_at
- * @property Carbon $updated_at
- * @property int $created_by
- * @property int $updated_by
- * @property boolean $restricted
+ * @property int        $id
+ * @property string     $name
+ * @property string     $slug
+ * @property Carbon     $created_at
+ * @property Carbon     $updated_at
+ * @property Carbon     $deleted_at
+ * @property int        $created_by
+ * @property int        $updated_by
  * @property Collection $tags
+ *
  * @method static Entity|Builder visible()
- * @method static Entity|Builder hasPermission(string $permission)
  * @method static Builder withLastView()
  * @method static Builder withViewCount()
  */
-class Entity extends Ownable
+abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, Deletable, Loggable
 {
     use SoftDeletes;
+    use HasCreatorAndUpdater;
+    use HasOwner;
 
     /**
      * @var string - Name of property where the main text content is found
      */
-    public $textField = 'description';
+    public string $textField = 'description';
 
     /**
-     * @var float - Multiplier for search indexing.
+     * @var string - Name of the property where the main HTML content is found
      */
-    public $searchFactor = 1.0;
+    public string $htmlField = 'description_html';
 
     /**
-     * Get the entities that are visible to the current user.
+     * @var float - Multiplier for search indexing.
      */
-    public function scopeVisible(Builder $query)
-    {
-        return $this->scopeHasPermission($query, 'view');
-    }
+    public float $searchFactor = 1.0;
 
     /**
-     * Scope the query to those entities that the current user has the given permission for.
+     * Get the entities that are visible to the current user.
      */
-    public function scopeHasPermission(Builder $query, string $permission)
+    public function scopeVisible(Builder $query): Builder
     {
-        return Permissions::restrictEntityQuery($query, $permission);
+        return app()->make(PermissionApplicator::class)->restrictEntityQuery($query);
     }
 
     /**
@@ -92,32 +106,26 @@ class Entity extends Ownable
     /**
      * Compares this entity to another given entity.
      * Matches by comparing class and id.
-     * @param $entity
-     * @return bool
      */
-    public function matches($entity)
+    public function matches(self $entity): bool
     {
         return [get_class($this), $this->id] === [get_class($entity), $entity->id];
     }
 
     /**
-     * Checks if an entity matches or contains another given entity.
-     * @param Entity $entity
-     * @return bool
+     * Checks if the current entity matches or contains the given.
      */
-    public function matchesOrContains(Entity $entity)
+    public function matchesOrContains(self $entity): bool
     {
-        $matches = [get_class($this), $this->id] === [get_class($entity), $entity->id];
-
-        if ($matches) {
+        if ($this->matches($entity)) {
             return true;
         }
 
-        if (($entity->isA('chapter') || $entity->isA('page')) && $this->isA('book')) {
+        if (($entity instanceof BookChild) && $this instanceof Book) {
             return $entity->book_id === $this->id;
         }
 
-        if ($entity->isA('page') && $this->isA('chapter')) {
+        if ($entity instanceof Page && $this instanceof Chapter) {
             return $entity->chapter_id === $this->id;
         }
 
@@ -126,47 +134,43 @@ class Entity extends Ownable
 
     /**
      * 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, 'loggable')
             ->orderBy('created_at', 'desc');
     }
 
     /**
      * Get View objects for this entity.
      */
-    public function views()
+    public function views(): MorphMany
     {
         return $this->morphMany(View::class, 'viewable');
     }
 
     /**
      * 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');
     }
 
     /**
-     * Get the comments for an entity
-     * @param bool $orderByCreated
-     * @return MorphMany
+     * Get the comments for an entity.
      */
-    public function comments($orderByCreated = true)
+    public function comments(bool $orderByCreated = true): MorphMany
     {
         $query = $this->morphMany(Comment::class, 'entity');
+
         return $orderByCreated ? $query->orderBy('created_at', 'asc') : $query;
     }
 
     /**
      * Get the related search terms.
-     * @return MorphMany
      */
-    public function searchTerms()
+    public function searchTerms(): MorphMany
     {
         return $this->morphMany(SearchTerm::class, 'entity');
     }
@@ -174,21 +178,17 @@ class Entity extends Ownable
     /**
      * Get this entities restrictions.
      */
-    public function permissions()
+    public function permissions(): MorphMany
     {
-        return $this->morphMany(EntityPermission::class, 'restrictable');
+        return $this->morphMany(EntityPermission::class, 'entity');
     }
 
     /**
      * 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 hasPermissions(): bool
     {
-        return $this->permissions()->where('role_id', '=', $role_id)
-            ->where('action', '=', $action)->count() > 0;
+        return $this->permissions()->count() > 0;
     }
 
     /**
@@ -208,37 +208,40 @@ class Entity extends Ownable
     }
 
     /**
-     * Check if this instance or class is a certain type of entity.
-     * Examples of $type are 'page', 'book', 'chapter'
+     * Get the references pointing from this entity to other items.
      */
-    public static function isA(string $type): bool
+    public function referencesFrom(): MorphMany
     {
-        return static::getType() === strtolower($type);
+        return $this->morphMany(Reference::class, 'from');
     }
 
     /**
-     * Get entity type.
-     * @return mixed
+     * Get the references pointing to this entity from other items.
      */
-    public static function getType()
+    public function referencesTo(): MorphMany
     {
-        return strtolower(static::getClassName());
+        return $this->morphMany(Reference::class, 'to');
     }
 
     /**
-     * Get an instance of an entity of the given type.
-     * @param $type
-     * @return Entity
+     * Check if this instance or class is a certain type of entity.
+     * Examples of $type are 'page', 'book', 'chapter'.
+     *
+     * @deprecated Use instanceof instead.
      */
-    public static function getEntityInstance($type)
+    public static function isA(string $type): bool
     {
-        $types = ['Page', 'Book', 'Chapter', 'Bookshelf'];
-        $className = str_replace([' ', '-', '_'], '', ucwords($type));
-        if (!in_array($className, $types)) {
-            return null;
-        }
+        return static::getType() === strtolower($type);
+    }
+
+    /**
+     * Get the entity type as a simple lowercase word.
+     */
+    public static function getType(): string
+    {
+        $className = array_slice(explode('\\', static::class), -1, 1)[0];
 
-        return app('BookStack\\Entities\\' . $className);
+        return strtolower($className);
     }
 
     /**
@@ -249,55 +252,43 @@ class Entity extends Ownable
         if (mb_strlen($this->name) <= $length) {
             return $this->name;
         }
-        return mb_substr($this->name, 0, $length - 3) . '...';
-    }
 
-    /**
-     * Get the body text of this entity.
-     * @return mixed
-     */
-    public function getText()
-    {
-        return $this->{$this->textField};
+        return mb_substr($this->name, 0, $length - 3) . '...';
     }
 
     /**
      * 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->{$this->textField} ?? '';
+
         if (mb_strlen($text) > $length) {
-            $text = mb_substr($text, 0, $length-3) . '...';
+            $text = mb_substr($text, 0, $length - 3) . '...';
         }
+
         return trim($text);
     }
 
     /**
-     * Get the url of this entity
-     * @param $path
-     * @return string
+     * Get the url of this entity.
      */
-    public function getUrl($path = '/')
-    {
-        return $path;
-    }
+    abstract public function getUrl(string $path = '/'): string;
 
     /**
      * Get the parent entity if existing.
      * This is the "static" parent and does not include dynamic
      * relations such as shelves to books.
      */
-    public function getParent(): ?Entity
+    public function getParent(): ?self
     {
-        if ($this->isA('page')) {
+        if ($this instanceof Page) {
             return $this->chapter_id ? $this->chapter()->withTrashed()->first() : $this->book()->withTrashed()->first();
         }
-        if ($this->isA('chapter')) {
+        if ($this instanceof Chapter) {
             return $this->book()->withTrashed()->first();
         }
+
         return null;
     }
 
@@ -306,25 +297,58 @@ class Entity extends Ownable
      */
     public function rebuildPermissions()
     {
-        /** @noinspection PhpUnhandledExceptionInspection */
-        Permissions::buildJointPermissionsForEntity(clone $this);
+        app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
     }
 
     /**
-     * Index the current entity for search
+     * Index the current entity for search.
      */
     public function indexForSearch()
     {
-        $searchService = app()->make(SearchService::class);
-        $searchService->indexEntity(clone $this);
+        app()->make(SearchIndex::class)->indexEntity(clone $this);
     }
 
     /**
-     * Generate and set a new URL slug for this model.
+     * {@inheritdoc}
      */
     public function refreshSlug(): string
     {
-        $this->slug = (new SlugGenerator)->generate($this);
+        $this->slug = app()->make(SlugGenerator::class)->generate($this);
+
         return $this->slug;
     }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function favourites(): MorphMany
+    {
+        return $this->morphMany(Favourite::class, 'favouritable');
+    }
+
+    /**
+     * Check if the entity is a favourite of the current user.
+     */
+    public function isFavourite(): bool
+    {
+        return $this->favourites()
+            ->where('user_id', '=', user()->id)
+            ->exists();
+    }
+
+    /**
+     * Get the related watches for this entity.
+     */
+    public function watches(): MorphMany
+    {
+        return $this->morphMany(Watch::class, 'watchable');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function logDescriptor(): string
+    {
+        return "({$this->id}) {$this->name}";
+    }
 }