namespace BookStack\Entities\Models;
+use BookStack\References\ReferenceUpdater;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
*/
public function changeBook(int $newBookId): Entity
{
+ $oldUrl = $this->getUrl();
$this->book_id = $newBookId;
$this->refreshSlug();
$this->save();
+
+ if ($oldUrl !== $this->getUrl()) {
+ app()->make(ReferenceUpdater::class)->updateEntityPageReferences($this, $oldUrl);
+ }
+
$this->refresh();
// Update all child pages if a chapter
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Exceptions\ImageUploadException;
+use BookStack\References\ReferenceUpdater;
use BookStack\Uploads\ImageRepo;
use Illuminate\Http\UploadedFile;
{
protected TagRepo $tagRepo;
protected ImageRepo $imageRepo;
+ protected ReferenceUpdater $referenceUpdater;
- public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
+ public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo, ReferenceUpdater $referenceUpdater)
{
$this->tagRepo = $tagRepo;
$this->imageRepo = $imageRepo;
+ $this->referenceUpdater = $referenceUpdater;
}
/**
*/
public function update(Entity $entity, array $input)
{
+ $oldUrl = $entity->getUrl();
+
$entity->fill($input);
$entity->updated_by = user()->id;
$entity->rebuildPermissions();
$entity->indexForSearch();
+
+ if ($oldUrl !== $entity->getUrl()) {
+ $this->referenceUpdater->updateEntityPageReferences($entity, $oldUrl);
+ }
}
/**
use BookStack\Exceptions\PermissionsException;
use BookStack\Facades\Activity;
use BookStack\References\ReferenceStore;
+use BookStack\References\ReferenceUpdater;
use Exception;
use Illuminate\Pagination\LengthAwarePaginator;
{
protected BaseRepo $baseRepo;
protected RevisionRepo $revisionRepo;
- protected ReferenceStore $references;
+ protected ReferenceStore $referenceStore;
+ protected ReferenceUpdater $referenceUpdater;
/**
* PageRepo constructor.
*/
- public function __construct(BaseRepo $baseRepo, RevisionRepo $revisionRepo, ReferenceStore $references)
+ public function __construct(
+ BaseRepo $baseRepo,
+ RevisionRepo $revisionRepo,
+ ReferenceStore $referenceStore,
+ ReferenceUpdater $referenceUpdater
+ )
{
$this->baseRepo = $baseRepo;
$this->revisionRepo = $revisionRepo;
- $this->references = $references;
+ $this->referenceStore = $referenceStore;
+ $this->referenceUpdater = $referenceUpdater;
}
/**
public function getNewDraftPage(Entity $parent)
{
$page = (new Page())->forceFill([
- 'name' => trans('entities.pages_initial_name'),
+ 'name' => trans('entities.pages_initial_name'),
'created_by' => user()->id,
- 'owned_by' => user()->id,
+ 'owned_by' => user()->id,
'updated_by' => user()->id,
- 'draft' => true,
+ 'draft' => true,
]);
if ($parent instanceof Chapter) {
$draft->draft = false;
$draft->revision_count = 1;
$draft->priority = $this->getNewPriority($draft);
- $draft->refreshSlug();
$draft->save();
$this->revisionRepo->storeNewForPage($draft, trans('entities.pages_initial_revision'));
- $draft->indexForSearch();
- $this->references->updateForPage($draft);
+ $this->referenceStore->updateForPage($draft);
$draft->refresh();
Activity::add(ActivityType::PAGE_CREATE, $draft);
$this->updateTemplateStatusAndContentFromInput($page, $input);
$this->baseRepo->update($page, $input);
- $this->references->updateForPage($page);
+ $this->referenceStore->updateForPage($page);
// Update with new details
$page->revision_count++;
*/
public function restoreRevision(Page $page, int $revisionId): Page
{
+ $oldUrl = $page->getUrl();
$page->revision_count++;
/** @var PageRevision $revision */
$page->refreshSlug();
$page->save();
$page->indexForSearch();
- $this->references->updateForPage($page);
+ $this->referenceStore->updateForPage($page);
$summary = trans('entities.pages_revision_restored_from', ['id' => strval($revisionId), 'summary' => $revision->summary]);
$this->revisionRepo->storeNewForPage($page, $summary);
+ if ($oldUrl !== $page->getUrl()) {
+ $this->referenceUpdater->updateEntityPageReferences($page, $oldUrl);
+ }
+
Activity::add(ActivityType::PAGE_RESTORE, $page);
Activity::add(ActivityType::REVISION_RESTORE, $revision);
use DOMDocument;
use DOMXPath;
-class CrossLinkReplacer
+class ReferenceUpdater
{
protected ReferenceFetcher $referenceFetcher;
protected RevisionRepo $revisionRepo;
return $markdown;
}
- $commonLinkRegex = '/(\[.*?\]\()' . preg_quote($oldLink) . '(.*?\))/i';
+ $commonLinkRegex = '/(\[.*?\]\()' . preg_quote($oldLink, '/') . '(.*?\))/i';
$markdown = preg_replace($commonLinkRegex, '$1' . $newLink . '$2', $markdown);
- $referenceLinkRegex = '/(\[.*?\]:\s?)' . preg_quote($oldLink) . '(.*?)($|\s)/i';
+ $referenceLinkRegex = '/(\[.*?\]:\s?)' . preg_quote($oldLink, '/') . '(.*?)($|\s)/i';
$markdown = preg_replace($referenceLinkRegex, '$1' . $newLink . '$2$3', $markdown);
return $markdown;
namespace Tests\References;
+use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Repos\PageRepo;
use BookStack\Entities\Tools\TrashCan;
->assertSee('There are no tracked references');
}
+ public function test_pages_leading_to_entity_updated_on_url_change()
+ {
+ /** @var Page $pageA */
+ /** @var Page $pageB */
+ /** @var Book $book */
+ $pageA = Page::query()->first();
+ $pageB = Page::query()->where('id', '!=', $pageA->id)->first();
+ $book = Book::query()->first();
+
+ foreach ([$pageA, $pageB] as $page) {
+ $page->html = '<a href="' . $book->getUrl() . '">Link</a>';
+ $page->save();
+ $this->createReference($page, $book);
+ }
+
+ $this->asEditor()->put($book->getUrl(), [
+ 'name' => 'my updated book slugaroo',
+ ]);
+
+ foreach ([$pageA, $pageB] as $page) {
+ $page->refresh();
+ $this->assertStringContainsString('href="http://localhost/books/my-updated-book-slugaroo"', $page->html);
+ $this->assertDatabaseHas('page_revisions', [
+ 'page_id' => $page->id,
+ 'summary' => 'System auto-update of internal links'
+ ]);
+ }
+ }
+
+ public function test_markdown_links_leading_to_entity_updated_on_url_change()
+ {
+ /** @var Page $page */
+ /** @var Book $book */
+ $page = Page::query()->first();
+ $book = Book::query()->first();
+
+ $bookUrl = $book->getUrl();
+ $markdown = '
+ [An awesome link](' . $bookUrl . ')
+ [An awesome link with query & hash](' . $bookUrl . '?test=yes#cats)
+ [An awesome link with path](' . $bookUrl . '/an/extra/trail)
+ [An awesome link with title](' . $bookUrl . ' "title")
+ [ref]: ' . $bookUrl . '?test=yes#dogs
+ [ref_without_space]:' . $bookUrl . '
+ [ref_with_title]: ' . $bookUrl . ' "title"';
+ $page->markdown = $markdown;
+ $page->save();
+ $this->createReference($page, $book);
+
+ $this->asEditor()->put($book->getUrl(), [
+ 'name' => 'my updated book slugadoo',
+ ]);
+
+ $page->refresh();
+ $expected = str_replace($bookUrl, 'http://localhost/books/my-updated-book-slugadoo', $markdown);
+ $this->assertEquals($expected, $page->markdown);
+ }
+
protected function createReference(Model $from, Model $to)
{
(new Reference())->forceFill([