Skip to content
Draft
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: 2 additions & 2 deletions src/Actions/DuplicateModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ private function duplicateModel(Model $original, Resource $resource): Model
{
$model = $original->replicate($resource->blueprint()->fields()->all()->reject->shouldBeDuplicated()->keys()->all());

if ($resource->titleField() && in_array($resource->titleField(), $resource->databaseColumns())) {
$model->setAttribute($resource->titleField(), $original->getAttribute($resource->titleField()).' (Duplicate)');
if ($resource->titleField()) {
$resource->setTitleValue($model, $resource->titleValue($original).' (Duplicate)');
}

if ($resource->hasPublishStates()) {
Expand Down
4 changes: 2 additions & 2 deletions src/Fieldtypes/BaseFieldtype.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ private function applyOrderingToIndexQuery(Builder|SearchQueryBuilder $query, Fi
if ($orderBy = OrderBy::column($request->input('sort'))) {
// The stack selector always uses `title` as the default sort column, but
// the "title field" for the model might be a different column, so we need to convert it.
$sortColumn = $orderBy === 'title' ? $this->resource()->titleField() : $orderBy;
$sortColumn = $orderBy === 'title' ? $this->resource()->titleDbColumn() : $orderBy;

$query->reorder($sortColumn, $request->input('order'));
}
Expand Down Expand Up @@ -304,7 +304,7 @@ public function preProcessIndex($data)

return [
'id' => $model->{$resource->primaryKey()},
'title' => $fieldtype->preProcessIndex($model->{$column}),
'title' => $fieldtype->preProcessIndex($resource->titleValue($model)),
'edit_url' => $model->runwayEditUrl(),
];
});
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Controllers/CP/ModelRevisionsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function show(Request $request, Resource $resource, Model $model, $revisi
[$values, $meta] = $this->extractFromFields($model, $resource, $blueprint);

return [
'title' => $model->getAttribute($resource->titleField()),
'title' => $resource->titleValue($model),
'editing' => true,
'actions' => [
'save' => $model->runwayUpdateUrl(),
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Controllers/CP/ResourceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public function edit(EditRequest $request, Resource $resource, Model $model)
[$values, $meta] = $this->extractFromFields($model, $resource, $blueprint);

$viewData = [
'title' => $model->getAttribute($resource->titleField()),
'title' => $resource->titleValue($model),
'reference' => $model->reference(),
'method' => 'patch',
'resource' => $resource->toArray(),
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Controllers/CP/Traits/ExtractsFromModelFields.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected function extractFromFields(Model $model, Resource $resource, Blueprint

$values = $fields->values()->merge([
'id' => $model->getKey(),
'title' => $model->getAttribute($resource->titleField()),
'title' => $resource->titleValue($model),
'edit_url' => $model->runwayEditUrl(),
$resource->publishedColumn() => $model->published(),
]);
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Resources/CP/ListedModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function toArray($request): array

return [
'id' => $model->getKey(),
'title' => $model->getAttribute($this->runwayResource->titleField()),
'title' => $this->runwayResource->titleValue($model),
'published' => $this->resource->published(),
'status' => $this->resource->publishedStatus(),
'edit_url' => $model->runwayEditUrl(),
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Resources/CP/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function toArray($request)
$data = [
'id' => $this->resource->getKey(),
'reference' => $this->resource->reference(),
'title' => $this->resource->{$runwayResource->titleField()},
'title' => $runwayResource->titleValue($this->resource),
'permalink' => $runwayResource->hasRouting() ? $this->resource->absoluteUrl() : null,
'status' => $this->resource->publishedStatus(),
'published' => $this->resource->published(),
Expand Down
61 changes: 61 additions & 0 deletions src/Resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,67 @@ public function titleField()
->first();
}

public function titleValue(Model $model): mixed
{
$titleField = $this->titleField();

if (! $titleField) {
return null;
}

if ($prefix = $this->nestedFieldPrefix($titleField)) {
$key = Str::after($titleField, "{$prefix}_");

return data_get($model, "{$prefix}.{$key}");
}

return $model->getAttribute($titleField);
}

public function titleDbColumn(): ?string
{
$titleField = $this->titleField();

if (! $titleField) {
return null;
}

if ($prefix = $this->nestedFieldPrefix($titleField)) {
$key = Str::after($titleField, "{$prefix}_");

return "{$prefix}->{$key}";
}

return $titleField;
}

public function setTitleValue(Model $model, mixed $value): void
{
$titleField = $this->titleField();

if (! $titleField) {
return;
}

if ($prefix = $this->nestedFieldPrefix($titleField)) {
$key = Str::after($titleField, "{$prefix}_");
$current = $model->getAttribute($prefix) ?? [];

if (is_object($current)) {
$current = (array) $current;
}

data_set($current, $key, $value);
$model->setAttribute($prefix, $current);

return;
}

if (in_array($titleField, $this->databaseColumns())) {
$model->setAttribute($titleField, $value);
}
}

public function icon(): string
{
$navItem = Nav::build()->pluck('items')->flatten()
Expand Down
2 changes: 1 addition & 1 deletion src/Search/Searchable.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public function toSearchResult(): Result

public function getCpSearchResultTitle(): string
{
return $this->model->{$this->resource->titleField()};
return $this->resource->titleValue($this->model);
}

public function getCpSearchResultUrl(): string
Expand Down
20 changes: 20 additions & 0 deletions tests/Actions/DuplicateModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,26 @@ public function only_duplicates_fields_where_duplication_is_enabled()
$this->assertFalse($duplicate->published());
}

#[Test]
public function it_duplicates_models_with_nested_title_field()
{
Config::set('runway.resources.StatamicRadPack\Runway\Tests\Fixtures\Models\Post.title_field', 'values_alt_title');

Runway::discoverResources();

$post = Post::factory()->create([
'values' => ['alt_title' => 'Nested Hello', 'alt_body' => 'Some body'],
]);

(new DuplicateModel)->run(collect([$post]), []);

$duplicate = Post::query()->whereNot('id', $post->id)->first();

$this->assertEquals('Nested Hello (Duplicate)', data_get($duplicate, 'values.alt_title'));
$this->assertEquals('Some body', data_get($duplicate, 'values.alt_body'));
$this->assertFalse($duplicate->published());
}

#[Test]
public function it_does_not_duplicate_the_title_field_when_it_is_computed()
{
Expand Down
30 changes: 30 additions & 0 deletions tests/Http/Controllers/CP/ResourceListingControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,36 @@ public function can_sort_listing_rows()
]);
}

#[Test]
public function can_sort_listing_rows_with_nested_title_field()
{
Config::set('runway.resources.StatamicRadPack\Runway\Tests\Fixtures\Models\Post.title_field', 'values_alt_title');

Runway::discoverResources();

$user = User::make()->makeSuper()->save();

$postA = Post::factory()->create(['values' => ['alt_title' => 'Nested Alpha']]);
$postB = Post::factory()->create(['values' => ['alt_title' => 'Nested Beta']]);

$this
->actingAs($user)
->get(cp_route('runway.listing-api', ['resource' => 'post']))
->assertOk()
->assertJson([
'data' => [
[
'title' => 'Nested Alpha',
'id' => $postA->id,
],
[
'title' => 'Nested Beta',
'id' => $postB->id,
],
],
]);
}

#[Test]
public function listing_rows_are_ordered_as_per_config()
{
Expand Down
97 changes: 97 additions & 0 deletions tests/ResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Statamic\Facades\Blueprint;
use Statamic\Facades\Fieldset;
use StatamicRadPack\Runway\Runway;
use StatamicRadPack\Runway\Tests\Fixtures\Models\Post;

class ResourceTest extends TestCase
{
Expand Down Expand Up @@ -274,6 +275,102 @@ public function can_get_title_field()
$this->assertEquals('values->listable_hidden_field', $resource->titleField());
}

#[Test]
public function can_get_title_value()
{
Runway::discoverResources();

$resource = Runway::findResource('post');

$post = Post::factory()->create(['title' => 'Hello World']);

$this->assertEquals('Hello World', $resource->titleValue($post));
}

#[Test]
public function can_get_title_value_for_nested_field()
{
Config::set('runway.resources.StatamicRadPack\Runway\Tests\Fixtures\Models\Post.title_field', 'values_alt_title');

Runway::discoverResources();

$resource = Runway::findResource('post');

$post = Post::factory()->create(['values' => ['alt_title' => 'Nested Title']]);

$this->assertEquals('Nested Title', $resource->titleValue($post));
}

#[Test]
public function can_get_title_db_column()
{
Runway::discoverResources();

$resource = Runway::findResource('post');

$this->assertEquals('title', $resource->titleDbColumn());
}

#[Test]
public function can_get_title_db_column_for_nested_field()
{
Config::set('runway.resources.StatamicRadPack\Runway\Tests\Fixtures\Models\Post.title_field', 'values_alt_title');

Runway::discoverResources();

$resource = Runway::findResource('post');

$this->assertEquals('values->alt_title', $resource->titleDbColumn());
}

#[Test]
public function can_set_title_value()
{
Runway::discoverResources();

$resource = Runway::findResource('post');

$post = Post::factory()->create(['title' => 'Original']);

$resource->setTitleValue($post, 'Updated');

$this->assertEquals('Updated', $post->getAttribute('title'));
}

#[Test]
public function can_set_title_value_for_nested_field()
{
Config::set('runway.resources.StatamicRadPack\Runway\Tests\Fixtures\Models\Post.title_field', 'values_alt_title');

Runway::discoverResources();

$resource = Runway::findResource('post');

$post = Post::factory()->create(['values' => ['alt_title' => 'Original', 'alt_body' => 'Keep me']]);

$resource->setTitleValue($post, 'Updated');

$this->assertEquals('Updated', data_get($post, 'values.alt_title'));
$this->assertEquals('Keep me', data_get($post, 'values.alt_body'));
}

#[Test]
public function set_title_value_does_not_set_computed_fields()
{
Config::set('runway.resources.StatamicRadPack\Runway\Tests\Fixtures\Models\Post.title_field', 'appended_value');

Runway::discoverResources();

$resource = Runway::findResource('post');

$post = Post::factory()->create();
$original = $post->getAttribute('appended_value');

$resource->setTitleValue($post, 'Should not be set');

$this->assertEquals($original, $post->getAttribute('appended_value'));
}

#[Test]
public function revisions_can_be_enabled()
{
Expand Down
18 changes: 18 additions & 0 deletions tests/__fixtures__/revisions/resources/post/1/1776426484.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
action: unpublish
date: 1776426484
user: 15e7f3fa-4581-449c-b95c-1bbda174fad2
message: 'Live live live!'
attributes:
id: 1
published: false
data:
title: 'pariatur eum quae animi vero aliquam'
slug: pariatur-eum-quae-animi-vero-aliquam
body: 'Quae ut libero sed veritatis unde. Cumque velit sunt quia modi possimus ullam aut. Ipsa deleniti laudantium ratione deleniti dolorum debitis eos. Eum iure officiis aspernatur qui qui aperiam enim. Dolor et necessitatibus qui quis et iusto vel. Autem est aperiam eum et rem libero aliquid. Omnis consequatur dolor facilis voluptas velit. Voluptatum sit ratione suscipit aut. Voluptatem consequatur laudantium voluptatem qui dolore impedit consequatur explicabo. Provident at omnis doloremque repellat expedita possimus consequuntur. Enim perferendis corporis vel non blanditiis. Cumque voluptatum veritatis molestiae rerum sunt. Similique ut consequatur recusandae repudiandae dolorem laboriosam est veniam. Quidem eum hic expedita nesciunt illum incidunt ad alias. Quia ad a sed maiores. Vitae aliquid inventore saepe sint voluptatem. Cum qui corporis vero rerum provident est perspiciatis. Aut minus iusto id ducimus eos pariatur. Rerum rem distinctio quo pariatur vel perspiciatis. Soluta voluptas consequatur vero velit reiciendis sed fuga. Error voluptatem omnis quia. Aspernatur temporibus iste molestias reiciendis aut rem exercitationem. Neque quisquam vero corporis voluptatem vitae. Distinctio est aspernatur sed voluptas eaque. Vel tenetur et possimus impedit. Voluptates aspernatur officia ex enim. Perferendis facilis in et voluptatem nisi perspiciatis consequatur. Unde id praesentium distinctio amet illo deleniti. Totam voluptate impedit quia natus est. Et officia nam placeat illum. Amet ea voluptates ipsam eos ducimus.'
excerpt: 'This is an excerpt.'
author_id: 1
start_date: '2026-04-17 11:48:04'
end_date: null
membership_status: null
values: null
external_links: null
Loading