Added smarter page finding so changing the page name does not break old urls
Added page & book slug history to revisions so they can be looked up if a page is not found.
Showing
4 changed files
with
85 additions
and
2 deletions
| ... | @@ -11,6 +11,7 @@ use BookStack\Http\Requests; | ... | @@ -11,6 +11,7 @@ use BookStack\Http\Requests; |
| 11 | use BookStack\Repos\BookRepo; | 11 | use BookStack\Repos\BookRepo; |
| 12 | use BookStack\Repos\ChapterRepo; | 12 | use BookStack\Repos\ChapterRepo; |
| 13 | use BookStack\Repos\PageRepo; | 13 | use BookStack\Repos\PageRepo; |
| 14 | +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||
| 14 | use Views; | 15 | use Views; |
| 15 | 16 | ||
| 16 | class PageController extends Controller | 17 | class PageController extends Controller |
| ... | @@ -81,6 +82,8 @@ class PageController extends Controller | ... | @@ -81,6 +82,8 @@ class PageController extends Controller |
| 81 | 82 | ||
| 82 | /** | 83 | /** |
| 83 | * Display the specified page. | 84 | * Display the specified page. |
| 85 | + * If the page is not found via the slug the | ||
| 86 | + * revisions are searched for a match. | ||
| 84 | * | 87 | * |
| 85 | * @param $bookSlug | 88 | * @param $bookSlug |
| 86 | * @param $pageSlug | 89 | * @param $pageSlug |
| ... | @@ -89,7 +92,15 @@ class PageController extends Controller | ... | @@ -89,7 +92,15 @@ class PageController extends Controller |
| 89 | public function show($bookSlug, $pageSlug) | 92 | public function show($bookSlug, $pageSlug) |
| 90 | { | 93 | { |
| 91 | $book = $this->bookRepo->getBySlug($bookSlug); | 94 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 95 | + | ||
| 96 | + try { | ||
| 92 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 97 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 98 | + } catch (NotFoundHttpException $e) { | ||
| 99 | + $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); | ||
| 100 | + if ($page === null) abort(404); | ||
| 101 | + return redirect($page->getUrl()); | ||
| 102 | + } | ||
| 103 | + | ||
| 93 | $sidebarTree = $this->bookRepo->getChildren($book); | 104 | $sidebarTree = $this->bookRepo->getChildren($book); |
| 94 | Views::add($page); | 105 | Views::add($page); |
| 95 | $this->setPageTitle($page->getShortName()); | 106 | $this->setPageTitle($page->getShortName()); | ... | ... |
| ... | @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Log; | ... | @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Log; |
| 10 | use Illuminate\Support\Str; | 10 | use Illuminate\Support\Str; |
| 11 | use BookStack\Page; | 11 | use BookStack\Page; |
| 12 | use BookStack\PageRevision; | 12 | use BookStack\PageRevision; |
| 13 | +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||
| 13 | 14 | ||
| 14 | class PageRepo | 15 | class PageRepo |
| 15 | { | 16 | { |
| ... | @@ -65,11 +66,28 @@ class PageRepo | ... | @@ -65,11 +66,28 @@ class PageRepo |
| 65 | public function getBySlug($slug, $bookId) | 66 | public function getBySlug($slug, $bookId) |
| 66 | { | 67 | { |
| 67 | $page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); | 68 | $page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); |
| 68 | - if ($page === null) abort(404); | 69 | + if ($page === null) throw new NotFoundHttpException('Page not found'); |
| 69 | return $page; | 70 | return $page; |
| 70 | } | 71 | } |
| 71 | 72 | ||
| 72 | /** | 73 | /** |
| 74 | + * Search through page revisions and retrieve | ||
| 75 | + * the last page in the current book that | ||
| 76 | + * has a slug equal to the one given. | ||
| 77 | + * @param $pageSlug | ||
| 78 | + * @param $bookSlug | ||
| 79 | + * @return null | Page | ||
| 80 | + */ | ||
| 81 | + public function findPageUsingOldSlug($pageSlug, $bookSlug) | ||
| 82 | + { | ||
| 83 | + $revision = $this->pageRevision->where('slug', '=', $pageSlug) | ||
| 84 | + ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') | ||
| 85 | + ->with('page')->first(); | ||
| 86 | + return $revision !== null ? $revision->page : null; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + /** | ||
| 90 | + * Get a new Page instance from the given input. | ||
| 73 | * @param $input | 91 | * @param $input |
| 74 | * @return Page | 92 | * @return Page |
| 75 | */ | 93 | */ |
| ... | @@ -245,9 +263,13 @@ class PageRepo | ... | @@ -245,9 +263,13 @@ class PageRepo |
| 245 | $this->saveRevision($page); | 263 | $this->saveRevision($page); |
| 246 | } | 264 | } |
| 247 | 265 | ||
| 266 | + // Prevent slug being updated if no name change | ||
| 267 | + if ($page->name !== $input['name']) { | ||
| 268 | + $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id); | ||
| 269 | + } | ||
| 270 | + | ||
| 248 | // Update with new details | 271 | // Update with new details |
| 249 | $page->fill($input); | 272 | $page->fill($input); |
| 250 | - $page->slug = $this->findSuitableSlug($page->name, $book_id, $page->id); | ||
| 251 | $page->html = $this->formatHtml($input['html']); | 273 | $page->html = $this->formatHtml($input['html']); |
| 252 | $page->text = strip_tags($page->html); | 274 | $page->text = strip_tags($page->html); |
| 253 | $page->updated_by = auth()->user()->id; | 275 | $page->updated_by = auth()->user()->id; |
| ... | @@ -283,6 +305,8 @@ class PageRepo | ... | @@ -283,6 +305,8 @@ class PageRepo |
| 283 | { | 305 | { |
| 284 | $revision = $this->pageRevision->fill($page->toArray()); | 306 | $revision = $this->pageRevision->fill($page->toArray()); |
| 285 | $revision->page_id = $page->id; | 307 | $revision->page_id = $page->id; |
| 308 | + $revision->slug = $page->slug; | ||
| 309 | + $revision->book_slug = $page->book->slug; | ||
| 286 | $revision->created_by = auth()->user()->id; | 310 | $revision->created_by = auth()->user()->id; |
| 287 | $revision->created_at = $page->updated_at; | 311 | $revision->created_at = $page->updated_at; |
| 288 | $revision->save(); | 312 | $revision->save(); | ... | ... |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Schema\Blueprint; | ||
| 4 | +use Illuminate\Database\Migrations\Migration; | ||
| 5 | + | ||
| 6 | +class AddSlugToRevisions extends Migration | ||
| 7 | +{ | ||
| 8 | + /** | ||
| 9 | + * Run the migrations. | ||
| 10 | + * | ||
| 11 | + * @return void | ||
| 12 | + */ | ||
| 13 | + public function up() | ||
| 14 | + { | ||
| 15 | + Schema::table('page_revisions', function (Blueprint $table) { | ||
| 16 | + $table->string('slug'); | ||
| 17 | + $table->index('slug'); | ||
| 18 | + $table->string('book_slug'); | ||
| 19 | + $table->index('book_slug'); | ||
| 20 | + }); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * Reverse the migrations. | ||
| 25 | + * | ||
| 26 | + * @return void | ||
| 27 | + */ | ||
| 28 | + public function down() | ||
| 29 | + { | ||
| 30 | + Schema::table('page_revisions', function (Blueprint $table) { | ||
| 31 | + $table->dropColumn('slug'); | ||
| 32 | + $table->dropColumn('book_slug'); | ||
| 33 | + }); | ||
| 34 | + } | ||
| 35 | +} |
| ... | @@ -211,5 +211,18 @@ class EntityTest extends TestCase | ... | @@ -211,5 +211,18 @@ class EntityTest extends TestCase |
| 211 | ->seeInNthElement('.entity-list .page', 0, $content['page']->name); | 211 | ->seeInNthElement('.entity-list .page', 0, $content['page']->name); |
| 212 | } | 212 | } |
| 213 | 213 | ||
| 214 | + public function test_old_page_slugs_redirect_to_new_pages() | ||
| 215 | + { | ||
| 216 | + $page = \BookStack\Page::all()->first(); | ||
| 217 | + $pageUrl = $page->getUrl(); | ||
| 218 | + $newPageUrl = '/books/' . $page->book->slug . '/page/super-test-page'; | ||
| 219 | + $this->asAdmin()->visit($pageUrl) | ||
| 220 | + ->clickInElement('#content', 'Edit') | ||
| 221 | + ->type('super test page', '#name') | ||
| 222 | + ->press('Save Page') | ||
| 223 | + ->seePageIs($newPageUrl) | ||
| 224 | + ->visit($pageUrl) | ||
| 225 | + ->seePageIs($newPageUrl); | ||
| 226 | + } | ||
| 214 | 227 | ||
| 215 | } | 228 | } | ... | ... |
-
Please register or sign in to post a comment