Sorting: Added book autosort logic
This commit is contained in:
		
							parent
							
								
									7093daa49d
								
							
						
					
					
						commit
						c13ce18837
					
				| 
						 | 
				
			
			@ -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))) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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[]
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue