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 @@ -14,7 +14,7 @@ function mountFieldtype({ value, maxItems, selectedOptions, fetchedOptions, shal
handle: 'country',
value,
config: { max_items: maxItems },
meta: { url: '/cp/fieldtypes/dictionaries/partial_countries', selectedOptions },
meta: { url: '/!/fieldtypes/dictionaries/partial_countries', selectedOptions },
},
global: {
mocks: {
Expand Down
2 changes: 0 additions & 2 deletions routes/cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
use Statamic\Http\Controllers\CP\Fields\FieldsetController;
use Statamic\Http\Controllers\CP\Fields\FieldtypesController;
use Statamic\Http\Controllers\CP\Fields\MetaController;
use Statamic\Http\Controllers\CP\Fieldtypes\DictionaryFieldtypeController;
use Statamic\Http\Controllers\CP\Fieldtypes\FilesFieldtypeController;
use Statamic\Http\Controllers\CP\Fieldtypes\IconFieldtypeController;
use Statamic\Http\Controllers\CP\Fieldtypes\MarkdownFieldtypeController;
Expand Down Expand Up @@ -385,7 +384,6 @@
Route::get('relationship/filters', [RelationshipFieldtypeController::class, 'filters'])->name('relationship.filters');
Route::post('markdown', [MarkdownFieldtypeController::class, 'preview'])->name('markdown.preview');
Route::post('files/upload', [FilesFieldtypeController::class, 'upload'])->name('files.upload');
Route::get('dictionaries/{dictionary}', DictionaryFieldtypeController::class)->name('dictionary-fieldtype');
Route::post('icons', IconFieldtypeController::class)->name('icon-fieldtype');
Route::post('replicator/set', ReplicatorSetController::class)->name('replicator-fieldtype.set');
});
Expand Down
3 changes: 3 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Statamic\Facades\TwoFactor;
use Statamic\Http\Controllers\ActivateAccountController;
use Statamic\Http\Controllers\Auth\ElevatedSessionController;
use Statamic\Http\Controllers\DictionaryFieldtypeController;
use Statamic\Http\Controllers\ForgotPasswordController;
use Statamic\Http\Controllers\FormController;
use Statamic\Http\Controllers\FrontendController;
Expand Down Expand Up @@ -41,6 +42,8 @@
Route::get('protect/password', [PasswordProtectController::class, 'show'])->name('protect.password.show')->middleware([HandleInertiaRequests::class]);
Route::post('protect/password', [PasswordProtectController::class, 'store'])->name('protect.password.store');

Route::get('fieldtypes/dictionaries/{dictionary}', DictionaryFieldtypeController::class)->name('dictionary-fieldtype');

Route::group(['prefix' => 'auth', 'middleware' => [AuthGuard::class]], function () {
Route::get('logout', [LoginController::class, 'logout'])->name('logout');

Expand Down
5 changes: 5 additions & 0 deletions src/Dictionaries/Countries.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,9 @@ protected function getItems(): array
['name' => __('statamic::dictionary-countries.names.ZWE'), 'iso3' => 'ZWE', 'iso2' => 'ZW', 'region' => $this->regions['africa'], 'subregion' => $this->subregions['eastern_africa'], 'emoji' => '🇿🇼'],
];
}

public function allowsPublicAccess(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Dictionaries/Currencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,9 @@ protected function getItems(): array
['code' => 'ZWG', 'name' => __('statamic::dictionary-currencies.ZWG'), 'symbol' => '$', 'decimals' => 2],
];
}

public function allowsPublicAccess(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Dictionaries/Dictionary.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,9 @@ public function keywords(): array
{
return $this->keywords;
}

public function allowsPublicAccess(): bool
{
return false;
}
}
5 changes: 5 additions & 0 deletions src/Dictionaries/Languages.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,9 @@ protected function getItems(): array
['code' => 'gl', 'name' => __('statamic::dictionary-languages.gl')],
];
}

public function allowsPublicAccess(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Dictionaries/Locales.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ private function buildLocalesCommand(): array

return ['locale', '-a'];
}

public function allowsPublicAccess(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Dictionaries/Timezones.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ private function getOffset(string $tz): string

return stripos($offsetInSecs, '-') === false ? "+{$hoursAndSec}" : "-{$hoursAndSec}";
}

public function allowsPublicAccess(): bool
{
return true;
}
}
2 changes: 1 addition & 1 deletion src/Fieldtypes/Dictionary.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected function configFieldItems(): array
public function preload(): array
{
return [
'url' => cp_route('dictionary-fieldtype', $this->dictionary()->handle()),
'url' => route('statamic.dictionary-fieldtype', $this->dictionary()->handle()),
'selectedOptions' => $this->getItemData($this->field->value()),
];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
<?php

namespace Statamic\Http\Controllers\CP\Fieldtypes;
namespace Statamic\Http\Controllers;

use Facades\Statamic\Fields\FieldtypeRepository as Fieldtype;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Statamic\Exceptions\ForbiddenHttpException;
use Statamic\Fields\Field;
use Statamic\Http\Controllers\CP\CpController;

class DictionaryFieldtypeController extends CpController
class DictionaryFieldtypeController extends Controller
{
public function __invoke(Request $request, string $dictionary)
{
$options = $this->fieldtype($request)->dictionary()->options($request->search);
$dictionary = $this->fieldtype($request)->dictionary();

if (Gate::denies('access cp') && ! $dictionary->allowsPublicAccess()) {
throw new ForbiddenHttpException;
}

$options = $dictionary->options($request->search);

// Return an ordered list of key/value pairs rather than a value-keyed object.
// When the values are integers, the browser would re-sort the object's keys ascending,
Expand Down
60 changes: 59 additions & 1 deletion tests/Fieldtypes/DictionaryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Dictionaries\Countries;
use Statamic\Dictionaries\Dictionary;
use Statamic\Dictionaries\Item;
use Statamic\Exceptions\DictionaryNotFoundException;
use Statamic\Exceptions\UndefinedDictionaryException;
use Statamic\Facades\User;
use Statamic\Fields\Field;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

class DictionaryTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

#[Test]
#[DataProvider('dictionaryConfigProvider')]
public function it_gets_the_dictionary($dictionaryConfig, $expectedConfig)
Expand Down Expand Up @@ -91,7 +96,7 @@ public function it_returns_preload_data()
$preload = $fieldtype->preload();

$this->assertArraySubset([
'url' => 'http://localhost/cp/fieldtypes/dictionaries/countries',
'url' => 'http://localhost/!/fieldtypes/dictionaries/countries',
'selectedOptions' => [
['value' => 'USA', 'label' => '🇺🇸 United States', 'invalid' => false],
['value' => 'AUS', 'label' => '🇦🇺 Australia', 'invalid' => false],
Expand All @@ -103,6 +108,44 @@ public function it_returns_preload_data()
], $preload);
}

#[Test]
public function a_guest_can_request_options_from_a_dictionary_that_allows_public_access()
{
$this
->getJson($this->optionsUrl('countries'))
->assertOk()
->assertJsonStructure(['data' => [['key', 'value']]]);
}

#[Test]
public function a_guest_cannot_request_options_from_a_dictionary_that_does_not_allow_public_access()
{
RestrictedDictionary::register();

$this
->getJson($this->optionsUrl('restricted'))
->assertForbidden();
}

#[Test]
public function a_cp_user_can_request_options_from_a_dictionary_that_does_not_allow_public_access()
{
RestrictedDictionary::register();

$this
->actingAs(User::make()->makeSuper())
->getJson($this->optionsUrl('restricted'))
->assertOk()
->assertJsonStructure(['data' => [['key', 'value']]]);
}

private function optionsUrl(string $dictionary): string
{
$config = base64_encode(json_encode(['type' => 'dictionary', 'dictionary' => $dictionary]));

return route('statamic.dictionary-fieldtype', $dictionary).'?config='.$config;
}

#[Test]
public function it_augments_a_single_option()
{
Expand Down Expand Up @@ -280,3 +323,18 @@ public function it_returns_extra_renderable_field_data()
], $extraRenderableFieldData);
}
}

class RestrictedDictionary extends Dictionary
{
protected static $handle = 'restricted';

public function options(?string $search = null): array
{
return ['foo' => 'Foo', 'bar' => 'Bar'];
}

public function get(string $key): ?Item
{
return new Item($key, $this->options()[$key], []);
}
}
Loading