1
0
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:
Dan Brown
2025-09-02 11:10:47 +01:00
parent 5ea4e1e935
commit 1e34954554
18 changed files with 94 additions and 57 deletions

View File

@@ -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
{

View File

@@ -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';

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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);
}
}

View 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;
}

View 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();
}
}
}

View File

@@ -66,6 +66,9 @@ class PageQueries implements ProvidesEntityQueries
});
}
/**
* @return Builder<Page>
*/
public function visibleForList(): Builder
{
return $this->start()

View File

@@ -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']);
}
}
}

View File

@@ -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);

View File

@@ -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');

View File

@@ -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());
}

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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'] = "![{$mdEscapedName}]({$mdEscapedThumb})";
}