Also ran auto-removal of unused imports across app folder.
namespace BookStack\Activity\Controllers;
use BookStack\Activity\CommentRepo;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
namespace BookStack\App;
use BookStack\Activity\ActivityQueries;
-use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Queries\RecentlyViewed;
use BookStack\Theming\ThemeEvents;
use BookStack\Theming\ThemeService;
-use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class ThemeServiceProvider extends ServiceProvider
namespace BookStack\Console\Commands;
-use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Queries\BookshelfQueries;
use BookStack\Entities\Tools\PermissionsUpdater;
use Illuminate\Console\Command;
use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\View;
use BookStack\Activity\Tools\UserEntityWatchOptions;
-use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Queries\BookshelfQueries;
use BookStack\Entities\Repos\BookRepo;
namespace BookStack\Entities\Controllers;
-use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Queries\ChapterQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Http\ApiController;
use BookStack\Activity\Tools\UserEntityWatchOptions;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Repos\PageRepo;
namespace BookStack\Entities\Controllers;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Http\ApiController;
* @property int $priority
* @property string $book_slug
* @property Book $book
- *
- * @method Builder whereSlugs(string $bookSlug, string $childSlug)
*/
abstract class BookChild extends Entity
{
- /**
- * Scope a query to find items where the child has the given childSlug
- * where its parent has the bookSlug.
- */
- public function scopeWhereSlugs(Builder $query, string $bookSlug, string $childSlug)
- {
- return $query->with('book')
- ->whereHas('book', function (Builder $query) use ($bookSlug) {
- $query->where('slug', '=', $bookSlug);
- })
- ->where('slug', '=', $childSlug);
- }
-
/**
* Get the book this page sits in.
*/
->orderBy('priority', 'asc')
->get();
}
-
- /**
- * Get a visible chapter by its book and page slugs.
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public static function getBySlugs(string $bookSlug, string $chapterSlug): self
- {
- return static::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail();
- }
}
{
use HasFactory;
- public static $listAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'text', 'created_at', 'updated_at', 'priority'];
- public static $contentAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'html', 'text', 'created_at', 'updated_at', 'priority'];
-
protected $fillable = ['name', 'priority'];
public string $textField = 'text';
return $refreshed;
}
-
- /**
- * Get a visible page by its book and page slugs.
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public static function getBySlugs(string $bookSlug, string $pageSlug): self
- {
- return static::visible()->whereSlugs($bookSlug, $pageSlug)->firstOrFail();
- }
}
class BookQueries implements ProvidesEntityQueries
{
+ protected static array $listAttributes = [
+ 'id', 'slug', 'name', 'description', 'created_at', 'updated_at', 'image_id'
+ ];
+
public function start(): Builder
{
return Book::query();
public function visibleForList(): Builder
{
- return $this->start()->scopes('visible');
+ return $this->start()->scopes('visible')
+ ->select(static::$listAttributes);
}
public function visibleForListWithCover(): Builder
class BookshelfQueries implements ProvidesEntityQueries
{
+ protected static array $listAttributes = [
+ 'id', 'slug', 'name', 'description', 'created_at', 'updated_at', 'image_id'
+ ];
+
public function start(): Builder
{
return Bookshelf::query();
public function visibleForList(): Builder
{
- return $this->start()->scopes('visible');
+ return $this->start()->scopes('visible')->select(static::$listAttributes);
}
public function visibleForListWithCover(): Builder
{
protected static array $listAttributes = [
'id', 'slug', 'name', 'description', 'priority',
- 'created_at', 'updated_at',
- 'created_by', 'updated_by', 'owned_by',
+ 'created_at', 'updated_at'
];
public function start(): Builder
namespace BookStack\Entities\Queries;
use BookStack\Entities\Models\Entity;
+use Illuminate\Database\Eloquent\Builder;
+use InvalidArgumentException;
class EntityQueries
{
$explodedId = explode(':', $identifier);
$entityType = $explodedId[0];
$entityId = intval($explodedId[1]);
+ $queries = $this->getQueriesForType($entityType);
+ return $queries->findVisibleById($entityId);
+ }
+
+ /**
+ * Start a query of visible entities of the given type,
+ * suitable for listing display.
+ */
+ public function visibleForList(string $entityType): Builder
+ {
+ $queries = $this->getQueriesForType($entityType);
+ return $queries->visibleForList();
+ }
+
+ protected function getQueriesForType(string $type): ProvidesEntityQueries
+ {
/** @var ?ProvidesEntityQueries $queries */
- $queries = match ($entityType) {
+ $queries = match ($type) {
'page' => $this->pages,
'chapter' => $this->chapters,
'book' => $this->books,
};
if (is_null($queries)) {
- return null;
+ throw new InvalidArgumentException("No entity query class configured for {$type}");
}
- return $queries->findVisibleById($entityId);
+ return $queries;
}
}
class PageQueries implements ProvidesEntityQueries
{
+ protected static array $contentAttributes = [
+ 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft',
+ 'template', 'html', 'text', 'created_at', 'updated_at', 'priority'
+ ];
+ protected static array $listAttributes = [
+ 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft',
+ 'template', 'text', 'created_at', 'updated_at', 'priority'
+ ];
+
public function start(): Builder
{
return Page::query();
{
return $this->start()
->scopes('visible')
- ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) {
- $builder->select('slug')
- ->from('books')
- ->whereColumn('books.id', '=', 'pages.book_id');
- }]));
+ ->select($this->mergeBookSlugForSelect(static::$listAttributes));
}
public function visibleWithContents(): Builder
{
return $this->start()
->scopes('visible')
- ->select(array_merge(Page::$contentAttributes, ['book_slug' => function ($builder) {
- $builder->select('slug')
- ->from('books')
- ->whereColumn('books.id', '=', 'pages.book_id');
- }]));
+ ->select($this->mergeBookSlugForSelect(static::$contentAttributes));
}
public function currentUserDraftsForList(): Builder
return $this->visibleForList()
->where('template', '=', true);
}
+
+ protected function mergeBookSlugForSelect(array $columns): array
+ {
+ return array_merge($columns, ['book_slug' => function ($builder) {
+ $builder->select('slug')
+ ->from('books')
+ ->whereColumn('books.id', '=', 'pages.book_id');
+ }]);
+ }
}
*/
interface ProvidesEntityQueries
{
+ /**
+ * Start a new query for this entity type.
+ */
public function start(): Builder;
+
+ /**
+ * Find the entity of the given ID, or return null if not found.
+ */
public function findVisibleById(int $id): ?Entity;
+
+ /**
+ * Start a query for items that are visible, with selection
+ * configured for list display of this item.
+ */
+ public function visibleForList(): Builder;
}
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Entities\Models\HasHtmlDescription;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Exceptions\ImageUploadException;
use BookStack\References\ReferenceStore;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
-use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
namespace BookStack\References;
-use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Bookshelf;
-use BookStack\Entities\Models\Chapter;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Http\Controller;
namespace BookStack\Search;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Queries\Popular;
use BookStack\Entities\Tools\SiblingFetcher;
namespace BookStack\Search;
use BookStack\Entities\EntityProvider;
-use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
+use BookStack\Entities\Queries\EntityQueries;
use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\User;
use Illuminate\Database\Connection;
class SearchRunner
{
- protected EntityProvider $entityProvider;
- protected PermissionApplicator $permissions;
-
/**
* Acceptable operators to be used in a query.
*
*/
protected $termAdjustmentCache;
- public function __construct(EntityProvider $entityProvider, PermissionApplicator $permissions)
- {
- $this->entityProvider = $entityProvider;
- $this->permissions = $permissions;
+ public function __construct(
+ protected EntityProvider $entityProvider,
+ protected PermissionApplicator $permissions,
+ protected EntityQueries $entityQueries,
+ ) {
$this->termAdjustmentCache = new SplObjectStorage();
}
continue;
}
- $entityModelInstance = $this->entityProvider->get($entityType);
- $searchQuery = $this->buildQuery($searchOpts, $entityModelInstance);
+ $searchQuery = $this->buildQuery($searchOpts, $entityType);
$entityTotal = $searchQuery->count();
- $searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityModelInstance, $page, $count);
+ $searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityType, $page, $count);
if ($entityTotal > ($page * $count)) {
$hasMore = true;
continue;
}
- $entityModelInstance = $this->entityProvider->get($entityType);
- $search = $this->buildQuery($opts, $entityModelInstance)->where('book_id', '=', $bookId)->take(20)->get();
+ $search = $this->buildQuery($opts, $entityType)->where('book_id', '=', $bookId)->take(20)->get();
$results = $results->merge($search);
}
public function searchChapter(int $chapterId, string $searchString): Collection
{
$opts = SearchOptions::fromString($searchString);
- $entityModelInstance = $this->entityProvider->get('page');
- $pages = $this->buildQuery($opts, $entityModelInstance)->where('chapter_id', '=', $chapterId)->take(20)->get();
+ $pages = $this->buildQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get();
return $pages->sortByDesc('score');
}
/**
* Get a page of result data from the given query based on the provided page parameters.
*/
- protected function getPageOfDataFromQuery(EloquentBuilder $query, Entity $entityModelInstance, int $page = 1, int $count = 20): EloquentCollection
+ protected function getPageOfDataFromQuery(EloquentBuilder $query, string $entityType, int $page = 1, int $count = 20): EloquentCollection
{
$relations = ['tags'];
- if ($entityModelInstance instanceof BookChild) {
+ if ($entityType === 'page' || $entityType === 'chapter') {
$relations['book'] = function (BelongsTo $query) {
$query->scopes('visible');
};
}
- if ($entityModelInstance instanceof Page) {
+ if ($entityType === 'page') {
$relations['chapter'] = function (BelongsTo $query) {
$query->scopes('visible');
};
/**
* Create a search query for an entity.
*/
- protected function buildQuery(SearchOptions $searchOpts, Entity $entityModelInstance): EloquentBuilder
+ protected function buildQuery(SearchOptions $searchOpts, string $entityType): EloquentBuilder
{
- $entityQuery = $entityModelInstance->newQuery()->scopes('visible');
-
- if ($entityModelInstance instanceof Page) {
- $entityQuery->select(array_merge($entityModelInstance::$listAttributes, ['owned_by']));
- } else {
- $entityQuery->select(['*']);
- }
+ $entityModelInstance = $this->entityProvider->get($entityType);
+ $entityQuery = $this->entityQueries->visibleForList($entityType);
// Handle normal search terms
- $this->applyTermSearch($entityQuery, $searchOpts, $entityModelInstance);
+ $this->applyTermSearch($entityQuery, $searchOpts, $entityType);
// Handle exact term matching
foreach ($searchOpts->exacts as $inputTerm) {
/**
* For the given search query, apply the queries for handling the regular search terms.
*/
- protected function applyTermSearch(EloquentBuilder $entityQuery, SearchOptions $options, Entity $entity): void
+ protected function applyTermSearch(EloquentBuilder $entityQuery, SearchOptions $options, string $entityType): void
{
$terms = $options->searches;
if (count($terms) === 0) {
$subQuery->addBinding($scoreSelect['bindings'], 'select');
- $subQuery->where('entity_type', '=', $entity->getMorphClass());
+ $subQuery->where('entity_type', '=', $entityType);
$subQuery->where(function (Builder $query) use ($terms) {
foreach ($terms as $inputTerm) {
$inputTerm = str_replace('\\', '\\\\', $inputTerm);
use BookStack\Exceptions\FileUploadException;
use Exception;
-use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem as Storage;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Support\Facades\Log;
namespace BookStack\Uploads\Controllers;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Exceptions\FileUploadException;
use BookStack\Http\ApiController;
use BookStack\Uploads\ImageResizer;
use BookStack\Util\OutOfMemoryHandler;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\App;
-use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class GalleryImageController extends Controller
namespace BookStack\Uploads\Controllers;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Http\ApiController;
use BookStack\Uploads\Image;
namespace BookStack\Uploads;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Permissions\PermissionApplicator;
namespace BookStack\Uploads;
-use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Bookshelf;
-use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Exceptions\ImageUploadException;
use Exception;