Skip to content

Commit 1dedd0d

Browse files
authored
Merge pull request #18728 from craftcms/feature/deletion-blockers
Element deletion blockers
2 parents eca8681 + 4b2d78d commit 1dedd0d

39 files changed

Lines changed: 1718 additions & 163 deletions

CHANGELOG-WIP.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- Timestamps in the control panel now include their time zone abbreviation. ([#18639](https://github.com/craftcms/cms/pull/18639))
2626
- Generated field values are no longer truncated within element cards. ([#18646](https://github.com/craftcms/cms/discussions/18646))
2727
- Assets’ Alternative Text values are now automatically set on upload, based on descriptive text data found in the uploaded file’s metadata. ([#18744](https://github.com/craftcms/cms/pull/18744))
28+
- When deleting elements, a modal window is now shown alerting the user of any potential issues, such as existing relationships. ([#18728](https://github.com/craftcms/cms/pull/18728))
2829

2930
### Administration
3031
- Sections now have a “Min Authors” setting. ([#18662](https://github.com/craftcms/cms/pull/18662))
@@ -48,17 +49,34 @@
4849

4950
### Extensibility
5051
- Added `craft\base\DefaultableFieldInterface`. ([#18522](https://github.com/craftcms/cms/pull/18522))
52+
- Added `craft\base\Element::EVENT_DEFINE_DELETION_BLOCKERS`. ([#18728](https://github.com/craftcms/cms/pull/18728))
53+
- Added `craft\base\ElementInterface::deletionBlockers()`. ([#18728](https://github.com/craftcms/cms/pull/18728))
5154
- Added `craft\base\ElementInterface::setDirtyFieldTracking()`.
5255
- Added `craft\elements\PopulateElementEvent::$content`.
56+
- Added `craft\elements\db\ElementQueryInterface::collectIds()`.
57+
- Added `craft\elements\deletionblockers\BaseDeletionBlocker`. ([#18728](https://github.com/craftcms/cms/pull/18728))
58+
- Added `craft\elements\deletionblockers\DeletionBlockerInterface`. ([#18728](https://github.com/craftcms/cms/pull/18728))
59+
- Added `craft\elements\deletionblockers\EntryAuthorsBlocker`. ([#18728](https://github.com/craftcms/cms/pull/18728))
60+
- Added `craft\elements\deletionblockers\RelationDeletionBlocker`. ([#18728](https://github.com/craftcms/cms/pull/18728))
61+
- Added `craft\events\DefineElementDeletionBlockersEvent`. ([#18728](https://github.com/craftcms/cms/pull/18728))
62+
- Added `craft\helpers\Html::jsWithVars()`.
63+
- Added `craft\helpers\Markdown`. ([#18671](https://github.com/craftcms/cms/issues/18671))
5364
- Added `craft\models\Section::$minAuthors`. ([#18662](https://github.com/craftcms/cms/pull/18662))
65+
- Added `craft\queue\jobs\ReplaceRelations`. ([#18728](https://github.com/craftcms/cms/pull/18728))
66+
- Added `craft\services\Entries::reassignEntries()`.
5467
- Added `craft\validators\TimeValidator::$outOfRange`. ([#18575](https://github.com/craftcms/cms/pull/18575))
5568
- Added `Craft.CpScreenSlideout::reload()`. ([#18625](https://github.com/craftcms/cms/pull/18625))
56-
- Added `craft\helpers\Markdown`. ([#18671](https://github.com/craftcms/cms/issues/18671))
69+
- Added `Craft.ElementDeletionManager`.
5770
- `craft\elements\PopulateElementEvent::$row` no longer includes `fieldValues` or `generatedFieldValues` keys.
5871
- `craft\helpers\DateTimeHelper::timeZoneAbbreviation()` is no longer deprecated, and now has a `$date` argument.
5972
- `craft\i18n\Formatter::asTime()` and `asDatetime()` now have `$withTimeZone` arguments. ([#18639](https://github.com/craftcms/cms/pull/18639))
6073
- Removed `craft\controllers\AppController::actionResourceJs()`. ([#18559](https://github.com/craftcms/cms/pull/18559))
6174
- `Craft.CP` now triggers a `queueCompleted` event when the last queue job is completed.
75+
- Deprecated `craft\controllers\UsersController::EVENT_DEFINE_CONTENT_SUMMARY`. ([#18728](https://github.com/craftcms/cms/pull/18728))
76+
- Deprecated `craft\elements\User::$inheritorOnDelete`. ([#18728](https://github.com/craftcms/cms/pull/18728))
77+
- Deprecated `craft\elements\actions\DeleteUsers`. ([#18728](https://github.com/craftcms/cms/pull/18728))
78+
- Deprecated `craft\events\DefineUserContentSummaryEvent`. ([#18728](https://github.com/craftcms/cms/pull/18728))
79+
- Deprecated `Craft.DeleteUserModal`. ([#18728](https://github.com/craftcms/cms/pull/18728))
6280

6381
### System
6482
- Improve the image quality of WEBP transforms, when `optimizeImageFilesize` is disabled. ([#18635](https://github.com/craftcms/cms/pull/18635))

src/base/Element.php

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use craft\elements\db\ElementQuery;
3434
use craft\elements\db\ElementQueryInterface;
3535
use craft\elements\db\NestedElementQueryInterface;
36+
use craft\elements\deletionblockers\RelationDeletionBlocker;
3637
use craft\elements\ElementCollection;
3738
use craft\elements\Entry;
3839
use craft\elements\exporters\Expanded;
@@ -47,6 +48,7 @@
4748
use craft\events\DefineAttributeHtmlEvent;
4849
use craft\events\DefineAttributeKeywordsEvent;
4950
use craft\events\DefineEagerLoadingMapEvent;
51+
use craft\events\DefineElementDeletionBlockersEvent;
5052
use craft\events\DefineHtmlEvent;
5153
use craft\events\DefineMenuItemsEvent;
5254
use craft\events\DefineMetadataEvent;
@@ -755,6 +757,24 @@ abstract class Element extends Component implements ElementInterface
755757
*/
756758
public const EVENT_AFTER_PROPAGATE = 'afterPropagate';
757759

760+
/**
761+
* @event DefineElementDeletionBlockersEvent The event that is triggered when defining any blockers that should prevent a user from being deleted
762+
*
763+
* ---
764+
* ```php
765+
* use craft\elements\User;
766+
* use craft\events\DefineUserDeletionBlockersEvent;
767+
* use yii\base\Event;
768+
*
769+
* Event::on(User::class, User::EVENT_DEFINE_DELETION_BLOCKERS, function(DefineElementDeletionBlockersEvent $event) {
770+
* $event->blockers[] = // ...
771+
* });
772+
* ```
773+
*
774+
* @since 5.10.0
775+
*/
776+
public const EVENT_DEFINE_DELETION_BLOCKERS = 'defineDeletionBlockers';
777+
758778
/**
759779
* @event ModelEvent The event that is triggered before the element is deleted.
760780
*
@@ -2214,6 +2234,36 @@ private static function _mapRevisionCreators(array $sourceElements): array
22142234
];
22152235
}
22162236

2237+
/**
2238+
* @inheritdoc
2239+
*/
2240+
public static function deletionBlockers(ElementCollection $elements, bool $hardDelete): array
2241+
{
2242+
$blockers = [
2243+
new RelationDeletionBlocker(Entry::class, $elements, $hardDelete, [
2244+
'elementIndexSettings' => [
2245+
'defaultTableColumns' => [
2246+
['section'],
2247+
],
2248+
'defaultSort' => ['section', 'asc'],
2249+
],
2250+
]),
2251+
];
2252+
2253+
// Fire a 'defineDeletionBlockers' event
2254+
if (Event::hasHandlers(static::class, self::EVENT_DEFINE_DELETION_BLOCKERS)) {
2255+
$event = new DefineElementDeletionBlockersEvent([
2256+
'elements' => $elements,
2257+
'hardDelete' => $hardDelete,
2258+
'blockers' => $blockers,
2259+
]);
2260+
Event::trigger(static::class, self::EVENT_DEFINE_DELETION_BLOCKERS, $event);
2261+
$blockers = $event->blockers;
2262+
}
2263+
2264+
return $blockers;
2265+
}
2266+
22172267
/**
22182268
* @inheritdoc
22192269
*/
@@ -4265,22 +4315,47 @@ protected function destructiveActionMenuItems(): array
42654315

42664316
// Delete
42674317
if ($canDeleteCanonical) {
4318+
$view = Craft::$app->getView();
4319+
$deleteId = sprintf('action-delete-%s', mt_rand());
42684320
$items[] = [
4321+
'id' => $deleteId,
42694322
'icon' => 'trash',
42704323
'label' => StringHelper::upperCaseFirst(Craft::t('app', 'Delete {type}', [
42714324
'type' => $isUnpublishedDraft ? Craft::t('app', 'draft') : static::lowerDisplayName(),
42724325
])),
4273-
'action' => $isUnpublishedDraft ? 'elements/delete-draft' : 'elements/delete',
4274-
'params' => [
4275-
'elementId' => $this->getCanonicalId(),
4276-
'siteId' => $this->siteId,
4277-
],
4278-
'redirect' => "$redirectUrl#",
4279-
'confirm' => Craft::t('app', 'Are you sure you want to delete this {type}?', [
4280-
'type' => $isUnpublishedDraft ? Craft::t('app', 'draft') : static::lowerDisplayName(),
4281-
]),
4282-
'destructive' => true,
42834326
];
4327+
4328+
$view->registerJsWithVars(fn(
4329+
$id,
4330+
$elementType,
4331+
$elementId,
4332+
$siteId,
4333+
$ownerId,
4334+
$confirmationMessage,
4335+
$redirect,
4336+
) => <<<JS
4337+
$('#' + $id).on('activate', async () => {
4338+
new Craft.ElementDeletionManager($elementType, [$elementId], {
4339+
siteId: $siteId,
4340+
ownerId: $ownerId,
4341+
confirmationMessage: $confirmationMessage,
4342+
onSuccess: () => {
4343+
document.location.href = $redirect;
4344+
},
4345+
});
4346+
});
4347+
JS,
4348+
[
4349+
$view->namespaceInputId($deleteId),
4350+
static::class,
4351+
$this->id,
4352+
$this->siteId,
4353+
$this instanceof NestedElementInterface ? $this->getOwnerId() : null,
4354+
Craft::t('app', 'Are you sure you want to delete this {type}?', [
4355+
'type' => $isDraft ? Craft::t('app', 'draft') : static::lowerDisplayName(),
4356+
]),
4357+
"$redirectUrl#",
4358+
]);
42844359
}
42854360
} elseif ($isDraft && $canDeleteDraft) {
42864361
// Delete draft for site

src/base/ElementInterface.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use craft\elements\conditions\ElementConditionInterface;
1212
use craft\elements\db\EagerLoadPlan;
1313
use craft\elements\db\ElementQueryInterface;
14+
use craft\elements\deletionblockers\DeletionBlockerInterface;
1415
use craft\elements\ElementCollection;
1516
use craft\elements\User;
1617
use craft\enums\AttributeStatus;
@@ -650,6 +651,16 @@ public static function attributePreviewHtml(array $attribute): mixed;
650651
*/
651652
public static function eagerLoadingMap(array $sourceElements, string $handle): array|null|false;
652653

654+
/**
655+
* Returns any deletion blockers for the given elements.
656+
*
657+
* @param ElementCollection $elements The elements to be deleted
658+
* @param bool $hardDelete Whether the elements will be hard-deleted
659+
* @return DeletionBlockerInterface[]
660+
* @since 5.10.0
661+
*/
662+
public static function deletionBlockers(ElementCollection $elements, bool $hardDelete): array;
663+
653664
/**
654665
* Returns the base GraphQL type name that represents elements of this type.
655666
*

0 commit comments

Comments
 (0)