mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-09-09 06:29:32 +03:00
Maintenance: Continued work towards PHPstan level 2
Updated html description code to be behind a proper interface. Set new convention for mode traits/interfaces.
This commit is contained in:
@@ -26,10 +26,10 @@ use Illuminate\Support\Collection;
|
||||
* @property ?Page $defaultTemplate
|
||||
* @property ?SortRule $sortRule
|
||||
*/
|
||||
class Book extends Entity implements HasCoverImage
|
||||
class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
use HtmlDescriptionTrait;
|
||||
|
||||
public float $searchFactor = 1.2;
|
||||
|
||||
@@ -111,6 +111,7 @@ class Book extends Entity implements HasCoverImage
|
||||
|
||||
/**
|
||||
* Get all chapters within this book.
|
||||
* @return HasMany<Chapter>
|
||||
*/
|
||||
public function chapters(): HasMany
|
||||
{
|
||||
|
@@ -8,10 +8,10 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Bookshelf extends Entity implements HasCoverImage
|
||||
class Bookshelf extends Entity implements CoverImageInterface, HtmlDescriptionInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
use HtmlDescriptionTrait;
|
||||
|
||||
protected $table = 'bookshelves';
|
||||
|
||||
|
@@ -14,10 +14,10 @@ use Illuminate\Support\Collection;
|
||||
* @property ?int $default_template_id
|
||||
* @property ?Page $defaultTemplate
|
||||
*/
|
||||
class Chapter extends BookChild
|
||||
class Chapter extends BookChild implements HtmlDescriptionInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
use HtmlDescriptionTrait;
|
||||
|
||||
public float $searchFactor = 1.2;
|
||||
|
||||
|
@@ -4,7 +4,7 @@ namespace BookStack\Entities\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
interface HasCoverImage
|
||||
interface CoverImageInterface
|
||||
{
|
||||
/**
|
||||
* Get the cover image for this item.
|
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
* A model that can be deleted in a manner that deletions
|
||||
* are tracked to be part of the recycle bin system.
|
||||
*/
|
||||
interface Deletable
|
||||
interface DeletableInterface
|
||||
{
|
||||
public function deletions(): MorphMany;
|
||||
}
|
@@ -13,7 +13,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
* @property int $deleted_by
|
||||
* @property string $deletable_type
|
||||
* @property int $deletable_id
|
||||
* @property Deletable $deletable
|
||||
* @property DeletableInterface $deletable
|
||||
*/
|
||||
class Deletion extends Model implements Loggable
|
||||
{
|
||||
|
@@ -49,7 +49,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @method static Builder withLastView()
|
||||
* @method static Builder withViewCount()
|
||||
*/
|
||||
abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, Deletable, Loggable
|
||||
abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, DeletableInterface, Loggable
|
||||
{
|
||||
use SoftDeletes;
|
||||
use HasCreatorAndUpdater;
|
||||
|
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
use BookStack\Util\HtmlContentFilter;
|
||||
|
||||
/**
|
||||
* @property string $description
|
||||
* @property string $description_html
|
||||
*/
|
||||
trait HasHtmlDescription
|
||||
{
|
||||
/**
|
||||
* Get the HTML description for this book.
|
||||
*/
|
||||
public function descriptionHtml(): string
|
||||
{
|
||||
$html = $this->description_html ?: '<p>' . nl2br(e($this->description)) . '</p>';
|
||||
return HtmlContentFilter::removeScriptsFromHtmlString($html);
|
||||
}
|
||||
}
|
17
app/Entities/Models/HtmlDescriptionInterface.php
Normal file
17
app/Entities/Models/HtmlDescriptionInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
interface HtmlDescriptionInterface
|
||||
{
|
||||
/**
|
||||
* Get the HTML-based description for this item.
|
||||
* By default, the content should be sanitised unless raw is set to true.
|
||||
*/
|
||||
public function descriptionHtml(bool $raw = false): string;
|
||||
|
||||
/**
|
||||
* Set the HTML-based description for this item.
|
||||
*/
|
||||
public function setDescriptionHtml(string $html, string|null $plaintext = null): void;
|
||||
}
|
35
app/Entities/Models/HtmlDescriptionTrait.php
Normal file
35
app/Entities/Models/HtmlDescriptionTrait.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
use BookStack\Util\HtmlContentFilter;
|
||||
|
||||
/**
|
||||
* @property string $description
|
||||
* @property string $description_html
|
||||
*/
|
||||
trait HtmlDescriptionTrait
|
||||
{
|
||||
public function descriptionHtml(bool $raw = false): string
|
||||
{
|
||||
$html = $this->description_html ?: '<p>' . nl2br(e($this->description)) . '</p>';
|
||||
if ($raw) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return HtmlContentFilter::removeScriptsFromHtmlString($html);
|
||||
}
|
||||
|
||||
public function setDescriptionHtml(string $html, string|null $plaintext = null): void
|
||||
{
|
||||
$this->description_html = $html;
|
||||
|
||||
if ($plaintext !== null) {
|
||||
$this->description = $plaintext;
|
||||
}
|
||||
|
||||
if (empty($html) && !empty($plaintext)) {
|
||||
$this->description_html = $this->descriptionHtml();
|
||||
}
|
||||
}
|
||||
}
|
@@ -66,6 +66,9 @@ class PageQueries implements ProvidesEntityQueries
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<Page>
|
||||
*/
|
||||
public function visibleForList(): Builder
|
||||
{
|
||||
return $this->start()
|
||||
|
@@ -7,8 +7,9 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\HasHtmlDescription;
|
||||
use BookStack\Entities\Models\CoverImageInterface;
|
||||
use BookStack\Entities\Models\HtmlDescriptionInterface;
|
||||
use BookStack\Entities\Models\HtmlDescriptionTrait;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\References\ReferenceStore;
|
||||
@@ -88,7 +89,7 @@ class BaseRepo
|
||||
/**
|
||||
* Update the given items' cover image, or clear it.
|
||||
*
|
||||
* @param Entity&HasCoverImage $entity
|
||||
* @param Entity&CoverImageInterface $entity
|
||||
*
|
||||
* @throws ImageUploadException
|
||||
* @throws \Exception
|
||||
@@ -150,18 +151,17 @@ class BaseRepo
|
||||
|
||||
protected function updateDescription(Entity $entity, array $input): void
|
||||
{
|
||||
if (!in_array(HasHtmlDescription::class, class_uses($entity))) {
|
||||
if (!($entity instanceof HtmlDescriptionInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var HasHtmlDescription $entity */
|
||||
if (isset($input['description_html'])) {
|
||||
$entity->description_html = HtmlDescriptionFilter::filterFromString($input['description_html']);
|
||||
$entity->description = html_entity_decode(strip_tags($input['description_html']));
|
||||
$entity->setDescriptionHtml(
|
||||
HtmlDescriptionFilter::filterFromString($input['description_html']),
|
||||
html_entity_decode(strip_tags($input['description_html']))
|
||||
);
|
||||
} else if (isset($input['description'])) {
|
||||
$entity->description = $input['description'];
|
||||
$entity->description_html = '';
|
||||
$entity->description_html = $entity->descriptionHtml();
|
||||
$entity->setDescriptionHtml('', $input['description']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\CoverImageInterface;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Repos\ChapterRepo;
|
||||
@@ -105,7 +105,7 @@ class Cloner
|
||||
$inputData['tags'] = $this->entityTagsToInputArray($entity);
|
||||
|
||||
// Add a cover to the data if existing on the original entity
|
||||
if ($entity instanceof HasCoverImage) {
|
||||
if ($entity instanceof CoverImageInterface) {
|
||||
$cover = $entity->cover()->first();
|
||||
if ($cover) {
|
||||
$inputData['image'] = $this->imageToUploadedFile($cover);
|
||||
|
@@ -7,7 +7,6 @@ use Closure;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
|
||||
class PageIncludeParser
|
||||
{
|
||||
@@ -159,7 +158,7 @@ class PageIncludeParser
|
||||
|
||||
/**
|
||||
* Splits the given $parentNode at the location of the $domNode within it.
|
||||
* Attempts replicate the original $parentNode, moving some of their parent
|
||||
* Attempts to replicate the original $parentNode, moving some of their parent
|
||||
* children in where needed, before adding the $domNode between.
|
||||
*/
|
||||
protected function splitNodeAtChildNode(DOMElement $parentNode, DOMNode $domNode): void
|
||||
@@ -171,6 +170,10 @@ class PageIncludeParser
|
||||
}
|
||||
|
||||
$parentClone = $parentNode->cloneNode();
|
||||
if (!($parentClone instanceof DOMElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parentNode->parentNode->insertBefore($parentClone, $parentNode);
|
||||
$parentClone->removeAttribute('id');
|
||||
|
||||
|
@@ -8,7 +8,7 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Deletion;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\CoverImageInterface;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
@@ -398,7 +398,7 @@ class TrashCan
|
||||
$entity->referencesTo()->delete();
|
||||
$entity->referencesFrom()->delete();
|
||||
|
||||
if ($entity instanceof HasCoverImage && $entity->cover()->exists()) {
|
||||
if ($entity instanceof CoverImageInterface && $entity->cover()->exists()) {
|
||||
$imageService = app()->make(ImageService::class);
|
||||
$imageService->destroy($entity->cover()->first());
|
||||
}
|
||||
|
@@ -4,7 +4,8 @@ namespace BookStack\References;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasHtmlDescription;
|
||||
use BookStack\Entities\Models\HtmlDescriptionInterface;
|
||||
use BookStack\Entities\Models\HtmlDescriptionTrait;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\RevisionRepo;
|
||||
use BookStack\Util\HtmlDocument;
|
||||
@@ -61,20 +62,18 @@ class ReferenceUpdater
|
||||
{
|
||||
if ($entity instanceof Page) {
|
||||
$this->updateReferencesWithinPage($entity, $oldLink, $newLink);
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array(HasHtmlDescription::class, class_uses($entity))) {
|
||||
if ($entity instanceof HtmlDescriptionInterface) {
|
||||
$this->updateReferencesWithinDescription($entity, $oldLink, $newLink);
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateReferencesWithinDescription(Entity $entity, string $oldLink, string $newLink): void
|
||||
protected function updateReferencesWithinDescription(Entity&HtmlDescriptionInterface $entity, string $oldLink, string $newLink): void
|
||||
{
|
||||
/** @var HasHtmlDescription&Entity $entity */
|
||||
$entity = (clone $entity)->refresh();
|
||||
$html = $this->updateLinksInHtml($entity->description_html ?: '', $oldLink, $newLink);
|
||||
$entity->description_html = $html;
|
||||
$html = $this->updateLinksInHtml($entity->descriptionHtml(true) ?: '', $oldLink, $newLink);
|
||||
$entity->setDescriptionHtml($html);
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,7 @@ class SortRuleController extends Controller
|
||||
|
||||
$operations = SortRuleOperation::fromSequence($request->input('sequence'));
|
||||
if (count($operations) === 0) {
|
||||
return redirect()->withInput()->withErrors(['sequence' => 'No operations set.']);
|
||||
return redirect('/settings/sorting/rules/new')->withInput()->withErrors(['sequence' => 'No operations set.']);
|
||||
}
|
||||
|
||||
$rule = new SortRule();
|
||||
|
@@ -146,10 +146,10 @@ class ImageGalleryApiController extends ApiController
|
||||
$data['content']['html'] = "<div drawio-diagram=\"{$image->id}\"><img src=\"{$escapedUrl}\"></div>";
|
||||
$data['content']['markdown'] = $data['content']['html'];
|
||||
} else {
|
||||
$escapedDisplayThumb = htmlentities($image->thumbs['display']);
|
||||
$escapedDisplayThumb = htmlentities($image->getAttribute('thumbs')['display']);
|
||||
$data['content']['html'] = "<a href=\"{$escapedUrl}\" target=\"_blank\"><img src=\"{$escapedDisplayThumb}\" alt=\"{$escapedName}\"></a>";
|
||||
$mdEscapedName = str_replace(']', '', str_replace('[', '', $image->name));
|
||||
$mdEscapedThumb = str_replace(']', '', str_replace('[', '', $image->thumbs['display']));
|
||||
$mdEscapedThumb = str_replace(']', '', str_replace('[', '', $image->getAttribute('thumbs')['display']));
|
||||
$data['content']['markdown'] = "";
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user