Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private function getPageValues($page)
->merge($page->pageData())
->merge([
'title' => $page->title(),
'url' => $page->reference() ? null : $page->url(),
'url' => $page->reference() ? null : $page->customUrl(),
])->all();
}

Expand Down
39 changes: 36 additions & 3 deletions src/Structures/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Statamic\Structures;

use ArrayAccess;
use Facades\Statamic\Routing\ResolveRedirect;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Support\Traits\ForwardsCalls;
Expand Down Expand Up @@ -80,7 +81,7 @@ public function setUrl($url)
public function url()
{
if ($this->url) {
return $this->url;
return $this->resolveCustomUrl();
}

if (! $entry = $this->entry()) {
Expand All @@ -93,7 +94,7 @@ public function url()
public function urlWithoutRedirect()
{
if ($this->url) {
return $this->url;
return $this->resolveCustomUrl();
}

if (! $entry = $this->entry()) {
Expand All @@ -103,6 +104,36 @@ public function urlWithoutRedirect()
return $this->linksToAnotherSite($entry) ? $entry->absoluteUrlWithoutRedirect() : $entry->urlWithoutRedirect();
}

public function customUrl()
{
return $this->url;
}

/**
* When the navigation blueprint uses a link field for the url, the custom url
* can be a reference like "entry::id" or "asset::container::path".
*/
private function resolveCustomUrl()
{
if (! Str::contains($this->url, '::')) {
return $this->url;
}

$canSelectAcrossSites = $this->structure() instanceof Nav && $this->structure()->canSelectAcrossSites();

$item = ResolveRedirect::item($this->url, $this, ! $canSelectAcrossSites);

if (! is_object($item)) {
return $item;
}

if ($item instanceof Entry && $this->linksToAnotherSite($item)) {
return $item->absoluteUrl();
}

return $item->url();
}

private function linksToAnotherSite(Entry $entry)
{
if (! $this->structure() instanceof Nav || ! $this->structure()->canSelectAcrossSites()) {
Expand Down Expand Up @@ -272,7 +303,9 @@ public function absoluteUrl()
}

if ($this->url) {
return $this->absoluteUrl = URL::makeAbsolute($this->url);
$url = $this->resolveCustomUrl();

return $this->absoluteUrl = $url ? URL::makeAbsolute($url) : null;
}

return $this->absoluteUrl = optional($this->entry())->absoluteUrl();
Expand Down
44 changes: 44 additions & 0 deletions tests/Data/Structures/PageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,50 @@ public function it_gets_the_uri_of_a_hardcoded_absolute_link()
$this->assertTrue($page->hasCustomUrl());
}

#[Test]
public function it_resolves_a_hardcoded_link_to_an_entry()
{
// The navigation blueprint can use a link field for the url, which stores entry references.
Collections::make('pages')->routes('{slug}')->save();
EntryFactory::collection('pages')->id('123')->slug('contact')->create();

$nav = $this->mock(Nav::class);
$nav->shouldReceive('canSelectAcrossSites')->andReturnFalse();

$tree = $this->newTree()->setStructure($nav);

$page = (new Page)
->setTree($tree)
->setUrl('entry::123');

$this->assertEquals('/contact', $page->uri());
$this->assertEquals('/contact', $page->url());
$this->assertEquals('/contact', $page->urlWithoutRedirect());
$this->assertEquals('http://localhost/contact', $page->absoluteUrl());
$this->assertEquals('http://localhost/contact', $page->absoluteUrlWithoutRedirect());
$this->assertEquals('entry::123', $page->customUrl());
$this->assertTrue($page->hasCustomUrl());
}

#[Test]
public function a_hardcoded_link_to_a_missing_entry_has_no_url()
{
$nav = $this->mock(Nav::class);
$nav->shouldReceive('canSelectAcrossSites')->andReturnFalse();

$tree = $this->newTree()->setStructure($nav);

$page = (new Page)
->setTree($tree)
->setUrl('entry::unknown');

$this->assertNull($page->uri());
$this->assertNull($page->url());
$this->assertNull($page->urlWithoutRedirect());
$this->assertNull($page->absoluteUrl());
$this->assertNull($page->absoluteUrlWithoutRedirect());
}

#[Test]
public function it_gets_the_uri_of_a_hardcoded_text_only_page()
{
Expand Down
28 changes: 28 additions & 0 deletions tests/Feature/Navigation/EditNavigationPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,34 @@ public function it_gets_the_values_for_a_regular_nav_item()
]);
}

#[Test]
public function it_gets_the_stored_value_of_a_link_to_an_entry()
{
$this->withoutExceptionHandling();
$this->mockTextFieldtype();
$user = tap(User::make()->makeSuper())->save();
$nav = tap(Nav::make('test'))->save();
$nav->makeTree('en', [
[
'id' => 'id7',
'title' => 'The title',
'url' => 'entry::123',
],
])->save();
BlueprintRepository::partialMock();
BlueprintRepository::shouldReceive('find')->with('navigation.test')->andReturn(Blueprint::makeFromFields([]));

// The form gets the reference, not the url it resolves to, so that saving keeps the link.
$this
->actingAs($user)
->request($nav, 'id7')
->assertJson([
'values' => [
'url' => 'entry::123 (preprocessed)',
],
]);
}

#[Test]
public function it_gets_the_values_for_a_text_only_nav_item()
{
Expand Down
Loading