Skip to content

Sort rules #5457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/Activity/ActivityType.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class ActivityType
const IMPORT_RUN = 'import_run';
const IMPORT_DELETE = 'import_delete';

const SORT_RULE_CREATE = 'sort_rule_create';
const SORT_RULE_UPDATE = 'sort_rule_update';
const SORT_RULE_DELETE = 'sort_rule_delete';

/**
* Get all the possible values.
*/
Expand Down
2 changes: 2 additions & 0 deletions app/Activity/Controllers/AuditLogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\Activity;
use BookStack\Http\Controller;
use BookStack\Sorting\SortUrl;
use BookStack\Util\SimpleListOptions;
use Illuminate\Http\Request;

Expand Down Expand Up @@ -65,6 +66,7 @@ public function index(Request $request)
'filters' => $filters,
'listOptions' => $listOptions,
'activityTypes' => $types,
'filterSortUrl' => new SortUrl('settings/audit', array_filter($request->except('page')))
]);
}
}
32 changes: 0 additions & 32 deletions app/App/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,35 +96,3 @@ function theme_path(string $path = ''): ?string

return base_path('themes/' . $theme . ($path ? DIRECTORY_SEPARATOR . $path : $path));
}

/**
* Generate a URL with multiple parameters for sorting purposes.
* Works out the logic to set the correct sorting direction
* Discards empty parameters and allows overriding.
*/
function sortUrl(string $path, array $data, array $overrideData = []): string
{
$queryStringSections = [];
$queryData = array_merge($data, $overrideData);

// Change sorting direction is already sorted on current attribute
if (isset($overrideData['sort']) && $overrideData['sort'] === $data['sort']) {
$queryData['order'] = ($data['order'] === 'asc') ? 'desc' : 'asc';
} elseif (isset($overrideData['sort'])) {
$queryData['order'] = 'asc';
}

foreach ($queryData as $name => $value) {
$trimmedVal = trim($value);
if ($trimmedVal === '') {
continue;
}
$queryStringSections[] = urlencode($name) . '=' . urlencode($trimmedVal);
}

if (count($queryStringSections) === 0) {
return url($path);
}

return url($path . '?' . implode('&', $queryStringSections));
}
99 changes: 99 additions & 0 deletions app/Console/Commands/AssignSortRuleCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace BookStack\Console\Commands;

use BookStack\Entities\Models\Book;
use BookStack\Sorting\BookSorter;
use BookStack\Sorting\SortRule;
use Illuminate\Console\Command;

class AssignSortRuleCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:assign-sort-rule
{sort-rule=0: ID of the sort rule to apply}
{--all-books : Apply to all books in the system}
{--books-without-sort : Apply to only books without a sort rule already assigned}
{--books-with-sort= : Apply to only books with the sort rule of given id}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Assign a sort rule to content in the system';

/**
* Execute the console command.
*/
public function handle(BookSorter $sorter): int
{
$sortRuleId = intval($this->argument('sort-rule')) ?? 0;
if ($sortRuleId === 0) {
return $this->listSortRules();
}

$rule = SortRule::query()->find($sortRuleId);
if ($this->option('all-books')) {
$query = Book::query();
} else if ($this->option('books-without-sort')) {
$query = Book::query()->whereNull('sort_rule_id');
} else if ($this->option('books-with-sort')) {
$sortId = intval($this->option('books-with-sort')) ?: 0;
if (!$sortId) {
$this->error("Provided --books-with-sort option value is invalid");
return 1;
}
$query = Book::query()->where('sort_rule_id', $sortId);
} else {
$this->error("No option provided to specify target. Run with the -h option to see all available options.");
return 1;
}

if (!$rule) {
$this->error("Sort rule of provided id {$sortRuleId} not found!");
return 1;
}

$count = $query->clone()->count();
$this->warn("This will apply sort rule [{$rule->id}: {$rule->name}] to {$count} book(s) and run the sort on each.");
$confirmed = $this->confirm("Are you sure you want to continue?");

if (!$confirmed) {
return 1;
}

$processed = 0;
$query->chunkById(10, function ($books) use ($rule, $sorter, $count, &$processed) {
$max = min($count, ($processed + 10));
$this->info("Applying to {$processed}-{$max} of {$count} books");
foreach ($books as $book) {
$book->sort_rule_id = $rule->id;
$book->save();
$sorter->runBookAutoSort($book);
}
$processed = $max;
});

$this->info("Sort applied to {$processed} book(s)!");

return 0;
}

protected function listSortRules(): int
{

$rules = SortRule::query()->orderBy('id', 'asc')->get();
$this->error("Sort rule ID required!");
$this->warn("\nAvailable sort rules:");
foreach ($rules as $rule) {
$this->info("{$rule->id}: {$rule->name}");
}

return 1;
}
}
11 changes: 11 additions & 0 deletions app/Entities/Models/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace BookStack\Entities\Models;

use BookStack\Sorting\SortRule;
use BookStack\Uploads\Image;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand All @@ -16,12 +17,14 @@
* @property string $description
* @property int $image_id
* @property ?int $default_template_id
* @property ?int $sort_rule_id
* @property Image|null $cover
* @property \Illuminate\Database\Eloquent\Collection $chapters
* @property \Illuminate\Database\Eloquent\Collection $pages
* @property \Illuminate\Database\Eloquent\Collection $directPages
* @property \Illuminate\Database\Eloquent\Collection $shelves
* @property ?Page $defaultTemplate
* @property ?SortRule $sortRule
*/
class Book extends Entity implements HasCoverImage
{
Expand Down Expand Up @@ -82,6 +85,14 @@ public function defaultTemplate(): BelongsTo
return $this->belongsTo(Page::class, 'default_template_id');
}

/**
* Get the sort set assigned to this book, if existing.
*/
public function sortRule(): BelongsTo
{
return $this->belongsTo(SortRule::class);
}

/**
* Get all pages within this book.
*/
Expand Down
15 changes: 15 additions & 0 deletions app/Entities/Repos/BaseRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use BookStack\Activity\TagRepo;
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;
Expand All @@ -12,6 +13,7 @@
use BookStack\Exceptions\ImageUploadException;
use BookStack\References\ReferenceStore;
use BookStack\References\ReferenceUpdater;
use BookStack\Sorting\BookSorter;
use BookStack\Uploads\ImageRepo;
use BookStack\Util\HtmlDescriptionFilter;
use Illuminate\Http\UploadedFile;
Expand All @@ -24,6 +26,7 @@ public function __construct(
protected ReferenceUpdater $referenceUpdater,
protected ReferenceStore $referenceStore,
protected PageQueries $pageQueries,
protected BookSorter $bookSorter,
) {
}

Expand Down Expand Up @@ -134,6 +137,18 @@ public function updateDefaultTemplate(Book|Chapter $entity, int $templateId): vo
$entity->save();
}

/**
* Sort the parent of the given entity, if any auto sort actions are set for it.
* Typical ran during create/update/insert events.
*/
public function sortParent(Entity $entity): void
{
if ($entity instanceof BookChild) {
$book = $entity->book;
$this->bookSorter->runBookAutoSort($book);
}
}

protected function updateDescription(Entity $entity, array $input): void
{
if (!in_array(HasHtmlDescription::class, class_uses($entity))) {
Expand Down
7 changes: 7 additions & 0 deletions app/Entities/Repos/BookRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Activity;
use BookStack\Sorting\SortRule;
use BookStack\Uploads\ImageRepo;
use Exception;
use Illuminate\Http\UploadedFile;
Expand All @@ -33,6 +34,12 @@ public function create(array $input): Book
$this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::BOOK_CREATE, $book);

$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
$book->sort_rule_id = $defaultBookSortSetting;
$book->save();
}

return $book;
}

Expand Down
6 changes: 6 additions & 0 deletions app/Entities/Repos/ChapterRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public function create(array $input, Book $parentBook): Chapter
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);

$this->baseRepo->sortParent($chapter);

return $chapter;
}

Expand All @@ -50,6 +52,8 @@ public function update(Chapter $chapter, array $input): Chapter

Activity::add(ActivityType::CHAPTER_UPDATE, $chapter);

$this->baseRepo->sortParent($chapter);

return $chapter;
}

Expand Down Expand Up @@ -88,6 +92,8 @@ public function move(Chapter $chapter, string $parentIdentifier): Book
$chapter->rebuildPermissions();
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);

$this->baseRepo->sortParent($chapter);

return $parent;
}
}
6 changes: 6 additions & 0 deletions app/Entities/Repos/PageRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public function publishDraft(Page $draft, array $input): Page
$draft->refresh();

Activity::add(ActivityType::PAGE_CREATE, $draft);
$this->baseRepo->sortParent($draft);

return $draft;
}
Expand Down Expand Up @@ -128,6 +129,7 @@ public function update(Page $page, array $input): Page
}

Activity::add(ActivityType::PAGE_UPDATE, $page);
$this->baseRepo->sortParent($page);

return $page;
}
Expand Down Expand Up @@ -243,6 +245,8 @@ public function restoreRevision(Page $page, int $revisionId): Page
Activity::add(ActivityType::PAGE_RESTORE, $page);
Activity::add(ActivityType::REVISION_RESTORE, $revision);

$this->baseRepo->sortParent($page);

return $page;
}

Expand Down Expand Up @@ -272,6 +276,8 @@ public function move(Page $page, string $parentIdentifier): Entity

Activity::add(ActivityType::PAGE_MOVE, $page);

$this->baseRepo->sortParent($page);

return $parent;
}

Expand Down
Loading
Loading