Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e717416
Add field references + deletion blockers
riasvdv Jun 1, 2026
3f21ac5
Add support for slug/ref element refs, not just numeric ids
riasvdv Jun 1, 2026
58adf2d
Merge branch 'feature/markdown-field' into feature/field-references
riasvdv Jun 2, 2026
0aebbdc
Merge branch '6.x' into feature/field-references
brandonkelly Jun 2, 2026
7c722e0
Merge branch '6.x' into feature/field-references
brandonkelly Jun 3, 2026
ce9ef7f
TracksReferences trait
brandonkelly Jun 3, 2026
3ccac9f
Update Link to store references instead of relations
brandonkelly Jun 3, 2026
66fa13c
Merge branch 'feature/markdown-field' into feature/field-references
brandonkelly Jun 5, 2026
0c2a5a7
Merge branch 'feature/markdown-field' into feature/field-references
brandonkelly Jun 5, 2026
889a79f
Merge branch 'feature/markdown-field' into feature/field-references
brandonkelly Jun 5, 2026
6aac9a6
Merge branch 'feature/markdown-field' into feature/field-references
brandonkelly Jun 5, 2026
841b708
Drop Link field support from ReplaceRelations job
brandonkelly Jun 5, 2026
bfb02b9
Fixed a couple bugs
brandonkelly Jun 5, 2026
e1ccf41
Drop RelationalField trait (no more usages)
brandonkelly Jun 5, 2026
7129eb9
Keep it around in yii2-adapter
brandonkelly Jun 5, 2026
11fae68
Scan yii2-adapter/legacy/base
brandonkelly Jun 5, 2026
b9bbb93
Fixed test
brandonkelly Jun 5, 2026
6f61173
Ignore RelationalFieldTrait
brandonkelly Jun 5, 2026
8b20fc0
Merge branch 'feature/markdown-field' into feature/field-references
brandonkelly Jun 5, 2026
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
2 changes: 1 addition & 1 deletion packages/craftcms-cp/src/styles/shared/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
--c-status-disabled-text: var(--color-slate-600);
--c-status-disabled-border: var(--color-slate-600);

/**
/**
Static colors
*/
--c-color-static-success-fill: var(--color-static-emerald-200);
Expand Down
1 change: 1 addition & 0 deletions resources/build/assets/AppLayout-CK__PXlQ.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions resources/build/assets/CpQueueIndicator-DHP-2TyH.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resources/build/assets/DynamicHtmlRenderer-wtRRsM3x.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resources/build/assets/ImageTransforms-BDfCP5AW.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resources/build/assets/Login-CaHFcZdA.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions resources/build/assets/SectionsEdit-g2s0kyQu.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions resources/build/assets/_plugin-vue_export-helper-Cuhevpfm.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions resources/build/assets/cp-BFrrhCw6.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resources/build/assets/cp-BeR5H0b6.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions resources/build/assets/cp-CWmEw_Vi.css

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions resources/build/assets/legacy-FAo_c5Qa.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resources/build/assets/login-form-CRpV1xXl.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

256 changes: 256 additions & 0 deletions resources/build/assets/nav-item-ixoxjtrg-DTBVd69z.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions resources/build/assets/nav-item.ts-DorpDR5s.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resources/build/assets/recovery-code-form--0Xz_YCy.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resources/build/assets/totp-form-D_DK2DKZ.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions routes/actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@
Route::post('delete-elements/delete', [DeleteElementsController::class, 'destroy']);
Route::any('delete-elements/replace-relations-modal', [DeleteElementsController::class, 'replaceRelationsModal']);
Route::post('delete-elements/replace-relations', [DeleteElementsController::class, 'replaceRelations']);
Route::any('delete-elements/replace-references-modal', [DeleteElementsController::class, 'replaceReferencesModal']);
Route::post('delete-elements/replace-references', [DeleteElementsController::class, 'replaceReferences']);

Route::post('elements/create', CreateElementController::class);
Route::any('elements/edit', EditElementController::class);
Expand Down
2 changes: 1 addition & 1 deletion src/Cms.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

public const string VERSION = '6.0.0-alpha.6';

public const string SCHEMA_VERSION = '6.0.0.2';
public const string SCHEMA_VERSION = '6.0.0.3';

public const string MIN_VERSION_REQUIRED = '5.9.0';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

use CraftCms\Cms\Database\Migration;
use CraftCms\Cms\Database\Table;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
if (Schema::hasTable(Table::FIELDREFERENCES)) {
return;
}

Schema::create(Table::FIELDREFERENCES, function (Blueprint $table) {
$table->integer('id', true);
$table->integer('fieldId');
$table->uuid('fieldInstanceUid');
$table->integer('sourceId');
$table->integer('sourceSiteId')->nullable();
$table->integer('targetId');
});

Schema::createIndex(Table::FIELDREFERENCES, ['fieldId', 'fieldInstanceUid', 'sourceId', 'sourceSiteId', 'targetId'], unique: true);
Schema::createIndex(Table::FIELDREFERENCES, ['targetId']);

Schema::table(Table::FIELDREFERENCES, function (Blueprint $table) {
$table->foreign('fieldId')->references('id')->on(Table::FIELDS)->cascadeOnDelete();
$table->foreign('sourceId')->references('id')->on(Table::ELEMENTS)->cascadeOnDelete();
$table->foreign('sourceSiteId')->references('id')->on(Table::SITES)->cascadeOnDelete()->cascadeOnUpdate();
});
}

public function down(): void
{
Schema::dropIfExists(Table::FIELDREFERENCES);
}
};
23 changes: 23 additions & 0 deletions src/Database/Migrations/2026_06_02_230524_link_references.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

use CraftCms\Cms\Database\Table;
use CraftCms\Cms\Field\Fields;
use CraftCms\Cms\Field\Link;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
public function up(): void
{
$linkFieldIds = app(Fields::class)->getAllFields()
->filter(fn ($f) => $f instanceof Link)
->map(fn (Link $f) => $f->id);

DB::table(Table::RELATIONS)
->whereIn('fieldId', $linkFieldIds)
->delete();
}

public function down(): void {}
};
15 changes: 15 additions & 0 deletions src/Database/Migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,16 @@ public function createTables(?Logger $logger = null): void
$table->char('uid', 36)->default('0');
});

$logger?->subLabel('fieldreferences');
Schema::create(Table::FIELDREFERENCES, function (Blueprint $table) {
$table->integer('id', true);
$table->integer('fieldId');
$table->uuid('fieldInstanceUid');
$table->integer('sourceId');
$table->integer('sourceSiteId')->nullable();
$table->integer('targetId');
});

$logger?->subLabel('gqltokens');
Schema::create('gqltokens', function (Blueprint $table) {
$table->integer('id', true);
Expand Down Expand Up @@ -1069,6 +1079,8 @@ public function createIndexes(): void
Schema::createIndex(Table::ENTRYTYPES, ['dateDeleted']);
Schema::createIndex(Table::FIELDLAYOUTS, ['dateDeleted']);
Schema::createIndex(Table::FIELDLAYOUTS, ['type']);
Schema::createIndex(Table::FIELDREFERENCES, ['fieldId', 'fieldInstanceUid', 'sourceId', 'sourceSiteId', 'targetId'], unique: true);
Schema::createIndex(Table::FIELDREFERENCES, ['targetId']);
Schema::createIndex(Table::FIELDS, ['handle', 'context']);
Schema::createIndex(Table::FIELDS, ['context']);
Schema::createIndex(Table::FIELDS, ['dateDeleted']);
Expand Down Expand Up @@ -1213,6 +1225,9 @@ public function addForeignKeys(): void
Schema::table(Table::ENTRIES, fn (Blueprint $table) => $table->foreign('fieldId')->references('id')->on(Table::FIELDS)->cascadeOnDelete());
Schema::table(Table::ENTRIES, fn (Blueprint $table) => $table->foreign('primaryOwnerId')->references('id')->on(Table::ELEMENTS)->cascadeOnDelete());
Schema::table(Table::ENTRYTYPES, fn (Blueprint $table) => $table->foreign('fieldLayoutId')->references('id')->on(Table::FIELDLAYOUTS)->nullOnDelete());
Schema::table(Table::FIELDREFERENCES, fn (Blueprint $table) => $table->foreign('fieldId')->references('id')->on(Table::FIELDS)->cascadeOnDelete());
Schema::table(Table::FIELDREFERENCES, fn (Blueprint $table) => $table->foreign('sourceId')->references('id')->on(Table::ELEMENTS)->cascadeOnDelete());
Schema::table(Table::FIELDREFERENCES, fn (Blueprint $table) => $table->foreign('sourceSiteId')->references('id')->on(Table::SITES)->cascadeOnDelete()->cascadeOnUpdate());
Schema::table(Table::GQLTOKENS, fn (Blueprint $table) => $table->foreign('schemaId')->references('id')->on(Table::GQLSCHEMAS)->nullOnDelete());
Schema::table(Table::RELATIONS, fn (Blueprint $table) => $table->foreign('fieldId')->references('id')->on(Table::FIELDS)->cascadeOnDelete());
Schema::table(Table::RELATIONS, fn (Blueprint $table) => $table->foreign('sourceId')->references('id')->on(Table::ELEMENTS)->cascadeOnDelete());
Expand Down
2 changes: 2 additions & 0 deletions src/Database/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@

public const string FIELDLAYOUTS = 'fieldlayouts';

public const string FIELDREFERENCES = 'fieldreferences';

public const string FIELDS = 'fields';

public const string GQLSCHEMAS = 'gqlschemas';
Expand Down
2 changes: 2 additions & 0 deletions src/Element/Concerns/HasDeletionBlockers.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace CraftCms\Cms\Element\Concerns;

use CraftCms\Cms\Element\DeletionBlockers\FieldReferencesDeletionBlocker;
use CraftCms\Cms\Element\DeletionBlockers\RelationDeletionBlocker;
use CraftCms\Cms\Element\ElementCollection;
use CraftCms\Cms\Element\Events\DefineDeletionBlockers;
Expand All @@ -22,6 +23,7 @@ public static function deletionBlockers(ElementCollection $elements, bool $hardD
'defaultSort' => ['section', 'asc'],
],
]),
new FieldReferencesDeletionBlocker($elements, $hardDelete),
];

event($event = new DefineDeletionBlockers(
Expand Down
131 changes: 131 additions & 0 deletions src/Element/DeletionBlockers/FieldReferencesDeletionBlocker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Element\DeletionBlockers;

use CraftCms\Cms\Cp\Html\ElementIndexHtml;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\ElementCollection;
use CraftCms\Cms\Field\FieldReferences;
use CraftCms\Cms\Support\Html;

use function CraftCms\Cms\t;

class FieldReferencesDeletionBlocker extends BaseDeletionBlocker
{
private readonly int $referenceCount;

public function __construct(
ElementCollection $elements,
bool $hardDelete,
array $config = [],
) {
parent::__construct($elements, $hardDelete, $config);

$this->referenceCount = $this->fieldReferences()->referenceCountForTargets($this->elements->ids()->all());
}

public function isActive(): bool
{
return $this->referenceCount !== 0;
}

public function getSummary(): string
{
$targetElementType = $this->targetElementType();

return t('The {numTargets, plural, =1{{targetTypeSingular} is} other{{targetTypePlural} are}} referenced by fields in {numSources, number} other {numSources, plural, =1{element} other{elements}}.', [
'targetTypeSingular' => $targetElementType::lowerDisplayName(),
'targetTypePlural' => $targetElementType::pluralLowerDisplayName(),
'numSources' => $this->referenceCount,
'numTargets' => $this->elements->count(),
]);
}

public function getDetails(): ?string
{
$groups = $this->fieldReferences()->referenceIdsByTypeForTargets($this->elements->ids()->all());

if ($groups->isEmpty()) {
return null;
}

return $groups
->map(function ($sourceIds, string $sourceElementType) {
/** @var class-string<ElementInterface> $sourceElementType */
return Html::tag('h3', $sourceElementType::pluralDisplayName()).
app(ElementIndexHtml::class)->html($sourceElementType, [
'context' => 'pane',
'sources' => false,
'jsSettings' => [
'criteria' => [
'id' => $sourceIds->all(),
'drafts' => null,
'provisionalDrafts' => null,
'revisions' => false,
'status' => null,
],
],
]);
})
->join('');
}

public function getActions(): array
{
$targetElementType = $this->targetElementType();

return [
[
'icon' => 'swap',
'label' => t('Replace {numReferences, plural, =1{reference} other{references}}', [
'numReferences' => $this->referenceCount,
]),
'callback' => Html::jsWithVars(fn (
$targetElementType,
$targetIds,
$hardDelete,
) => <<<JS
new Craft.CpModal('delete-elements/replace-references-modal', {
params: {
elementType: $targetElementType,
elementIds: $targetIds,
hardDelete: $hardDelete,
},
onSubmit: (ev) => {
resolve(ev.response.data.message);
},
onCancel: () => {
reject();
},
})
JS, [
$targetElementType,
$this->elements->ids()->all(),
$this->hardDelete,
]),
],
[
'icon' => 'xmark',
'label' => t('Ignore {numReferences, plural, =1{reference} other{references}}', [
'numReferences' => $this->referenceCount,
]),
'callback' => 'resolve();',
],
];
}

/**
* @return class-string<ElementInterface>
*/
private function targetElementType(): string
{
return $this->elements->first()::class;
}

private function fieldReferences(): FieldReferences
{
return app(FieldReferences::class);
}
}
Loading