]> BookStack Code Mirror - bookstack/commitdiff
Sorting: Added book autosort logic
authorDan Brown <redacted>
Wed, 5 Feb 2025 16:52:20 +0000 (16:52 +0000)
committerDan Brown <redacted>
Wed, 5 Feb 2025 16:52:20 +0000 (16:52 +0000)
app/Entities/Repos/BaseRepo.php
app/Entities/Repos/ChapterRepo.php
app/Entities/Repos/PageRepo.php
app/Sorting/BookSorter.php
app/Sorting/SortSetOperation.php
app/Sorting/SortSetOperationComparisons.php [new file with mode: 0644]

index 033350743e0dd824bc259254d67e80edee3c187c..151d5b0555bbc5fc884c23b8612d10e73884b36d 100644 (file)
@@ -4,6 +4,7 @@ namespace BookStack\Entities\Repos;
 
 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;
@@ -12,6 +13,7 @@ use BookStack\Entities\Queries\PageQueries;
 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;
@@ -24,6 +26,7 @@ class BaseRepo
         protected ReferenceUpdater $referenceUpdater,
         protected ReferenceStore $referenceStore,
         protected PageQueries $pageQueries,
+        protected BookSorter $bookSorter,
     ) {
     }
 
@@ -134,6 +137,18 @@ class BaseRepo
         $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))) {
index 17cbccd4133676bc44aac8aaf844478ba771e5c7..fdf2de4e20235b81d39d3fed3ed7b837cc473cc4 100644 (file)
@@ -34,6 +34,8 @@ class ChapterRepo
         $this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
         Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
 
+        $this->baseRepo->sortParent($chapter);
+
         return $chapter;
     }
 
@@ -50,6 +52,8 @@ class ChapterRepo
 
         Activity::add(ActivityType::CHAPTER_UPDATE, $chapter);
 
+        $this->baseRepo->sortParent($chapter);
+
         return $chapter;
     }
 
@@ -88,6 +92,8 @@ class ChapterRepo
         $chapter->rebuildPermissions();
         Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
 
+        $this->baseRepo->sortParent($chapter);
+
         return $parent;
     }
 }
index 68b1c398f801d22ac3d74211f2e10714505083f4..c3be6d826a26dd87a3eea69aa9646abadcd664ca 100644 (file)
@@ -83,6 +83,7 @@ class PageRepo
         $draft->refresh();
 
         Activity::add(ActivityType::PAGE_CREATE, $draft);
+        $this->baseRepo->sortParent($draft);
 
         return $draft;
     }
@@ -128,6 +129,7 @@ class PageRepo
         }
 
         Activity::add(ActivityType::PAGE_UPDATE, $page);
+        $this->baseRepo->sortParent($page);
 
         return $page;
     }
@@ -243,6 +245,8 @@ class PageRepo
         Activity::add(ActivityType::PAGE_RESTORE, $page);
         Activity::add(ActivityType::REVISION_RESTORE, $revision);
 
+        $this->baseRepo->sortParent($page);
+
         return $page;
     }
 
@@ -272,6 +276,8 @@ class PageRepo
 
         Activity::add(ActivityType::PAGE_MOVE, $page);
 
+        $this->baseRepo->sortParent($page);
+
         return $parent;
     }
 
index 7268b3543a3c6b268f03e2b080529e621f1c6341..e89fdaccc31df7f6ddd3e5c4b50878206e9a961c 100644 (file)
@@ -16,6 +16,54 @@ class BookSorter
     ) {
     }
 
+    /**
+     * Runs the auto-sort for a book if the book has a sort set applied to it.
+     * This does not consider permissions since the sort operations are centrally
+     * managed by admins so considered permitted if existing and assigned.
+     */
+    public function runBookAutoSort(Book $book): void
+    {
+        $set = $book->sortSet;
+        if (!$set) {
+            return;
+        }
+
+        $sortFunctions = array_map(function (SortSetOperation $op) {
+            return $op->getSortFunction();
+        }, $set->getOperations());
+
+        $chapters = $book->chapters()
+            ->with('pages:id,name,priority,created_at,updated_at')
+            ->get(['id', 'name', 'priority', 'created_at', 'updated_at']);
+
+        /** @var (Chapter|Book)[] $topItems */
+        $topItems = [
+            ...$book->directPages()->get(['id', 'name', 'priority', 'created_at', 'updated_at']),
+            ...$chapters,
+        ];
+
+        foreach ($sortFunctions as $sortFunction) {
+            usort($topItems, $sortFunction);
+        }
+
+        foreach ($topItems as $index => $topItem) {
+            $topItem->priority = $index + 1;
+            $topItem->save();
+        }
+
+        foreach ($chapters as $chapter) {
+            $pages = $chapter->pages->all();
+            foreach ($sortFunctions as $sortFunction) {
+                usort($pages, $sortFunction);
+            }
+
+            foreach ($pages as $index => $page) {
+                $page->priority = $index + 1;
+                $page->save();
+            }
+        }
+    }
+
 
     /**
      * Sort the books content using the given sort map.
index a6dd860f5c756acfda872917f52b5cd828bd0f5d..7fdd0b002bff2d610f5bd27decb9c8c1d79fd5c8 100644 (file)
@@ -2,6 +2,9 @@
 
 namespace BookStack\Sorting;
 
+use Closure;
+use Illuminate\Support\Str;
+
 enum SortSetOperation: string
 {
     case NameAsc = 'name_asc';
@@ -33,6 +36,12 @@ enum SortSetOperation: string
         return trim($label);
     }
 
+    public function getSortFunction(): callable
+    {
+        $camelValue = Str::camel($this->value);
+        return SortSetOperationComparisons::$camelValue(...);
+    }
+
     /**
      * @return SortSetOperation[]
      */
diff --git a/app/Sorting/SortSetOperationComparisons.php b/app/Sorting/SortSetOperationComparisons.php
new file mode 100644 (file)
index 0000000..e1c3e62
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+namespace BookStack\Sorting;
+
+use BookStack\Entities\Models\Chapter;
+use BookStack\Entities\Models\Entity;
+
+/**
+ * Sort comparison function for each of the possible SortSetOperation values.
+ * Method names should be camelCase names for the SortSetOperation enum value.
+ * TODO - Test to cover each SortSetOperation enum value is covered.
+ */
+class SortSetOperationComparisons
+{
+    public static function nameAsc(Entity $a, Entity $b): int
+    {
+        return $a->name <=> $b->name;
+    }
+
+    public static function nameDesc(Entity $a, Entity $b): int
+    {
+        return $b->name <=> $a->name;
+    }
+
+    public static function nameNumericAsc(Entity $a, Entity $b): int
+    {
+        $numRegex = '/^\d+(\.\d+)?/';
+        $aMatches = [];
+        $bMatches = [];
+        preg_match($numRegex, $a, $aMatches);
+        preg_match($numRegex, $b, $bMatches);
+        return ($aMatches[0] ?? 0) <=> ($bMatches[0] ?? 0);
+    }
+
+    public static function nameNumericDesc(Entity $a, Entity $b): int
+    {
+        return -(static::nameNumericAsc($a, $b));
+    }
+
+    public static function createdDateAsc(Entity $a, Entity $b): int
+    {
+        return $a->created_at->unix() <=> $b->created_at->unix();
+    }
+
+    public static function createdDateDesc(Entity $a, Entity $b): int
+    {
+        return $b->created_at->unix() <=> $a->created_at->unix();
+    }
+
+    public static function updatedDateAsc(Entity $a, Entity $b): int
+    {
+        return $a->updated_at->unix() <=> $b->updated_at->unix();
+    }
+
+    public static function updatedDateDesc(Entity $a, Entity $b): int
+    {
+        return $b->updated_at->unix() <=> $a->updated_at->unix();
+    }
+
+    public static function chaptersFirst(Entity $a, Entity $b): int
+    {
+        return ($b instanceof Chapter ? 1 : 0) - (($a instanceof Chapter) ? 1 : 0);
+    }
+
+    public static function chaptersLast(Entity $a, Entity $b): int
+    {
+        return ($a instanceof Chapter ? 1 : 0) - (($b instanceof Chapter) ? 1 : 0);
+    }
+}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.