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
4 changes: 4 additions & 0 deletions src/Sites/Site.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ protected function resolveAntlersValue($value)
->all();
}

if (! is_string($value) || ! Str::contains($value, ['{', '@'])) {
return $value;
}

$value = Parse::config($value);

$isEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
Expand Down
4 changes: 4 additions & 0 deletions src/Sites/Sites.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public function all()

public function authorized()
{
if (User::current()->isSuper()) {
return $this->sites;
}

return $this->sites->filter(fn ($site) => User::current()->can('view', $site));
}

Expand Down
2 changes: 1 addition & 1 deletion src/Structures/Nav.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public function sites()

public function existsIn($site)
{
return $this->trees()->has($site);
return $this->in($site) !== null;
}

public function blueprint()
Expand Down
115 changes: 115 additions & 0 deletions tests/Performance/SitesPerformanceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace Tests\Performance;

use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Role;
use Statamic\Facades\User;
use Statamic\Sites\Site;
use Statamic\Sites\Sites;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

class SitesPerformanceTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

/**
* Test that super users bypass authorization filtering for all sites.
* This ensures the performance optimization for super users is working.
*/
#[Test]
public function super_user_bypasses_site_authorization_check()
{
$sites = (new Sites)->setSites([
'en' => ['url' => 'http://test.com/'],
'fr' => ['url' => 'http://fr.test.com/'],
'de' => ['url' => 'http://test.com/de/'],
]);

// Create and act as a super user
$superUser = tap(User::make()->id(1))->save();
$superUser->makeSuper()->save();
$this->actingAs($superUser);

// Super user should get all sites without any filtering
$authorizedSites = $sites->authorized();

$this->assertEquals(3, $authorizedSites->count());
$this->assertEquals(['en', 'fr', 'de'], $authorizedSites->keys()->all());
}

/**
* Test that regular users still get filtered authorization check.
* This ensures regular users only see sites they have access to.
*/
#[Test]
public function regular_user_gets_filtered_site_authorization()
{
$sites = (new Sites)->setSites([
'en' => ['url' => 'http://test.com/'],
'fr' => ['url' => 'http://fr.test.com/'],
'de' => ['url' => 'http://test.com/de/'],
]);

Role::make('editor')
->permissions([
'access en site',
'access fr site',
])
->save();

$regularUser = tap(User::make()->assignRole('editor'))->save();
$this->actingAs($regularUser);

\Statamic\Facades\Site::shouldReceive('multiEnabled')->andReturnTrue();
\Statamic\Facades\Site::shouldReceive('all')->andReturn(collect()); // CorePermissions calls this

// Regular user should only get sites they have access to
$authorizedSites = $sites->authorized();

$this->assertEquals(2, $authorizedSites->count());
$this->assertEquals(['en', 'fr'], $authorizedSites->keys()->all());
}

/**
* Test that site configuration with plain strings doesn't trigger Antlers parsing.
* This ensures performance optimization for non-template string values.
*/
#[Test]
public function plain_string_site_config_skips_antlers_parsing()
{
// Test with a simple string URL that has no template syntax
$site = new Site('en', [
'url' => '/en/',
'locale' => 'en_US',
'name' => 'English',
]);

// These should be plain strings, not parsed by Antlers
// Note: URLs are tidied, so trailing slash is removed
$this->assertEquals('/en', $site->url());
$this->assertEquals('en_US', $site->locale());
$this->assertEquals('English', $site->name());
}

/**
* Test that site configuration with Antlers syntax still gets parsed.
* This ensures the optimization doesn't break actual template parsing.
*/
#[Test]
public function site_config_with_antlers_syntax_gets_parsed()
{
// Test with template syntax that should be parsed
$site = new Site('en', [
'url' => '/',
'locale' => '{{ config:app.locale }}',
'name' => 'Test Site',
]);

// Locale should have been parsed
$this->assertNotEmpty($site->locale());
// It should not contain the template syntax anymore
$this->assertStringNotContainsString('{{', $site->locale());
}
}
73 changes: 73 additions & 0 deletions tests/Structures/NavPerformanceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Tests\Structures;

use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Nav;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;
use Tests\UnlinksPaths;

class NavPerformanceTest extends TestCase
{
use PreventSavingStacheItemsToDisk;
use UnlinksPaths;

private $directory;

public function setUp(): void
{
parent::setUp();

$stache = $this->app->make('stache');
$stache->store('nav-trees')->directory($this->directory = $this->fakeStacheDirectory.$this->directory.'');

$this->setSites([
'en' => ['locale' => 'en_US', 'url' => '/'],
'fr' => ['locale' => 'fr_FR', 'url' => '/fr/'],
'de' => ['locale' => 'de_DE', 'url' => '/de/'],
]);
}

/**
* Test that existsIn() efficiently checks for nav in a specific site.
* This verifies the performance optimization that uses in() instead of trees().
*/
#[Test]
public function exists_in_checks_nav_for_specific_site()
{
$nav = tap(Nav::make('links'))->save();

// Create a tree for only the 'en' site
$nav->makeTree('en', [['title' => 'Home', 'url' => '/']])->save();

// Nav should exist in 'en' site
$this->assertTrue($nav->existsIn('en'));

// Nav should not exist in 'fr' site
$this->assertFalse($nav->existsIn('fr'));

// Nav should not exist in 'de' site
$this->assertFalse($nav->existsIn('de'));
}

/**
* Test that existsIn() works when nav exists in multiple sites.
*/
#[Test]
public function exists_in_checks_nav_across_multiple_sites()
{
$nav = tap(Nav::make('links'))->save();

// Create trees for 'en' and 'fr' sites
$nav->makeTree('en', [['title' => 'Home', 'url' => '/']])->save();
$nav->makeTree('fr', [['title' => 'Accueil', 'url' => '/']])->save();

// Nav should exist in both 'en' and 'fr' sites
$this->assertTrue($nav->existsIn('en'));
$this->assertTrue($nav->existsIn('fr'));

// Nav should not exist in 'de' site
$this->assertFalse($nav->existsIn('de'));
}
}
Loading