From 2b2fd22403f467d0ebf6be45d06081b34a6824f5 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:03:03 +0100 Subject: [PATCH 01/31] DefineCacheTags --- src/Element/Concerns/Cacheable.php | 23 ++--------- src/Element/Events/DefineCacheTags.php | 22 ++++++++++ tests/Element/Concerns/CacheableTest.php | 15 ++----- yii2-adapter/legacy/base/Element.php | 52 ++++++++++++++++++++---- yii2-adapter/src/Yii2ServiceProvider.php | 5 +++ 5 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 src/Element/Events/DefineCacheTags.php diff --git a/src/Element/Concerns/Cacheable.php b/src/Element/Concerns/Cacheable.php index ebe89315b66..2e0e1d2121e 100644 --- a/src/Element/Concerns/Cacheable.php +++ b/src/Element/Concerns/Cacheable.php @@ -4,7 +4,7 @@ namespace CraftCms\Cms\Element\Concerns; -use craft\events\DefineValueEvent; +use CraftCms\Cms\Element\Events\DefineCacheTags; /** * Cacheable provides cache tag management for elements. @@ -16,15 +16,6 @@ */ trait Cacheable { - /** - * @event DefineValueEvent The event that is triggered when defining the cache tags that should be cleared when - * this element is saved. - * - * @see getCacheTags() - * @since 4.1.0 - */ - public const EVENT_DEFINE_CACHE_TAGS = 'defineCacheTags'; - /** * Returns the cache tags that should be cleared when this element is saved. * @@ -34,17 +25,9 @@ trait Cacheable */ public function getCacheTags(): array { - $cacheTags = $this->cacheTags(); - - // Fire a 'defineCacheTags' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_CACHE_TAGS)) { - $event = new DefineValueEvent(['value' => $cacheTags]); - $this->trigger(self::EVENT_DEFINE_CACHE_TAGS, $event); - - return $event->value; - } + event($event = new DefineCacheTags($this, $this->cacheTags())); - return $cacheTags; + return $event->tags; } /** diff --git a/src/Element/Events/DefineCacheTags.php b/src/Element/Events/DefineCacheTags.php new file mode 100644 index 00000000000..b578e1417ad --- /dev/null +++ b/src/Element/Events/DefineCacheTags.php @@ -0,0 +1,22 @@ +setCustomCacheTags(['original']); - Event::on( - TestCacheableElement::class, - Element::EVENT_DEFINE_CACHE_TAGS, - function (DefineValueEvent $event) { - $event->value = array_merge($event->value, ['added-by-event']); - } - ); + \Illuminate\Support\Facades\Event::listen(function (DefineCacheTags $event) { + $event->tags = array_merge($event->tags, ['added-by-event']); + }); $tags = $element->getCacheTags(); expect($tags)->toContain('original'); expect($tags)->toContain('added-by-event'); - - Event::off(TestCacheableElement::class, Element::EVENT_DEFINE_CACHE_TAGS); }); test('works with real Entry element', function () { diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index a5fa228935b..4b6936e6ab4 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -7,15 +7,53 @@ namespace craft\base; -/** @phpstan-ignore-next-line */ -if (false) { +use craft\base\Event as YiiEvent; +use craft\events\DefineValueEvent; +use CraftCms\Cms\Element\Events\DefineCacheTags; +use Illuminate\Support\Facades\Event; + +/** + * @since 3.0.0 + * @deprecated 6.0.0 use {@see \CraftCms\Cms\Element\Element} instead. + */ +abstract class Element extends \CraftCms\Cms\Element\Element +{ /** - * @since 3.0.0 - * @deprecated 6.0.0 use {@see \CraftCms\Cms\Element\Element} instead. + * @event DefineValueEvent The event that is triggered when defining the cache tags that should be cleared when + * this element is saved. + * + * @see getCacheTags() + * @since 4.1.0 + * @deprecated 6.0.0 Use {@see DefineCacheTags} instead. */ - abstract class Element + public const EVENT_DEFINE_CACHE_TAGS = 'defineCacheTags'; + + public static function registerEvents(): void { + // Find all classes that extend Element + $classes = get_declared_classes(); + $elementClasses = []; + foreach ($classes as $class) { + if (is_subclass_of($class, self::class)) { + $elementClasses[] = $class; + } + } + + Event::listen(function(DefineCacheTags $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_CACHE_TAGS)) { + return; + } + + $yiiEvent = new DefineValueEvent([ + 'sender' => $event->element, + 'value' => $event->tags, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_CACHE_TAGS, $yiiEvent); + + $event->tags = $yiiEvent->value; + } + }); } } - -class_alias(\CraftCms\Cms\Element\Element::class, Element::class); diff --git a/yii2-adapter/src/Yii2ServiceProvider.php b/yii2-adapter/src/Yii2ServiceProvider.php index 464963bfb38..02af0b9da7d 100644 --- a/yii2-adapter/src/Yii2ServiceProvider.php +++ b/yii2-adapter/src/Yii2ServiceProvider.php @@ -471,6 +471,11 @@ public function convertDefinition(array $definition, string $type): string */ private function bootEvents(): void { + /** + * Elements + */ + \craft\base\Element::registerEvents(); + /** * Services */ From dde24dde956915e227add71f69e371bfacf990f6 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:11:07 +0100 Subject: [PATCH 02/31] Convert EVENT_REGISTER_SOURCES and EVENT_REGISTER_FIELD_LAYOUTS to Laravel events - Add RegisterSources and RegisterFieldLayouts Laravel event classes - Update HasSources concern to dispatch Laravel events - Remove EVENT_REGISTER_FIELD_LAYOUTS constant from HasCustomFields - Add backwards compatibility layer in yii2-adapter Element - Update tests to use Laravel Event::listen pattern --- src/Element/Concerns/HasCustomFields.php | 17 ++---- src/Element/Concerns/HasSources.php | 31 ++-------- src/Element/Events/RegisterFieldLayouts.php | 27 ++++++++ src/Element/Events/RegisterSources.php | 24 ++++++++ tests/Element/Concerns/HasSourcesTest.php | 37 +++++------ yii2-adapter/legacy/base/Element.php | 68 ++++++++++++++++++++- 6 files changed, 146 insertions(+), 58 deletions(-) create mode 100644 src/Element/Events/RegisterFieldLayouts.php create mode 100644 src/Element/Events/RegisterSources.php diff --git a/src/Element/Concerns/HasCustomFields.php b/src/Element/Concerns/HasCustomFields.php index 68be33ff8b0..8ea7a8b7985 100644 --- a/src/Element/Concerns/HasCustomFields.php +++ b/src/Element/Concerns/HasCustomFields.php @@ -23,9 +23,9 @@ * This trait contains all logic related to getting, setting, normalizing, * and tracking changes to custom field values on elements. * - * @property array $serializedFieldValues Array of the element’s serialized custom field values, indexed by their handles - * @property array $fieldValues The element’s normalized custom field values, indexed by their handles - * @property string $fieldContext The field context this element’s content uses + * @property array $serializedFieldValues Array of the element's serialized custom field values, indexed by their handles + * @property array $fieldValues The element's normalized custom field values, indexed by their handles + * @property string $fieldContext The field context this element's content uses * @property FieldLayout|null $fieldLayout The field layout used by this element * @property array $fieldParamNamespace The namespace used by custom field params on the request * @@ -34,16 +34,7 @@ trait HasCustomFields { /** - * @event RegisterElementFieldLayoutsEvent The event that is triggered when registering all of the field layouts - * associated with elements from a given source. - * - * @see fieldLayouts() - * @since 3.5.0 - */ - public const EVENT_REGISTER_FIELD_LAYOUTS = 'registerFieldLayouts'; - - /** - * @var int|null The element’s field layout ID + * @var int|null The element's field layout ID */ public ?int $fieldLayoutId = null; diff --git a/src/Element/Concerns/HasSources.php b/src/Element/Concerns/HasSources.php index bdbb95a5a5b..a6eb8e2c51d 100644 --- a/src/Element/Concerns/HasSources.php +++ b/src/Element/Concerns/HasSources.php @@ -4,11 +4,10 @@ namespace CraftCms\Cms\Element\Concerns; -use craft\events\RegisterElementFieldLayoutsEvent; -use craft\events\RegisterElementSourcesEvent; use craft\models\FieldLayout; +use CraftCms\Cms\Element\Events\RegisterFieldLayouts; +use CraftCms\Cms\Element\Events\RegisterSources; use CraftCms\Cms\Support\Facades\Fields; -use yii\base\Event; /** * HasSources provides element source management functionality. @@ -20,11 +19,6 @@ */ trait HasSources { - /** - * @event RegisterElementSourcesEvent The event that is triggered when registering the available sources for the element type. - */ - public const EVENT_REGISTER_SOURCES = 'registerSources'; - /** * @see sources() */ @@ -47,15 +41,8 @@ public static function sources(string $context): array // Memoize the results immediately, in case sources() gets called again via the event self::$sources[static::class][$context] = static::defineSources($context); - // Fire a 'registerSources' event - if (Event::hasHandlers(static::class, self::EVENT_REGISTER_SOURCES)) { - $event = new RegisterElementSourcesEvent([ - 'context' => $context, - 'sources' => self::$sources[static::class][$context], - ]); - Event::trigger(static::class, self::EVENT_REGISTER_SOURCES, $event); - self::$sources[static::class][$context] = $event->sources; - } + event($event = new RegisterSources(static::class, $context, self::$sources[static::class][$context])); + self::$sources[static::class][$context] = $event->sources; } return self::$sources[static::class][$context]; @@ -105,15 +92,7 @@ public static function fieldLayouts(?string $source): array { $fieldLayouts = static::defineFieldLayouts($source); - if (! Event::hasHandlers(static::class, self::EVENT_REGISTER_FIELD_LAYOUTS)) { - return $fieldLayouts; - } - - $event = new RegisterElementFieldLayoutsEvent([ - 'source' => $source, - 'fieldLayouts' => $fieldLayouts, - ]); - Event::trigger(static::class, self::EVENT_REGISTER_FIELD_LAYOUTS, $event); + event($event = new RegisterFieldLayouts(static::class, $source, $fieldLayouts)); return $event->fieldLayouts; } diff --git a/src/Element/Events/RegisterFieldLayouts.php b/src/Element/Events/RegisterFieldLayouts.php new file mode 100644 index 00000000000..e32111c3a43 --- /dev/null +++ b/src/Element/Events/RegisterFieldLayouts.php @@ -0,0 +1,27 @@ +toBe($sources2); }); - test('triggers registerSources event', function () { + test('triggers RegisterSources event', function () { $eventTriggered = false; - $handler = function (RegisterElementSourcesEvent $event) use (&$eventTriggered) { - $eventTriggered = true; - $event->sources = []; - }; - Event::on(TestHasSourcesElement::class, TestHasSourcesElement::EVENT_REGISTER_SOURCES, $handler); - TestHasSourcesElement::sources('index'); - Event::off(TestHasSourcesElement::class, TestHasSourcesElement::EVENT_REGISTER_SOURCES, $handler); + \Illuminate\Support\Facades\Event::listen(function (RegisterSources $event) use (&$eventTriggered) { + if ($event->elementType === TestHasSourcesElement::class) { + $eventTriggered = true; + $event->sources = []; + } + }); + + TestHasSourcesElement::sources('modal'); expect($eventTriggered)->toBeTrue(); }); @@ -82,16 +82,17 @@ public static function displayName(): string expect($layouts)->toBeArray(); }); - test('triggers registerFieldLayouts event', function () { + test('triggers RegisterFieldLayouts event', function () { $eventTriggered = false; - $handler = function (RegisterElementFieldLayoutsEvent $event) use (&$eventTriggered) { - $eventTriggered = true; - $event->fieldLayouts = []; - }; - Event::on(Entry::class, Entry::EVENT_REGISTER_FIELD_LAYOUTS, $handler); + \Illuminate\Support\Facades\Event::listen(function (RegisterFieldLayouts $event) use (&$eventTriggered) { + if ($event->elementType === Entry::class) { + $eventTriggered = true; + $event->fieldLayouts = []; + } + }); + Entry::fieldLayouts(null); - Event::off(Entry::class, Entry::EVENT_REGISTER_FIELD_LAYOUTS, $handler); expect($eventTriggered)->toBeTrue(); }); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 4b6936e6ab4..5b4d3d924b4 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -1,6 +1,8 @@ tags = $yiiEvent->value; } }); + + Event::listen(function(RegisterSources $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_SOURCES)) { + continue; + } + + $yiiEvent = new RegisterElementSourcesEvent([ + 'context' => $event->context, + 'sources' => $event->sources, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_SOURCES, $yiiEvent); + + $event->sources = $yiiEvent->sources; + } + }); + + Event::listen(function(RegisterFieldLayouts $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_FIELD_LAYOUTS)) { + continue; + } + + $yiiEvent = new RegisterElementFieldLayoutsEvent([ + 'source' => $event->source, + 'fieldLayouts' => $event->fieldLayouts, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_FIELD_LAYOUTS, $yiiEvent); + + $event->fieldLayouts = $yiiEvent->fieldLayouts; + } + }); } } From 684a6c62301fb5275cda6368d0639c5dcea50a6f Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:13:31 +0100 Subject: [PATCH 03/31] Convert EVENT_REGISTER_PREVIEW_TARGETS to Laravel event --- src/Element/Concerns/HasPreviewTargets.php | 20 ++----------- src/Element/Events/RegisterPreviewTargets.php | 24 +++++++++++++++ .../Concerns/HasPreviewTargetsTest.php | 27 ++++++----------- yii2-adapter/legacy/base/Element.php | 30 +++++++++++++++++++ 4 files changed, 66 insertions(+), 35 deletions(-) create mode 100644 src/Element/Events/RegisterPreviewTargets.php diff --git a/src/Element/Concerns/HasPreviewTargets.php b/src/Element/Concerns/HasPreviewTargets.php index 5719308eb64..f5702d20e92 100644 --- a/src/Element/Concerns/HasPreviewTargets.php +++ b/src/Element/Concerns/HasPreviewTargets.php @@ -5,8 +5,8 @@ namespace CraftCms\Cms\Element\Concerns; use Craft; -use craft\events\RegisterPreviewTargetsEvent; use craft\helpers\UrlHelper; +use CraftCms\Cms\Element\Events\RegisterPreviewTargets; use CraftCms\Cms\Support\Env; use Illuminate\Support\Collection; @@ -22,13 +22,6 @@ */ trait HasPreviewTargets { - /** - * @event RegisterPreviewTargetsEvent The event that is triggered when registering the element's preview targets. - * - * @since 3.2.0 - */ - public const EVENT_REGISTER_PREVIEW_TARGETS = 'registerPreviewTargets'; - /** * @var bool Whether the element is currently being previewed. * @@ -43,17 +36,10 @@ trait HasPreviewTargets */ public function getPreviewTargets(): array { - $previewTargets = $this->previewTargets(); - - // Fire a 'registerPreviewTargets' event - if ($this->hasEventHandlers(self::EVENT_REGISTER_PREVIEW_TARGETS)) { - $event = new RegisterPreviewTargetsEvent(['previewTargets' => $previewTargets]); - $this->trigger(self::EVENT_REGISTER_PREVIEW_TARGETS, $event); - $previewTargets = $event->previewTargets; - } + event($event = new RegisterPreviewTargets($this, $this->previewTargets())); // Normalize the targets - return new Collection($previewTargets) + return new Collection($event->previewTargets) ->map(function (array $previewTarget) { if (isset($previewTarget['urlFormat'])) { $url = trim(Craft::$app->getView()->renderObjectTemplate(Env::parse($previewTarget['urlFormat']), $this)); diff --git a/src/Element/Events/RegisterPreviewTargets.php b/src/Element/Events/RegisterPreviewTargets.php new file mode 100644 index 00000000000..356d6f974de --- /dev/null +++ b/src/Element/Events/RegisterPreviewTargets.php @@ -0,0 +1,24 @@ +primarySiteId = Sites::getPrimarySite()->id; }); -afterEach(function () { - Event::off(TestPreviewTargetsElement::class, Element::EVENT_REGISTER_PREVIEW_TARGETS); -}); - describe('getPreviewTargets', function () { test('returns empty array when element has no URL', function () { $element = new TestPreviewTargetsElement; @@ -164,21 +159,19 @@ protected function previewTargets(): array expect($targets[0]['refresh'])->toBeFalse(); }); - test('EVENT_REGISTER_PREVIEW_TARGETS can add targets', function () { + test('RegisterPreviewTargets event can add targets', function () { $element = new TestPreviewTargetsElement; $element->siteId = $this->primarySiteId; $element->setElementUrl('https://example.com/original'); - Event::on( - TestPreviewTargetsElement::class, - Element::EVENT_REGISTER_PREVIEW_TARGETS, - function (RegisterPreviewTargetsEvent $event) { + \Illuminate\Support\Facades\Event::listen(function (RegisterPreviewTargets $event) { + if ($event->element instanceof TestPreviewTargetsElement) { $event->previewTargets[] = [ 'label' => 'Added by Event', 'url' => 'https://example.com/event-added', ]; } - ); + }); $targets = $element->getPreviewTargets(); @@ -186,7 +179,7 @@ function (RegisterPreviewTargetsEvent $event) { expect($targets[1]['label'])->toBe('Added by Event'); }); - test('EVENT_REGISTER_PREVIEW_TARGETS can modify existing targets', function () { + test('RegisterPreviewTargets event can modify existing targets', function () { $element = new TestPreviewTargetsElement; $element->siteId = $this->primarySiteId; $element->setCustomPreviewTargets([ @@ -196,13 +189,11 @@ function (RegisterPreviewTargetsEvent $event) { ], ]); - Event::on( - TestPreviewTargetsElement::class, - Element::EVENT_REGISTER_PREVIEW_TARGETS, - function (RegisterPreviewTargetsEvent $event) { + \Illuminate\Support\Facades\Event::listen(function (RegisterPreviewTargets $event) { + if ($event->element instanceof TestPreviewTargetsElement) { $event->previewTargets[0]['label'] = 'Modified by Event'; } - ); + }); $targets = $element->getPreviewTargets(); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 5b4d3d924b4..609988ea7f8 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -52,6 +52,15 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_REGISTER_FIELD_LAYOUTS = 'registerFieldLayouts'; + /** + * @event RegisterPreviewTargetsEvent The event that is triggered when registering the element's preview targets. + * + * @see getPreviewTargets() + * @since 3.2.0 + * @deprecated 6.0.0 Use {@see RegisterPreviewTargets} instead. + */ + public const EVENT_REGISTER_PREVIEW_TARGETS = 'registerPreviewTargets'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -121,5 +130,26 @@ public static function registerEvents(): void $event->fieldLayouts = $yiiEvent->fieldLayouts; } }); + + Event::listen(function(RegisterPreviewTargets $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_PREVIEW_TARGETS)) { + continue; + } + + $yiiEvent = new RegisterPreviewTargetsEvent([ + 'sender' => $event->element, + 'previewTargets' => $event->previewTargets, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_PREVIEW_TARGETS, $yiiEvent); + + $event->previewTargets = $yiiEvent->previewTargets; + } + }); } } From cbc273646bcf6abaebd398c2b450aae01a7d51f8 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:15:09 +0100 Subject: [PATCH 04/31] Convert EVENT_REGISTER_ACTIONS to Laravel event --- src/Element/Concerns/HasActions.php | 27 +++++-------------- src/Element/Events/RegisterActions.php | 24 +++++++++++++++++ tests/Element/Concerns/HasActionsTest.php | 18 +++++++++++++ yii2-adapter/legacy/base/Element.php | 32 +++++++++++++++++++++++ 4 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 src/Element/Events/RegisterActions.php diff --git a/src/Element/Concerns/HasActions.php b/src/Element/Concerns/HasActions.php index 1925d4978e4..a81f7e801e2 100644 --- a/src/Element/Concerns/HasActions.php +++ b/src/Element/Concerns/HasActions.php @@ -10,9 +10,8 @@ use craft\elements\actions\Edit; use craft\elements\actions\SetStatus; use craft\elements\actions\View as ViewAction; -use craft\events\RegisterElementActionsEvent; +use CraftCms\Cms\Element\Events\RegisterActions; use Illuminate\Support\Collection; -use yii\base\Event; use function CraftCms\Cms\t; @@ -26,11 +25,6 @@ */ trait HasActions { - /** - * @event RegisterElementActionsEvent The event that is triggered when registering the available bulk actions for the element type. - */ - public const EVENT_REGISTER_ACTIONS = 'registerActions'; - /** * {@inheritdoc} */ @@ -86,20 +80,13 @@ public static function actions(string $source): array $actions->push(Delete::class); } - $actions = $actions->all(); - - // Fire a 'registerActions' event - if (Event::hasHandlers(static::class, static::EVENT_REGISTER_ACTIONS)) { - $event = new RegisterElementActionsEvent([ - 'source' => $source, - 'actions' => $actions, - ]); - Event::trigger(static::class, static::EVENT_REGISTER_ACTIONS, $event); - - return $event->actions; - } + event($event = new RegisterActions( + elementType: static::class, + source: $source, + actions: $actions->all(), + )); - return $actions; + return $event->actions; } /** diff --git a/src/Element/Events/RegisterActions.php b/src/Element/Events/RegisterActions.php new file mode 100644 index 00000000000..f97e91c63f4 --- /dev/null +++ b/src/Element/Events/RegisterActions.php @@ -0,0 +1,24 @@ + $elementType The element type class + * @param string $source The selected source's key + * @param array $actions List of registered bulk actions for the element type + */ + public function __construct( + public string $elementType, + public string $source, + public array $actions = [], + ) {} +} diff --git a/tests/Element/Concerns/HasActionsTest.php b/tests/Element/Concerns/HasActionsTest.php index cae242fab95..948f55bc38c 100644 --- a/tests/Element/Concerns/HasActionsTest.php +++ b/tests/Element/Concerns/HasActionsTest.php @@ -7,7 +7,9 @@ use craft\elements\actions\Edit; use craft\elements\actions\SetStatus; use craft\elements\actions\View; +use CraftCms\Cms\Element\Events\RegisterActions; use CraftCms\Cms\Entry\Elements\Entry; +use Illuminate\Support\Facades\Event; function extractActionTypes(array $actions): array { @@ -43,3 +45,19 @@ protected static function defineActions(string $source): array expect($class::actions('all'))->toContain(Foo::class); }); + +test('RegisterActions event allows adding custom actions', function () { + Event::listen(function (RegisterActions $event) { + if ($event->elementType === Entry::class) { + $event->actions[] = CustomAction::class; + } + }); + + $actions = Entry::actions('all'); + $actionTypes = extractActionTypes($actions); + + expect($actionTypes)->toContain(CustomAction::class); +}); + +class CustomAction {} +class Foo {} diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 609988ea7f8..3f0117d85c3 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -13,8 +13,10 @@ use craft\events\DefineValueEvent; use craft\events\RegisterElementFieldLayoutsEvent; use craft\events\RegisterElementSourcesEvent; +use craft\events\RegisterPreviewTargetsEvent; use CraftCms\Cms\Element\Events\DefineCacheTags; use CraftCms\Cms\Element\Events\RegisterFieldLayouts; +use CraftCms\Cms\Element\Events\RegisterPreviewTargets; use CraftCms\Cms\Element\Events\RegisterSources; use Illuminate\Support\Facades\Event; @@ -61,6 +63,15 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_REGISTER_PREVIEW_TARGETS = 'registerPreviewTargets'; + /** + * @event RegisterElementActionsEvent The event that is triggered when registering the available bulk actions for the element type. + * + * @see actions() + * @since 3.0.0 + * @deprecated 6.0.0 Use {@see RegisterActions} instead. + */ + public const EVENT_REGISTER_ACTIONS = 'registerActions'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -151,5 +162,26 @@ public static function registerEvents(): void $event->previewTargets = $yiiEvent->previewTargets; } }); + + Event::listen(function(RegisterActions $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_ACTIONS)) { + continue; + } + + $yiiEvent = new RegisterElementActionsEvent([ + 'source' => $event->source, + 'actions' => $event->actions, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_ACTIONS, $yiiEvent); + + $event->actions = $yiiEvent->actions; + } + }); } } From 3f2a44d1a0cba9ca28012c7bfd61160183c4b605 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:16:48 +0100 Subject: [PATCH 05/31] Convert EVENT_REGISTER_EXPORTERS to Laravel event --- src/Element/Concerns/Exportable.php | 29 ++++--------- src/Element/Events/RegisterExporters.php | 24 +++++++++++ tests/Element/Concerns/ExportableTest.php | 51 +++++++---------------- yii2-adapter/legacy/base/Element.php | 32 ++++++++++++++ 4 files changed, 78 insertions(+), 58 deletions(-) create mode 100644 src/Element/Events/RegisterExporters.php diff --git a/src/Element/Concerns/Exportable.php b/src/Element/Concerns/Exportable.php index eabb58c7933..06070b3afa6 100644 --- a/src/Element/Concerns/Exportable.php +++ b/src/Element/Concerns/Exportable.php @@ -6,8 +6,7 @@ use craft\elements\exporters\Expanded; use craft\elements\exporters\Raw; -use craft\events\RegisterElementExportersEvent; -use yii\base\Event; +use CraftCms\Cms\Element\Events\RegisterExporters; /** * Exportable provides element export functionality. @@ -19,13 +18,6 @@ */ trait Exportable { - /** - * @event RegisterElementExportersEvent The event that is triggered when registering the available exporters for the element type. - * - * @since 3.4.0 - */ - public const EVENT_REGISTER_EXPORTERS = 'registerExporters'; - /** * Returns the available element exporters for a given source. * @@ -36,20 +28,13 @@ trait Exportable */ public static function exporters(string $source): array { - $exporters = static::defineExporters($source); - - // Fire a 'registerExporters' event - if (Event::hasHandlers(static::class, self::EVENT_REGISTER_EXPORTERS)) { - $event = new RegisterElementExportersEvent([ - 'source' => $source, - 'exporters' => $exporters, - ]); - Event::trigger(static::class, self::EVENT_REGISTER_EXPORTERS, $event); - - return $event->exporters; - } + event($event = new RegisterExporters( + elementType: static::class, + source: $source, + exporters: static::defineExporters($source), + )); - return $exporters; + return $event->exporters; } /** diff --git a/src/Element/Events/RegisterExporters.php b/src/Element/Events/RegisterExporters.php new file mode 100644 index 00000000000..19515583133 --- /dev/null +++ b/src/Element/Events/RegisterExporters.php @@ -0,0 +1,24 @@ + $elementType The element type class + * @param string $source The selected source's key + * @param array $exporters List of registered exporters for the element type + */ + public function __construct( + public string $elementType, + public string $source, + public array $exporters = [], + ) {} +} diff --git a/tests/Element/Concerns/ExportableTest.php b/tests/Element/Concerns/ExportableTest.php index 4f950e85e99..63d6fb48a9d 100644 --- a/tests/Element/Concerns/ExportableTest.php +++ b/tests/Element/Concerns/ExportableTest.php @@ -4,9 +4,9 @@ use craft\elements\exporters\Expanded; use craft\elements\exporters\Raw; -use craft\events\RegisterElementExportersEvent; +use CraftCms\Cms\Element\Events\RegisterExporters; use CraftCms\Cms\Entry\Elements\Entry; -use yii\base\Event; +use Illuminate\Support\Facades\Event; describe('exporters', function () { test('returns default exporters', function () { @@ -27,8 +27,7 @@ ]); }); - test('triggers registerExporters event', function () { - $eventTriggered = false; + test('RegisterExporters event allows adding custom exporters', function () { $customExporter = new class { public static function displayName(): string @@ -37,72 +36,52 @@ public static function displayName(): string } }; - Event::on( - Entry::class, - Entry::EVENT_REGISTER_EXPORTERS, - function (RegisterElementExportersEvent $event) use (&$eventTriggered, $customExporter) { - $eventTriggered = true; + Event::listen(function (RegisterExporters $event) use ($customExporter) { + if ($event->elementType === Entry::class) { $event->exporters[] = $customExporter::class; } - ); + }); $exporters = Entry::exporters('*'); - expect($eventTriggered)->toBeTrue(); expect($exporters)->toContain(Raw::class); expect($exporters)->toContain(Expanded::class); expect($exporters)->toContain($customExporter::class); - - Event::off(Entry::class, Entry::EVENT_REGISTER_EXPORTERS); }); test('event provides source key', function () { $capturedSource = null; - Event::on( - Entry::class, - Entry::EVENT_REGISTER_EXPORTERS, - function (RegisterElementExportersEvent $event) use (&$capturedSource) { - $capturedSource = $event->source; - } - ); + Event::listen(function (RegisterExporters $event) use (&$capturedSource) { + $capturedSource = $event->source; + }); Entry::exporters('section:my-section'); expect($capturedSource)->toBe('section:my-section'); - - Event::off(Entry::class, Entry::EVENT_REGISTER_EXPORTERS); }); test('event can modify exporters', function () { - Event::on( - Entry::class, - Entry::EVENT_REGISTER_EXPORTERS, - function (RegisterElementExportersEvent $event) { + Event::listen(function (RegisterExporters $event) { + if ($event->elementType === Entry::class) { $event->exporters = [Raw::class]; } - ); + }); $exporters = Entry::exporters('*'); expect($exporters)->toBe([Raw::class]); - - Event::off(Entry::class, Entry::EVENT_REGISTER_EXPORTERS); }); test('event can remove all exporters', function () { - Event::on( - Entry::class, - Entry::EVENT_REGISTER_EXPORTERS, - function (RegisterElementExportersEvent $event) { + Event::listen(function (RegisterExporters $event) { + if ($event->elementType === Entry::class) { $event->exporters = []; } - ); + }); $exporters = Entry::exporters('*'); expect($exporters)->toBe([]); - - Event::off(Entry::class, Entry::EVENT_REGISTER_EXPORTERS); }); }); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 3f0117d85c3..68f5d2a4f2d 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -11,10 +11,12 @@ use craft\base\Event as YiiEvent; use craft\events\DefineValueEvent; +use craft\events\RegisterElementActionsEvent; use craft\events\RegisterElementFieldLayoutsEvent; use craft\events\RegisterElementSourcesEvent; use craft\events\RegisterPreviewTargetsEvent; use CraftCms\Cms\Element\Events\DefineCacheTags; +use CraftCms\Cms\Element\Events\RegisterActions; use CraftCms\Cms\Element\Events\RegisterFieldLayouts; use CraftCms\Cms\Element\Events\RegisterPreviewTargets; use CraftCms\Cms\Element\Events\RegisterSources; @@ -72,6 +74,15 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_REGISTER_ACTIONS = 'registerActions'; + /** + * @event RegisterElementExportersEvent The event that is triggered when registering the available exporters for the element type. + * + * @see exporters() + * @since 3.4.0 + * @deprecated 6.0.0 Use {@see RegisterExporters} instead. + */ + public const EVENT_REGISTER_EXPORTERS = 'registerExporters'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -183,5 +194,26 @@ public static function registerEvents(): void $event->actions = $yiiEvent->actions; } }); + + Event::listen(function(RegisterExporters $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_EXPORTERS)) { + continue; + } + + $yiiEvent = new RegisterElementExportersEvent([ + 'source' => $event->source, + 'exporters' => $event->exporters, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_EXPORTERS, $yiiEvent); + + $event->exporters = $yiiEvent->exporters; + } + }); } } From 2bbb5e434182b2fdedd424d7414e9b168c87f3a3 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:19:16 +0100 Subject: [PATCH 06/31] Convert EVENT_RENDER to Laravel event --- src/Element/Concerns/Renderable.php | 46 +++++-------------- src/Element/Events/Render.php | 30 ++++++++++++ tests/Element/Concerns/RenderableTest.php | 56 +++++++---------------- yii2-adapter/legacy/base/Element.php | 37 +++++++++++++++ 4 files changed, 94 insertions(+), 75 deletions(-) create mode 100644 src/Element/Events/Render.php diff --git a/src/Element/Concerns/Renderable.php b/src/Element/Concerns/Renderable.php index 5c79cf0e56e..2cc777c7f67 100644 --- a/src/Element/Concerns/Renderable.php +++ b/src/Element/Concerns/Renderable.php @@ -5,9 +5,9 @@ namespace CraftCms\Cms\Element\Concerns; use Craft; -use craft\events\RenderElementEvent; use craft\web\View; use CraftCms\Cms\Cms; +use CraftCms\Cms\Element\Events\Render; use CraftCms\Cms\Support\Arr; use CraftCms\Cms\Support\Html; use Twig\Markup; @@ -22,27 +22,6 @@ */ trait Renderable { - /** - * @event RenderElementEvent The event that is triggered before an element is rendered. - * - * @since 5.7.5 - * - * ```php - * use CraftCms\Cms\Element\Element; - * use craft\events\RenderElementEvent; - * use yii\base\Event; - * - * Event::on( - * Element::class, - * Element::EVENT_RENDER, - * function(RenderElementEvent $event) { - * $event->output = '…'; - * } - * ); - * ``` - */ - public const EVENT_RENDER = 'render'; - /** * {@inheritdoc} */ @@ -54,22 +33,19 @@ public function render(array $variables = []): Markup $variables[$refHandle] = $this; } - if ($this->hasEventHandlers(self::EVENT_RENDER)) { - $event = new RenderElementEvent([ - 'templates' => $templates, - 'variables' => $variables, - ]); - - $this->trigger(self::EVENT_RENDER, $event); + event($event = new Render( + element: $this, + templates: $templates, + variables: $variables, + )); - if (isset($event->output)) { - return new Markup($event->output, 'UTF-8'); - } - - $templates = $event->templates; - $variables = $event->variables; + if ($event->output !== null) { + return new Markup($event->output, 'UTF-8'); } + $templates = $event->templates; + $variables = $event->variables; + if (! empty($templates)) { $view = Craft::$app->getView(); foreach (Arr::sort($templates, 'priority') as $template) { diff --git a/src/Element/Events/Render.php b/src/Element/Events/Render.php new file mode 100644 index 00000000000..38d8667c3ed --- /dev/null +++ b/src/Element/Events/Render.php @@ -0,0 +1,30 @@ +toBeInstanceOf(Markup::class); }); - test('triggers render event', function () { - $eventTriggered = false; + test('Render event allows setting custom output', function () { $customOutput = 'Custom Output'; - Event::on( - Element::class, - Element::EVENT_RENDER, - function (RenderElementEvent $event) use (&$eventTriggered, $customOutput) { - $eventTriggered = true; - $event->output = $customOutput; - } - ); + Event::listen(function (Render $event) use ($customOutput) { + $event->output = $customOutput; + }); $markup = $this->entry->render(); - expect($eventTriggered)->toBeTrue(); expect((string) $markup)->toBe($customOutput); - - Event::off(Element::class, Element::EVENT_RENDER); }); - test('event can modify variables and templates', function () { - $eventTriggered = false; - $customVariables = ['foo' => 'bar']; - - Event::on( - Element::class, - Element::EVENT_RENDER, - function (RenderElementEvent $event) use (&$eventTriggered, $customVariables) { - $eventTriggered = true; - $event->variables = array_merge($event->variables, $customVariables); - } - ); - - // We can't easily verify templates logic without setting up view paths, - // but we can verify the event was triggered and properties were accessible. - $this->entry->render(); + test('Render event can modify variables and templates', function () { + $capturedVariables = null; - expect($eventTriggered)->toBeTrue(); + Event::listen(function (Render $event) use (&$capturedVariables) { + $event->variables = array_merge($event->variables, ['foo' => 'bar']); + $capturedVariables = $event->variables; + }); - Event::off(Element::class, Element::EVENT_RENDER); + $this->entry->render(); + + expect($capturedVariables)->toHaveKey('foo'); + expect($capturedVariables['foo'])->toBe('bar'); }); }); describe('partialTemplatePathCandidates', function () { test('returns correct candidates', function () { - // partialTemplatePathCandidates is protected, so we access it via reflection or if we can infer it from render behavior. - // Or we can assume it works if render works. - // However, we can use reflection to test protected methods if needed, or check if public API exposes it. - // Element::render uses it. - - // We'll test it via reflection to be sure. $reflection = new ReflectionClass($this->entry); $method = $reflection->getMethod('partialTemplatePathCandidates'); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 68f5d2a4f2d..ce179777283 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -12,11 +12,13 @@ use craft\base\Event as YiiEvent; use craft\events\DefineValueEvent; use craft\events\RegisterElementActionsEvent; +use craft\events\RegisterElementExportersEvent; use craft\events\RegisterElementFieldLayoutsEvent; use craft\events\RegisterElementSourcesEvent; use craft\events\RegisterPreviewTargetsEvent; use CraftCms\Cms\Element\Events\DefineCacheTags; use CraftCms\Cms\Element\Events\RegisterActions; +use CraftCms\Cms\Element\Events\RegisterExporters; use CraftCms\Cms\Element\Events\RegisterFieldLayouts; use CraftCms\Cms\Element\Events\RegisterPreviewTargets; use CraftCms\Cms\Element\Events\RegisterSources; @@ -83,6 +85,15 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_REGISTER_EXPORTERS = 'registerExporters'; + /** + * @event RenderElementEvent The event that is triggered before an element is rendered. + * + * @see render() + * @since 5.7.5 + * @deprecated 6.0.0 Use {@see Render} instead. + */ + public const EVENT_RENDER = 'render'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -215,5 +226,31 @@ public static function registerEvents(): void $event->exporters = $yiiEvent->exporters; } }); + + Event::listen(function(Render $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_RENDER)) { + continue; + } + + $yiiEvent = new RenderElementEvent([ + 'sender' => $event->element, + 'templates' => $event->templates, + 'variables' => $event->variables, + ]); + + YiiEvent::trigger($class, self::EVENT_RENDER, $yiiEvent); + + if (isset($yiiEvent->output)) { + $event->output = $yiiEvent->output; + } + $event->templates = $yiiEvent->templates; + $event->variables = $yiiEvent->variables; + } + }); } } From 4d2b6222a0009864696a536b3816571ad5868829 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:21:33 +0100 Subject: [PATCH 07/31] Convert EVENT_DEFINE_KEYWORDS to Laravel event --- src/Element/Concerns/Searchable.php | 46 ++++--------------- src/Element/Events/DefineKeywords.php | 30 +++++++++++++ tests/Element/Concerns/SearchableTest.php | 54 ++++++++--------------- yii2-adapter/legacy/base/Element.php | 36 +++++++++++++++ 4 files changed, 93 insertions(+), 73 deletions(-) create mode 100644 src/Element/Events/DefineKeywords.php diff --git a/src/Element/Concerns/Searchable.php b/src/Element/Concerns/Searchable.php index ccc6980d970..74a0f5ba71e 100644 --- a/src/Element/Concerns/Searchable.php +++ b/src/Element/Concerns/Searchable.php @@ -4,7 +4,7 @@ namespace CraftCms\Cms\Element\Concerns; -use craft\events\DefineAttributeKeywordsEvent; +use CraftCms\Cms\Element\Events\DefineKeywords; use CraftCms\Cms\Support\Str; /** @@ -19,46 +19,18 @@ */ trait Searchable { - /** - * @event DefineAttributeKeywordsEvent The event that is triggered when defining the search keywords for an - * element attribute. - * - * Note that you _must_ set [[Event::$handled]] to `true` if you want the element to accept your custom - * [[DefineAttributeKeywordsEvent::$keywords|$keywords]] value. - * - * ```php - * Event::on( - * craft\elements\Entry::class, - * craft\base\Element::EVENT_DEFINE_KEYWORDS, - * function(craft\events\DefineAttributeKeywordsEvent $e - * ) { - * // @var craft\elements\Entry $entry - * $entry = $e->sender; - * - * // Prevent entry titles in the Parts section from getting search keywords - * if ($entry->section->handle === 'parts' && $e->attribute === 'title') { - * $e->keywords = ''; - * $e->handled = true; - * } - * }); - * ``` - * - * @since 3.5.0 - */ - public const EVENT_DEFINE_KEYWORDS = 'defineKeywords'; - /** * @event RegisterElementSearchableAttributesEvent The event that is triggered when registering the searchable attributes for the element type. */ public const EVENT_REGISTER_SEARCHABLE_ATTRIBUTES = 'registerSearchableAttributes'; /** - * @var int|null The element’s search score, if the [[\craft\elements\db\ElementQuery::search]] parameter was used when querying for the element + * @var int|null The element's search score, if the [[\craft\elements\db\ElementQuery::search]] parameter was used when querying for the element */ public ?int $searchScore = null; /** - * @var bool Whether the element’s search keywords should be indexed immediately. + * @var bool Whether the element's search keywords should be indexed immediately. * * If `null`, the search index will only be updated immediately for console requests. * @@ -71,13 +43,13 @@ trait Searchable */ public function getSearchKeywords(string $attribute): string { - if ($this->hasEventHandlers(self::EVENT_DEFINE_KEYWORDS)) { - $event = new DefineAttributeKeywordsEvent(['attribute' => $attribute]); - $this->trigger(self::EVENT_DEFINE_KEYWORDS, $event); + event($event = new DefineKeywords( + element: $this, + attribute: $attribute, + )); - if ($event->handled) { - return $event->keywords ?? ''; - } + if ($event->handled) { + return $event->keywords; } return $this->searchKeywords($attribute); diff --git a/src/Element/Events/DefineKeywords.php b/src/Element/Events/DefineKeywords.php new file mode 100644 index 00000000000..2052d4a75b8 --- /dev/null +++ b/src/Element/Events/DefineKeywords.php @@ -0,0 +1,30 @@ +getSearchKeywords('customField'))->toBe('custom-keywords-for-value'); }); - test('EVENT_DEFINE_KEYWORDS event can override keywords when handled', function () { + test('DefineKeywords event can override keywords when handled', function () { $element = new TestSearchableElement; $element->title = 'Original Title'; - Event::on( - TestSearchableElement::class, - Element::EVENT_DEFINE_KEYWORDS, - function (DefineAttributeKeywordsEvent $event) { - if ($event->attribute === 'title') { - $event->keywords = 'overridden-keywords'; - $event->handled = true; - } + Event::listen(function (DefineKeywords $event) { + if ($event->attribute === 'title') { + $event->keywords = 'overridden-keywords'; + $event->handled = true; } - ); + }); $keywords = $element->getSearchKeywords('title'); expect($keywords)->toBe('overridden-keywords'); - - Event::off(TestSearchableElement::class, Element::EVENT_DEFINE_KEYWORDS); }); - test('EVENT_DEFINE_KEYWORDS returns empty string when keywords is null and handled', function () { + test('DefineKeywords returns empty string when keywords is empty and handled', function () { $element = new TestSearchableElement; $element->title = 'Original Title'; - Event::on( - TestSearchableElement::class, - Element::EVENT_DEFINE_KEYWORDS, - function (DefineAttributeKeywordsEvent $event) { - unset($event->keywords); - $event->handled = true; - } - ); + Event::listen(function (DefineKeywords $event) { + $event->keywords = ''; + $event->handled = true; + }); $keywords = $element->getSearchKeywords('title'); expect($keywords)->toBe(''); - - Event::off(TestSearchableElement::class, Element::EVENT_DEFINE_KEYWORDS); }); - test('EVENT_DEFINE_KEYWORDS is ignored when not handled', function () { + test('DefineKeywords is ignored when not handled', function () { $element = new TestSearchableElement; $element->title = 'Original Title'; - Event::on( - TestSearchableElement::class, - Element::EVENT_DEFINE_KEYWORDS, - function (DefineAttributeKeywordsEvent $event) { - $event->keywords = 'this-should-be-ignored'; - // Note: not setting $event->handled = true - } - ); + Event::listen(function (DefineKeywords $event) { + $event->keywords = 'this-should-be-ignored'; + // Note: not setting $event->handled = true + }); $keywords = $element->getSearchKeywords('title'); expect($keywords)->toBe('Original Title'); - - Event::off(TestSearchableElement::class, Element::EVENT_DEFINE_KEYWORDS); }); test('works with real Entry element', function () { diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index ce179777283..001e46e4163 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -16,12 +16,14 @@ use craft\events\RegisterElementFieldLayoutsEvent; use craft\events\RegisterElementSourcesEvent; use craft\events\RegisterPreviewTargetsEvent; +use craft\events\RenderElementEvent; use CraftCms\Cms\Element\Events\DefineCacheTags; use CraftCms\Cms\Element\Events\RegisterActions; use CraftCms\Cms\Element\Events\RegisterExporters; use CraftCms\Cms\Element\Events\RegisterFieldLayouts; use CraftCms\Cms\Element\Events\RegisterPreviewTargets; use CraftCms\Cms\Element\Events\RegisterSources; +use CraftCms\Cms\Element\Events\Render; use Illuminate\Support\Facades\Event; /** @@ -94,6 +96,15 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_RENDER = 'render'; + /** + * @event DefineAttributeKeywordsEvent The event that is triggered when defining the search keywords for an element attribute. + * + * @see getSearchKeywords() + * @since 3.5.0 + * @deprecated 6.0.0 Use {@see DefineKeywords} instead. + */ + public const EVENT_DEFINE_KEYWORDS = 'defineKeywords'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -252,5 +263,30 @@ public static function registerEvents(): void $event->variables = $yiiEvent->variables; } }); + + Event::listen(function(DefineKeywords $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_KEYWORDS)) { + continue; + } + + $yiiEvent = new DefineAttributeKeywordsEvent([ + 'sender' => $event->element, + 'attribute' => $event->attribute, + 'keywords' => $event->keywords, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_KEYWORDS, $yiiEvent); + + if ($yiiEvent->handled) { + $event->keywords = $yiiEvent->keywords; + $event->handled = true; + } + } + }); } } From ee16f499c1de86144ef4a82e909d6909c58dad42 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:30:43 +0100 Subject: [PATCH 08/31] Convert DisplayedInIndex events to Laravel events Converts 7 Yii2 events from DisplayedInIndex trait to Laravel events: - EVENT_REGISTER_SORT_OPTIONS -> RegisterSortOptions - EVENT_REGISTER_TABLE_ATTRIBUTES -> RegisterTableAttributes - EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES -> RegisterDefaultTableAttributes - EVENT_REGISTER_CARD_ATTRIBUTES -> RegisterCardAttributes - EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES -> RegisterDefaultCardAttributes - EVENT_REGISTER_SEARCHABLE_ATTRIBUTES -> RegisterSearchableAttributes - EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE -> PrepQueryForTableAttribute Also removes EVENT_REGISTER_SEARCHABLE_ATTRIBUTES constant from Searchable.php as it was defined there but used in DisplayedInIndex. --- src/Element/Concerns/DisplayedInIndex.php | 217 ++++-------------- src/Element/Concerns/Searchable.php | 5 - .../Events/PrepQueryForTableAttribute.php | 30 +++ src/Element/Events/RegisterCardAttributes.php | 26 +++ .../Events/RegisterDefaultCardAttributes.php | 22 ++ .../Events/RegisterDefaultTableAttributes.php | 24 ++ .../Events/RegisterSearchableAttributes.php | 22 ++ src/Element/Events/RegisterSortOptions.php | 22 ++ .../Events/RegisterTableAttributes.php | 22 ++ .../Element/Concerns/DisplayedInIndexTest.php | 71 +++--- yii2-adapter/legacy/base/Element.php | 208 +++++++++++++++++ 11 files changed, 450 insertions(+), 219 deletions(-) create mode 100644 src/Element/Events/PrepQueryForTableAttribute.php create mode 100644 src/Element/Events/RegisterCardAttributes.php create mode 100644 src/Element/Events/RegisterDefaultCardAttributes.php create mode 100644 src/Element/Events/RegisterDefaultTableAttributes.php create mode 100644 src/Element/Events/RegisterSearchableAttributes.php create mode 100644 src/Element/Events/RegisterSortOptions.php create mode 100644 src/Element/Events/RegisterTableAttributes.php diff --git a/src/Element/Concerns/DisplayedInIndex.php b/src/Element/Concerns/DisplayedInIndex.php index 80232403284..096bf18f47c 100644 --- a/src/Element/Concerns/DisplayedInIndex.php +++ b/src/Element/Concerns/DisplayedInIndex.php @@ -9,18 +9,18 @@ use craft\base\NestedElementInterface; use craft\db\Connection; use craft\db\ExcludeDescendantIdsExpression; -use craft\events\ElementIndexTableAttributeEvent; -use craft\events\RegisterElementCardAttributesEvent; -use craft\events\RegisterElementDefaultCardAttributesEvent; -use craft\events\RegisterElementDefaultTableAttributesEvent; -use craft\events\RegisterElementSearchableAttributesEvent; -use craft\events\RegisterElementSortOptionsEvent; -use craft\events\RegisterElementTableAttributesEvent; use craft\helpers\ElementHelper; use craft\models\FieldLayout; use CraftCms\Cms\Auth\SessionAuth; use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementSources; +use CraftCms\Cms\Element\Events\PrepQueryForTableAttribute; +use CraftCms\Cms\Element\Events\RegisterCardAttributes; +use CraftCms\Cms\Element\Events\RegisterDefaultCardAttributes; +use CraftCms\Cms\Element\Events\RegisterDefaultTableAttributes; +use CraftCms\Cms\Element\Events\RegisterSearchableAttributes; +use CraftCms\Cms\Element\Events\RegisterSortOptions; +use CraftCms\Cms\Element\Events\RegisterTableAttributes; use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface; use CraftCms\Cms\Element\Queries\ElementQuery; use CraftCms\Cms\Support\Arr; @@ -33,7 +33,6 @@ use Illuminate\Support\Facades\DB; use Stringable; use Tpetry\QueryExpressions\Function\Conditional\Coalesce; -use yii\base\Event; use yii\db\Expression; use function CraftCms\Cms\t; @@ -48,94 +47,6 @@ */ trait DisplayedInIndex { - /** - * @event RegisterElementSortOptionsEvent The event that is triggered when registering the sort options for the element type. - */ - public const EVENT_REGISTER_SORT_OPTIONS = 'registerSortOptions'; - - /** - * @event RegisterElementTableAttributesEvent The event that is triggered when registering the table attributes for the element type. - */ - public const EVENT_REGISTER_TABLE_ATTRIBUTES = 'registerTableAttributes'; - - /** - * @event RegisterElementTableAttributesEvent The event that is triggered when registering the table attributes for the element type. - */ - public const EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES = 'registerDefaultTableAttributes'; - - /** - * @event ElementIndexTableAttributeEvent The event that is triggered when preparing an element query for an element index, for each - * attribute present in the table. - * - * Paired with [[EVENT_REGISTER_TABLE_ATTRIBUTES]] and [[EVENT_DEFINE_ATTRIBUTE_HTML]], this allows optimization of queries on element indexes. - * - * ```php - * use CraftCms\Cms\Element\Element; - * use CraftCms\Cms\Entry\Elements\Entry; - * use craft\events\DefineAttributeHtmlEvent; - * use craft\events\ElementIndexTableAttributeEvent; - * use craft\events\RegisterElementTableAttributesEvent; - * use craft\helpers\Cp; - * use yii\base\Event; - * - * Event::on( - * Entry::class, - * Element::EVENT_REGISTER_TABLE_ATTRIBUTES, - * function(RegisterElementTableAttributesEvent $e) { - * $e->tableAttributes['authorExpertise'] = ['label' => 'Author Expertise']; - * } - * ); - * - * Event::on( - * Entry::class, - * Element::EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE, - * function(ElementIndexTableAttributeEvent $e) { - * $query = $e->query; - * $attr = $e->attribute; - * - * if ($attr === 'authorExpertise') { - * $query->andWith(['author.areasOfExpertiseCategoryField']); - * } - * } - * ); - * - * Event::on( - * Entry::class, - * Element::EVENT_DEFINE_ATTRIBUTE_HTML, - * function(DefineAttributeHtmlEvent $e) { - * $attribute = $e->attribute; - * - * if ($attribute !== 'authorExpertise') { - * return; - * } - * - * // The field data is eager-loaded! - * $author = $e->sender->getAuthor(); - * $categories = $author->areasOfExpertiseCategoryField; - * - * $e->html = Cp::elementPreviewHtml($categories); - * } - * ); - * ``` - * - * @since 3.7.14 - */ - public const EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE = 'prepQueryForTableAttribute'; - - /** - * @event RegisterElementCardAttributesEvent The event that is triggered when registering the card attributes for the element type. - * - * @since 5.5.0 - */ - public const EVENT_REGISTER_CARD_ATTRIBUTES = 'registerCardAttributes'; - - /** - * @event RegisterElementCardAttributesEvent The event that is triggered when registering the card attributes for the element type. - * - * @since 5.5.0 - */ - public const EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES = 'registerDefaultCardAttributes'; - /** * @var string|null The view mode used to show this element (e.g. `structure`, `table`, `thumbs`, `cards`). * @@ -150,17 +61,12 @@ trait DisplayedInIndex */ public static function searchableAttributes(): array { - $attributes = static::defineSearchableAttributes(); + event($event = new RegisterSearchableAttributes( + elementType: static::class, + attributes: static::defineSearchableAttributes(), + )); - // Fire a 'registerSearchableAttributes' event - if (Event::hasHandlers(static::class, Element::EVENT_REGISTER_SEARCHABLE_ATTRIBUTES)) { - $event = new RegisterElementSearchableAttributesEvent(['attributes' => $attributes]); - Event::trigger(static::class, Element::EVENT_REGISTER_SEARCHABLE_ATTRIBUTES, $event); - - return $event->attributes; - } - - return $attributes; + return $event->attributes; } /** @@ -285,18 +191,15 @@ public static function indexHtml( ); // Prepare the element query for each of the table attributes - $hasHandlers = Event::hasHandlers(static::class, Element::EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE); foreach ($variables['attributes'] as $attribute) { - if ($hasHandlers) { - // Fire a 'prepQueryForTableAttribute' event - $event = new ElementIndexTableAttributeEvent([ - 'query' => $elementQuery, - 'attribute' => $attribute[0], - ]); - Event::trigger(static::class, Element::EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE, $event); - if ($event->handled) { - continue; - } + event($event = new PrepQueryForTableAttribute( + elementType: static::class, + query: $elementQuery, + attribute: $attribute[0], + )); + + if ($event->handled) { + continue; } static::prepElementQueryForTableAttribute($elementQuery, $attribute[0]); @@ -498,15 +401,12 @@ public static function sortOptions(): array ...Arr::except($sortOptions, 'id'), ]; - // Fire a 'registerSortOptions' event - if (Event::hasHandlers(static::class, Element::EVENT_REGISTER_SORT_OPTIONS)) { - $event = new RegisterElementSortOptionsEvent(['sortOptions' => $sortOptions]); - Event::trigger(static::class, Element::EVENT_REGISTER_SORT_OPTIONS, $event); + event($event = new RegisterSortOptions( + elementType: static::class, + sortOptions: $sortOptions, + )); - return $event->sortOptions; - } - - return $sortOptions; + return $event->sortOptions; } /** @@ -536,17 +436,12 @@ protected static function defineSortOptions(): array */ public static function tableAttributes(): array { - $tableAttributes = static::defineTableAttributes(); - - // Fire a 'registerTableAttributes' event - if (Event::hasHandlers(static::class, Element::EVENT_REGISTER_TABLE_ATTRIBUTES)) { - $event = new RegisterElementTableAttributesEvent(['tableAttributes' => $tableAttributes]); - Event::trigger(static::class, Element::EVENT_REGISTER_TABLE_ATTRIBUTES, $event); - - return $event->tableAttributes; - } + event($event = new RegisterTableAttributes( + elementType: static::class, + tableAttributes: static::defineTableAttributes(), + )); - return $tableAttributes; + return $event->tableAttributes; } /** @@ -588,20 +483,13 @@ protected static function defineTableAttributes(): array */ public static function defaultTableAttributes(string $source): array { - $tableAttributes = static::defineDefaultTableAttributes($source); + event($event = new RegisterDefaultTableAttributes( + elementType: static::class, + source: $source, + tableAttributes: static::defineDefaultTableAttributes($source), + )); - // Fire a 'registerDefaultTableAttributes' event - if (Event::hasHandlers(static::class, Element::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES)) { - $event = new RegisterElementDefaultTableAttributesEvent([ - 'source' => $source, - 'tableAttributes' => $tableAttributes, - ]); - Event::trigger(static::class, Element::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES, $event); - - return $event->tableAttributes; - } - - return $tableAttributes; + return $event->tableAttributes; } /** @@ -631,17 +519,13 @@ protected static function defineDefaultTableAttributes(string $source): array */ public static function cardAttributes(?FieldLayout $fieldLayout = null): array { - $cardAttributes = static::defineCardAttributes(); - - // Fire a 'registerCardAttributes' event - if (Event::hasHandlers(static::class, Element::EVENT_REGISTER_CARD_ATTRIBUTES)) { - $event = new RegisterElementCardAttributesEvent(['cardAttributes' => $cardAttributes, 'fieldLayout' => $fieldLayout]); - Event::trigger(static::class, Element::EVENT_REGISTER_CARD_ATTRIBUTES, $event); + event($event = new RegisterCardAttributes( + elementType: static::class, + cardAttributes: static::defineCardAttributes(), + fieldLayout: $fieldLayout, + )); - return $event->cardAttributes; - } - - return $cardAttributes; + return $event->cardAttributes; } /** @@ -722,19 +606,12 @@ public static function attributePreviewHtml(array $attribute): mixed */ public static function defaultCardAttributes(): array { - $cardAttributes = static::defineDefaultCardAttributes(); - - // Fire a 'registerDefaultCardAttributes' event - if (Event::hasHandlers(static::class, Element::EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES)) { - $event = new RegisterElementDefaultCardAttributesEvent([ - 'cardAttributes' => $cardAttributes, - ]); - Event::trigger(static::class, Element::EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES, $event); - - return $event->cardAttributes; - } + event($event = new RegisterDefaultCardAttributes( + elementType: static::class, + cardAttributes: static::defineDefaultCardAttributes(), + )); - return $cardAttributes; + return $event->cardAttributes; } /** diff --git a/src/Element/Concerns/Searchable.php b/src/Element/Concerns/Searchable.php index 74a0f5ba71e..4fe5a80399e 100644 --- a/src/Element/Concerns/Searchable.php +++ b/src/Element/Concerns/Searchable.php @@ -19,11 +19,6 @@ */ trait Searchable { - /** - * @event RegisterElementSearchableAttributesEvent The event that is triggered when registering the searchable attributes for the element type. - */ - public const EVENT_REGISTER_SEARCHABLE_ATTRIBUTES = 'registerSearchableAttributes'; - /** * @var int|null The element's search score, if the [[\craft\elements\db\ElementQuery::search]] parameter was used when querying for the element */ diff --git a/src/Element/Events/PrepQueryForTableAttribute.php b/src/Element/Events/PrepQueryForTableAttribute.php new file mode 100644 index 00000000000..b55b2732b02 --- /dev/null +++ b/src/Element/Events/PrepQueryForTableAttribute.php @@ -0,0 +1,30 @@ + $elementType The element type class + * @param ElementQueryInterface $query The element query + * @param string $attribute The attribute name + * @param bool $handled Whether the event has been handled + */ + public function __construct( + public string $elementType, + public ElementQueryInterface $query, + public string $attribute, + public bool $handled = false, + ) {} +} diff --git a/src/Element/Events/RegisterCardAttributes.php b/src/Element/Events/RegisterCardAttributes.php new file mode 100644 index 00000000000..a2d78b8b3d5 --- /dev/null +++ b/src/Element/Events/RegisterCardAttributes.php @@ -0,0 +1,26 @@ + $elementType The element type class + * @param array $cardAttributes The card attributes + * @param FieldLayout|null $fieldLayout The field layout + */ + public function __construct( + public string $elementType, + public array $cardAttributes = [], + public ?FieldLayout $fieldLayout = null, + ) {} +} diff --git a/src/Element/Events/RegisterDefaultCardAttributes.php b/src/Element/Events/RegisterDefaultCardAttributes.php new file mode 100644 index 00000000000..7cd179297da --- /dev/null +++ b/src/Element/Events/RegisterDefaultCardAttributes.php @@ -0,0 +1,22 @@ + $elementType The element type class + * @param array $cardAttributes The default card attribute keys + */ + public function __construct( + public string $elementType, + public array $cardAttributes = [], + ) {} +} diff --git a/src/Element/Events/RegisterDefaultTableAttributes.php b/src/Element/Events/RegisterDefaultTableAttributes.php new file mode 100644 index 00000000000..ac0068dc1c0 --- /dev/null +++ b/src/Element/Events/RegisterDefaultTableAttributes.php @@ -0,0 +1,24 @@ + $elementType The element type class + * @param string $source The source key + * @param array $tableAttributes The default table attribute keys + */ + public function __construct( + public string $elementType, + public string $source, + public array $tableAttributes = [], + ) {} +} diff --git a/src/Element/Events/RegisterSearchableAttributes.php b/src/Element/Events/RegisterSearchableAttributes.php new file mode 100644 index 00000000000..1230d35e55e --- /dev/null +++ b/src/Element/Events/RegisterSearchableAttributes.php @@ -0,0 +1,22 @@ + $elementType The element type class + * @param array $attributes The searchable attributes + */ + public function __construct( + public string $elementType, + public array $attributes = [], + ) {} +} diff --git a/src/Element/Events/RegisterSortOptions.php b/src/Element/Events/RegisterSortOptions.php new file mode 100644 index 00000000000..6d8178f1003 --- /dev/null +++ b/src/Element/Events/RegisterSortOptions.php @@ -0,0 +1,22 @@ + $elementType The element type class + * @param array $sortOptions The sort options + */ + public function __construct( + public string $elementType, + public array $sortOptions = [], + ) {} +} diff --git a/src/Element/Events/RegisterTableAttributes.php b/src/Element/Events/RegisterTableAttributes.php new file mode 100644 index 00000000000..aea8d1bbc8e --- /dev/null +++ b/src/Element/Events/RegisterTableAttributes.php @@ -0,0 +1,22 @@ + $elementType The element type class + * @param array $tableAttributes The table attributes + */ + public function __construct( + public string $elementType, + public array $tableAttributes = [], + ) {} +} diff --git a/tests/Element/Concerns/DisplayedInIndexTest.php b/tests/Element/Concerns/DisplayedInIndexTest.php index c7a7921cd01..2d4fda09fba 100644 --- a/tests/Element/Concerns/DisplayedInIndexTest.php +++ b/tests/Element/Concerns/DisplayedInIndexTest.php @@ -2,15 +2,14 @@ declare(strict_types=1); -use craft\events\RegisterElementDefaultCardAttributesEvent; -use craft\events\RegisterElementDefaultTableAttributesEvent; -use craft\events\RegisterElementSortOptionsEvent; -use craft\events\RegisterElementTableAttributesEvent; -use CraftCms\Cms\Element\Element; +use CraftCms\Cms\Element\Events\RegisterDefaultCardAttributes; +use CraftCms\Cms\Element\Events\RegisterDefaultTableAttributes; +use CraftCms\Cms\Element\Events\RegisterSortOptions; +use CraftCms\Cms\Element\Events\RegisterTableAttributes; use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface; use CraftCms\Cms\Entry\Elements\Entry; use CraftCms\Cms\Entry\Models\Entry as EntryModel; -use yii\base\Event; +use Illuminate\Support\Facades\Event; /** * Test Entry class that exposes protected methods from DisplayedInIndex trait @@ -559,82 +558,66 @@ public static function hasStatuses(): bool $eventTriggered = false; $capturedAttributes = null; - Event::on( - Entry::class, - Element::EVENT_REGISTER_TABLE_ATTRIBUTES, - function (RegisterElementTableAttributesEvent $event) use (&$eventTriggered, &$capturedAttributes) { - $eventTriggered = true; - $capturedAttributes = $event->tableAttributes; + Event::listen(function (RegisterTableAttributes $event) use (&$eventTriggered, &$capturedAttributes) { + if ($event->elementType !== Entry::class) { + return; } - ); + $eventTriggered = true; + $capturedAttributes = $event->tableAttributes; + }); $attributes = Entry::tableAttributes(); expect($eventTriggered)->toBeTrue(); expect($capturedAttributes)->toBeArray(); expect($capturedAttributes)->toEqual($attributes); - - // Clean up - Event::off(Entry::class, Element::EVENT_REGISTER_TABLE_ATTRIBUTES); }); test('registerSortOptions event is triggered', function () { $eventTriggered = false; - Event::on( - Entry::class, - Element::EVENT_REGISTER_SORT_OPTIONS, - function (RegisterElementSortOptionsEvent $event) use (&$eventTriggered) { - $eventTriggered = true; + Event::listen(function (RegisterSortOptions $event) use (&$eventTriggered) { + if ($event->elementType !== Entry::class) { + return; } - ); + $eventTriggered = true; + }); Entry::sortOptions(); expect($eventTriggered)->toBeTrue(); - - // Clean up - Event::off(Entry::class, Element::EVENT_REGISTER_SORT_OPTIONS); }); test('registerDefaultTableAttributes event is triggered', function () { $eventTriggered = false; $capturedSource = null; - Event::on( - Entry::class, - Element::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES, - function (RegisterElementDefaultTableAttributesEvent $event) use (&$eventTriggered, &$capturedSource) { - $eventTriggered = true; - $capturedSource = $event->source; + Event::listen(function (RegisterDefaultTableAttributes $event) use (&$eventTriggered, &$capturedSource) { + if ($event->elementType !== Entry::class) { + return; } - ); + $eventTriggered = true; + $capturedSource = $event->source; + }); Entry::defaultTableAttributes('*'); expect($eventTriggered)->toBeTrue(); expect($capturedSource)->toBe('*'); - - // Clean up - Event::off(Entry::class, Element::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES); }); test('registerDefaultCardAttributes event is triggered', function () { $eventTriggered = false; - Event::on( - Entry::class, - Element::EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES, - function (RegisterElementDefaultCardAttributesEvent $event) use (&$eventTriggered) { - $eventTriggered = true; + Event::listen(function (RegisterDefaultCardAttributes $event) use (&$eventTriggered) { + if ($event->elementType !== Entry::class) { + return; } - ); + $eventTriggered = true; + }); Entry::defaultCardAttributes(); expect($eventTriggered)->toBeTrue(); - - // Clean up - Event::off(Entry::class, Element::EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES); }); }); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 001e46e4163..c0b221ae73c 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -105,6 +105,69 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_DEFINE_KEYWORDS = 'defineKeywords'; + /** + * @event RegisterElementSortOptionsEvent The event that is triggered when registering the sort options for the element type. + * + * @see sortOptions() + * @since 3.0.0 + * @deprecated 6.0.0 Use {@see RegisterSortOptions} instead. + */ + public const EVENT_REGISTER_SORT_OPTIONS = 'registerSortOptions'; + + /** + * @event RegisterElementTableAttributesEvent The event that is triggered when registering the table attributes for the element type. + * + * @see tableAttributes() + * @since 3.0.0 + * @deprecated 6.0.0 Use {@see RegisterTableAttributes} instead. + */ + public const EVENT_REGISTER_TABLE_ATTRIBUTES = 'registerTableAttributes'; + + /** + * @event RegisterElementDefaultTableAttributesEvent The event that is triggered when registering the default table attributes for the element type. + * + * @see defaultTableAttributes() + * @since 3.0.0 + * @deprecated 6.0.0 Use {@see RegisterDefaultTableAttributes} instead. + */ + public const EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES = 'registerDefaultTableAttributes'; + + /** + * @event RegisterElementCardAttributesEvent The event that is triggered when registering the card attributes for the element type. + * + * @see cardAttributes() + * @since 5.5.0 + * @deprecated 6.0.0 Use {@see RegisterCardAttributes} instead. + */ + public const EVENT_REGISTER_CARD_ATTRIBUTES = 'registerCardAttributes'; + + /** + * @event RegisterElementDefaultCardAttributesEvent The event that is triggered when registering the default card attributes for the element type. + * + * @see defaultCardAttributes() + * @since 5.5.0 + * @deprecated 6.0.0 Use {@see RegisterDefaultCardAttributes} instead. + */ + public const EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES = 'registerDefaultCardAttributes'; + + /** + * @event RegisterElementSearchableAttributesEvent The event that is triggered when registering the searchable attributes for the element type. + * + * @see searchableAttributes() + * @since 3.0.0 + * @deprecated 6.0.0 Use {@see RegisterSearchableAttributes} instead. + */ + public const EVENT_REGISTER_SEARCHABLE_ATTRIBUTES = 'registerSearchableAttributes'; + + /** + * @event ElementIndexTableAttributeEvent The event that is triggered when preparing an element query for a table attribute. + * + * @see indexHtml() + * @since 3.7.14 + * @deprecated 6.0.0 Use {@see PrepQueryForTableAttribute} instead. + */ + public const EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE = 'prepQueryForTableAttribute'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -288,5 +351,150 @@ public static function registerEvents(): void } } }); + + Event::listen(function(RegisterSortOptions $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_SORT_OPTIONS)) { + continue; + } + + $yiiEvent = new RegisterElementSortOptionsEvent([ + 'sortOptions' => $event->sortOptions, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_SORT_OPTIONS, $yiiEvent); + + $event->sortOptions = $yiiEvent->sortOptions; + } + }); + + Event::listen(function(RegisterTableAttributes $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_TABLE_ATTRIBUTES)) { + continue; + } + + $yiiEvent = new RegisterElementTableAttributesEvent([ + 'tableAttributes' => $event->tableAttributes, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_TABLE_ATTRIBUTES, $yiiEvent); + + $event->tableAttributes = $yiiEvent->tableAttributes; + } + }); + + Event::listen(function(RegisterDefaultTableAttributes $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES)) { + continue; + } + + $yiiEvent = new RegisterElementDefaultTableAttributesEvent([ + 'source' => $event->source, + 'tableAttributes' => $event->tableAttributes, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES, $yiiEvent); + + $event->tableAttributes = $yiiEvent->tableAttributes; + } + }); + + Event::listen(function(RegisterCardAttributes $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_CARD_ATTRIBUTES)) { + continue; + } + + $yiiEvent = new RegisterElementCardAttributesEvent([ + 'cardAttributes' => $event->cardAttributes, + 'fieldLayout' => $event->fieldLayout, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_CARD_ATTRIBUTES, $yiiEvent); + + $event->cardAttributes = $yiiEvent->cardAttributes; + } + }); + + Event::listen(function(RegisterDefaultCardAttributes $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES)) { + continue; + } + + $yiiEvent = new RegisterElementDefaultCardAttributesEvent([ + 'cardAttributes' => $event->cardAttributes, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES, $yiiEvent); + + $event->cardAttributes = $yiiEvent->cardAttributes; + } + }); + + Event::listen(function(RegisterSearchableAttributes $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_SEARCHABLE_ATTRIBUTES)) { + continue; + } + + $yiiEvent = new RegisterElementSearchableAttributesEvent([ + 'attributes' => $event->attributes, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_SEARCHABLE_ATTRIBUTES, $yiiEvent); + + $event->attributes = $yiiEvent->attributes; + } + }); + + Event::listen(function(PrepQueryForTableAttribute $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE)) { + continue; + } + + $yiiEvent = new ElementIndexTableAttributeEvent([ + 'query' => $event->query, + 'attribute' => $event->attribute, + ]); + + YiiEvent::trigger($class, self::EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE, $yiiEvent); + + if ($yiiEvent->handled) { + $event->handled = true; + } + } + }); } } From 22f5cf0bfefa75f48107bd786e9ed163ace88bb9 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:34:46 +0100 Subject: [PATCH 09/31] Convert Eagerloadable events to Laravel events Converts 2 Yii2 events from Eagerloadable trait to Laravel events: - EVENT_DEFINE_EAGER_LOADING_MAP -> DefineEagerLoadingMap - EVENT_SET_EAGER_LOADED_ELEMENTS -> SetEagerLoadedElements --- src/Element/Concerns/Eagerloadable.php | 93 +++++-------------- src/Element/Events/DefineEagerLoadingMap.php | 34 +++++++ src/Element/Events/SetEagerLoadedElements.php | 34 +++++++ yii2-adapter/legacy/base/Element.php | 82 ++++++++++++++++ 4 files changed, 173 insertions(+), 70 deletions(-) create mode 100644 src/Element/Events/DefineEagerLoadingMap.php create mode 100644 src/Element/Events/SetEagerLoadedElements.php diff --git a/src/Element/Concerns/Eagerloadable.php b/src/Element/Concerns/Eagerloadable.php index fca0de04184..a5edcc0a2a9 100644 --- a/src/Element/Concerns/Eagerloadable.php +++ b/src/Element/Concerns/Eagerloadable.php @@ -7,19 +7,18 @@ use craft\base\ElementInterface; use craft\elements\db\EagerLoadInfo; use craft\elements\db\EagerLoadPlan; -use craft\events\DefineEagerLoadingMapEvent; -use craft\events\SetEagerLoadedElementsEvent; use craft\helpers\ElementHelper; use CraftCms\Cms\Database\Table; use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementCollection; +use CraftCms\Cms\Element\Events\DefineEagerLoadingMap; +use CraftCms\Cms\Element\Events\SetEagerLoadedElements; use CraftCms\Cms\Field\Contracts\EagerLoadingFieldInterface; use CraftCms\Cms\Support\Facades\Sites; use CraftCms\Cms\User\Elements\User; use Illuminate\Database\Query\Builder; use Illuminate\Support\Facades\DB; use Tpetry\QueryExpressions\Language\Alias; -use yii\base\Event; use yii\base\InvalidConfigException; /** @@ -33,50 +32,6 @@ */ trait Eagerloadable { - /** - * @event DefineEagerLoadingMapEvent The event that is triggered when defining an eager-loading map. - * - * ```php - * use CraftCms\Cms\Element\Element; - * use craft\base\ElementInterface; - * use craft\db\Query; - * use CraftCms\Cms\Entry\Elements\Entry; - * use craft\events\DefineEagerLoadingMapEvent; - * use yii\base\Event; - * - * // Add support for `with(['bookClub'])` to entries - * Event::on( - * Entry::class, - * Element::EVENT_DEFINE_EAGER_LOADING_MAP, - * function(DefineEagerLoadingMapEvent $event) { - * if ($event->handle === 'bookClub') { - * $bookEntryIds = array_map(fn(ElementInterface $element) => $element->id, $event->elements); - * $event->elementType = \my\plugin\BookClub::class, - * $event->map = (new Query) - * ->select(['source' => 'bookId', 'target' => 'clubId']) - * ->from('{{%bookclub_books}}') - * ->where(['bookId' => $bookEntryIds]) - * ->all(); - * $event->handled = true; - * } - * } - * ); - * ``` - * - * @since 3.1.0 - */ - public const EVENT_DEFINE_EAGER_LOADING_MAP = 'defineEagerLoadingMap'; - - /** - * @event SetEagerLoadedElementsEvent The event that is triggered when setting eager-loaded elements. - * - * Set [[Event::$handled]] to `true` to prevent the elements from getting stored to the private - * `$_eagerLoadedElements` array. - * - * @since 3.5.0 - */ - public const EVENT_SET_EAGER_LOADED_ELEMENTS = 'setEagerLoadedElements'; - /** * @var ElementInterface[]|null All elements that the element was queried with. * @@ -205,19 +160,18 @@ public static function eagerLoadingMap(array $sourceElements, string $handle): a } // Fire a 'defineEagerLoadingMap' event - if (Event::hasHandlers(static::class, Element::EVENT_DEFINE_EAGER_LOADING_MAP)) { - $event = new DefineEagerLoadingMapEvent([ - 'sourceElements' => $sourceElements, - 'handle' => $handle, - ]); - Event::trigger(static::class, Element::EVENT_DEFINE_EAGER_LOADING_MAP, $event); - if ($event->elementType !== null) { - return [ - 'elementType' => $event->elementType, - 'map' => $event->map, - 'criteria' => $event->criteria, - ]; - } + event($event = new DefineEagerLoadingMap( + elementType: static::class, + sourceElements: $sourceElements, + handle: $handle, + )); + + if ($event->targetElementType !== null) { + return [ + 'elementType' => $event->targetElementType, + 'map' => $event->map, + 'criteria' => $event->criteria, + ]; } // return null so eager-loading is ignored for this handle @@ -651,16 +605,15 @@ public function setEagerLoadedElements(string $handle, array $elements, EagerLoa break; default: // Fire a 'setEagerLoadedElements' event - if ($this->hasEventHandlers(Element::EVENT_SET_EAGER_LOADED_ELEMENTS)) { - $event = new SetEagerLoadedElementsEvent([ - 'handle' => $handle, - 'elements' => $elements, - 'plan' => $plan, - ]); - $this->trigger(Element::EVENT_SET_EAGER_LOADED_ELEMENTS, $event); - if ($event->handled) { - break; - } + event($event = new SetEagerLoadedElements( + element: $this, + handle: $handle, + elements: $elements, + plan: $plan, + )); + + if ($event->handled) { + break; } // No takers. Just store it in the internal array then. diff --git a/src/Element/Events/DefineEagerLoadingMap.php b/src/Element/Events/DefineEagerLoadingMap.php new file mode 100644 index 00000000000..21b25615e78 --- /dev/null +++ b/src/Element/Events/DefineEagerLoadingMap.php @@ -0,0 +1,34 @@ + $elementType The element type class being queried + * @param ElementInterface[] $sourceElements An array of the source elements + * @param string $handle The property handle used to identify which target elements should be included in the map + * @param class-string|null $targetElementType The element type class to eager-load + * @param array|null $map An array of element ID mappings, where each element is a sub-array with `source` and `target` keys + * @param array|null $criteria Any criteria parameters that should be applied to the element query when fetching the eager-loaded elements + */ + public function __construct( + public string $elementType, + public array $sourceElements, + public string $handle, + public ?string $targetElementType = null, + public ?array $map = null, + public ?array $criteria = null, + ) {} +} diff --git a/src/Element/Events/SetEagerLoadedElements.php b/src/Element/Events/SetEagerLoadedElements.php new file mode 100644 index 00000000000..2730c3fdd49 --- /dev/null +++ b/src/Element/Events/SetEagerLoadedElements.php @@ -0,0 +1,34 @@ +elementType && !is_subclass_of($event->elementType, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_EAGER_LOADING_MAP)) { + continue; + } + + $yiiEvent = new DefineEagerLoadingMapEvent([ + 'sourceElements' => $event->sourceElements, + 'handle' => $event->handle, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_EAGER_LOADING_MAP, $yiiEvent); + + if ($yiiEvent->elementType !== null) { + $event->targetElementType = $yiiEvent->elementType; + $event->map = $yiiEvent->map; + $event->criteria = $yiiEvent->criteria; + } + } + }); + + Event::listen(function(SetEagerLoadedElements $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_SET_EAGER_LOADED_ELEMENTS)) { + continue; + } + + $yiiEvent = new SetEagerLoadedElementsEvent([ + 'sender' => $event->element, + 'handle' => $event->handle, + 'elements' => $event->elements, + 'plan' => $event->plan, + ]); + + YiiEvent::trigger($class, self::EVENT_SET_EAGER_LOADED_ELEMENTS, $yiiEvent); + + if ($yiiEvent->handled) { + $event->handled = true; + } + } + }); } } From f86a4b97eb0e051b6e0774cdc323468a7cb711b4 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:43:12 +0100 Subject: [PATCH 10/31] Convert EVENT_BEFORE_SAVE to Laravel event with backwards compatibility - Create BeforeSave event class in src/Element/Events/ - Update HasLifecycleHooks::beforeSave() to dispatch Laravel event - Move EVENT_BEFORE_SAVE constant to yii2-adapter for backwards compat - Add listener to bridge Laravel event to legacy Yii2 handlers - Update tests to use Laravel event pattern --- src/Element/Concerns/HasLifecycleHooks.php | 40 ++------------- src/Element/Events/BeforeSave.php | 25 ++++++++++ .../Concerns/HasLifecycleHooksTest.php | 50 +++++++++++++++---- yii2-adapter/legacy/base/Element.php | 33 ++++++++++++ 4 files changed, 101 insertions(+), 47 deletions(-) create mode 100644 src/Element/Events/BeforeSave.php diff --git a/src/Element/Concerns/HasLifecycleHooks.php b/src/Element/Concerns/HasLifecycleHooks.php index 29beb4b6c94..3e91fcabaa3 100644 --- a/src/Element/Concerns/HasLifecycleHooks.php +++ b/src/Element/Concerns/HasLifecycleHooks.php @@ -7,6 +7,7 @@ use craft\events\ModelEvent; use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementRelations; +use CraftCms\Cms\Element\Events\BeforeSave; /** * HasLifecycleHooks provides the lifecycle hooks for the element. @@ -17,35 +18,6 @@ */ trait HasLifecycleHooks { - /** - * @event ModelEvent The event that is triggered before the element is saved. - * - * You may set [[\yii\base\ModelEvent::$isValid]] to `false` to prevent the element from getting saved. - * - * If you want to ignore events for drafts or revisions, call [[\craft\helpers\ElementHelper::isDraftOrRevision()]] - * from your event handler: - * - * ```php - * use CraftCms\Cms\Element\Element; - * use CraftCms\Cms\Entry\Elements\Entry; - * use craft\events\ModelEvent; - * use craft\helpers\ElementHelper; - * use yii\base\Event; - * - * Event::on(Entry::class, Element::EVENT_BEFORE_SAVE, function(ModelEvent $e) { - * // @var Entry $entry - * $entry = $e->sender; - * - * if (ElementHelper::isDraftOrRevision($entry)) { - * return; - * } - * - * // ... - * }); - * ``` - */ - public const EVENT_BEFORE_SAVE = 'beforeSave'; - /** * @event ModelEvent The event that is triggered after the element is saved. * @@ -142,15 +114,9 @@ public function beforeSave(bool $isNew): bool return false; } - // Fire a 'beforeSave' event - if ($this->hasEventHandlers(Element::EVENT_BEFORE_SAVE)) { - $event = new ModelEvent(['isNew' => $isNew]); - $this->trigger(Element::EVENT_BEFORE_SAVE, $event); + event($event = new BeforeSave($this, $isNew)); - return $event->isValid; - } - - return true; + return $event->isValid; } /** diff --git a/src/Element/Events/BeforeSave.php b/src/Element/Events/BeforeSave.php new file mode 100644 index 00000000000..e51b71f3ecf --- /dev/null +++ b/src/Element/Events/BeforeSave.php @@ -0,0 +1,25 @@ + $this->entry->beforeSave(false)); + $triggered = false; + Event::listen(function (BeforeSave $event) use (&$triggered) { + $triggered = true; + }); + + $this->entry->beforeSave(false); + + expect($triggered)->toBeTrue(); +}); + +test('beforeSave event can prevent save', function () { + Event::listen(function (BeforeSave $event) { + $event->isValid = false; + }); + + $result = $this->entry->beforeSave(false); + + expect($result)->toBeFalse(); +}); + +test('beforeSave event receives isNew parameter', function () { + $receivedIsNew = null; + Event::listen(function (BeforeSave $event) use (&$receivedIsNew) { + $receivedIsNew = $event->isNew; + }); + + $this->entry->beforeSave(true); + + expect($receivedIsNew)->toBeTrue(); }); test('afterSave triggers event', function () { - expectEvent(Entry::class, Element::EVENT_AFTER_SAVE, fn () => $this->entry->afterSave(false)); + expectYiiEvent(Entry::class, Element::EVENT_AFTER_SAVE, fn () => $this->entry->afterSave(false)); }); test('afterPropagate triggers event', function () { - expectEvent(Entry::class, Element::EVENT_AFTER_PROPAGATE, fn () => $this->entry->afterPropagate(false)); + expectYiiEvent(Entry::class, Element::EVENT_AFTER_PROPAGATE, fn () => $this->entry->afterPropagate(false)); }); test('beforeDelete triggers event', function () { - expectEvent(Entry::class, Element::EVENT_BEFORE_DELETE, fn () => $this->entry->beforeDelete()); + expectYiiEvent(Entry::class, Element::EVENT_BEFORE_DELETE, fn () => $this->entry->beforeDelete()); }); test('afterDelete triggers event', function () { - expectEvent(Entry::class, Element::EVENT_AFTER_DELETE, fn () => $this->entry->afterDelete()); + expectYiiEvent(Entry::class, Element::EVENT_AFTER_DELETE, fn () => $this->entry->afterDelete()); }); test('beforeRestore triggers event', function () { - expectEvent(Entry::class, Element::EVENT_BEFORE_RESTORE, fn () => $this->entry->beforeRestore()); + expectYiiEvent(Entry::class, Element::EVENT_BEFORE_RESTORE, fn () => $this->entry->beforeRestore()); }); test('afterRestore triggers event', function () { - expectEvent(Entry::class, Element::EVENT_AFTER_RESTORE, fn () => $this->entry->afterRestore()); + expectYiiEvent(Entry::class, Element::EVENT_AFTER_RESTORE, fn () => $this->entry->afterRestore()); }); -function expectEvent(string $class, string $eventName, callable $action): void +function expectYiiEvent(string $class, string $eventName, callable $action): void { $triggered = false; - Event::on($class, $eventName, function () use (&$triggered) { + YiiEvent::on($class, $eventName, function () use (&$triggered) { $triggered = true; }); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index f8aa7107fbf..4098e7cc1dd 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -200,6 +200,16 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_SET_EAGER_LOADED_ELEMENTS = 'setEagerLoadedElements'; + /** + * @event ModelEvent The event that is triggered before the element is saved. + * + * You may set [[\yii\base\ModelEvent::$isValid]] to `false` to prevent the element from getting saved. + * + * @see beforeSave() + * @deprecated 6.0.0 Use {@see BeforeSave} instead. + */ + public const EVENT_BEFORE_SAVE = 'beforeSave'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -578,5 +588,28 @@ public static function registerEvents(): void } } }); + + Event::listen(function(BeforeSave $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_SAVE)) { + continue; + } + + $yiiEvent = new ModelEvent([ + 'sender' => $event->element, + 'isNew' => $event->isNew, + ]); + + YiiEvent::trigger($class, self::EVENT_BEFORE_SAVE, $yiiEvent); + + if (!$yiiEvent->isValid) { + $event->isValid = false; + } + } + }); } } From 06b0827c752d1aa94ec08193865d11c761db9cf8 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:46:28 +0100 Subject: [PATCH 11/31] Convert EVENT_AFTER_SAVE to Laravel event with backwards compatibility - Create AfterSave event class in src/Element/Events/ - Update HasLifecycleHooks::afterSave() to dispatch Laravel event - Move EVENT_AFTER_SAVE constant to yii2-adapter for backwards compat - Add listener to bridge Laravel event to legacy Yii2 handlers - Update tests to use Laravel event pattern --- src/Element/Concerns/HasLifecycleHooks.php | 37 ++----------------- src/Element/Events/AfterSave.php | 21 +++++++++++ .../Concerns/HasLifecycleHooksTest.php | 21 ++++++++++- yii2-adapter/legacy/base/Element.php | 28 ++++++++++++++ 4 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 src/Element/Events/AfterSave.php diff --git a/src/Element/Concerns/HasLifecycleHooks.php b/src/Element/Concerns/HasLifecycleHooks.php index 3e91fcabaa3..8de7bd361d5 100644 --- a/src/Element/Concerns/HasLifecycleHooks.php +++ b/src/Element/Concerns/HasLifecycleHooks.php @@ -7,6 +7,7 @@ use craft\events\ModelEvent; use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementRelations; +use CraftCms\Cms\Element\Events\AfterSave; use CraftCms\Cms\Element\Events\BeforeSave; /** @@ -18,33 +19,6 @@ */ trait HasLifecycleHooks { - /** - * @event ModelEvent The event that is triggered after the element is saved. - * - * If you want to ignore events for drafts or revisions, call [[\craft\helpers\ElementHelper::isDraftOrRevision()]] - * from your event handler: - * - * ```php - * use CraftCms\Cms\Element\Element; - * use CraftCms\Cms\Entry\Elements\Entry; - * use craft\events\ModelEvent; - * use craft\helpers\ElementHelper; - * use yii\base\Event; - * - * Event::on(Entry::class, Element::EVENT_AFTER_SAVE, function(ModelEvent $e) { - * // @var Entry $entry - * $entry = $e->sender; - * - * if (ElementHelper::isDraftOrRevision($entry)) { - * return; - * } - * - * // ... - * }); - * ``` - */ - public const EVENT_AFTER_SAVE = 'afterSave'; - /** * @event ModelEvent The event that is triggered after the element is fully saved and propagated to other sites. * @@ -126,7 +100,7 @@ public function beforeSave(bool $isNew): bool */ public function afterSave(bool $isNew): void { - // Update the element’s relation data + // Update the element's relation data app(ElementRelations::class)->updateRelations($this, $isNew); // Tell the fields about it @@ -134,12 +108,7 @@ public function afterSave(bool $isNew): void $field->afterElementSave($this, $isNew); } - // Fire an 'afterSave' event - if ($this->hasEventHandlers(Element::EVENT_AFTER_SAVE)) { - $this->trigger(Element::EVENT_AFTER_SAVE, new ModelEvent([ - 'isNew' => $isNew, - ])); - } + event(new AfterSave($this, $isNew)); } /** diff --git a/src/Element/Events/AfterSave.php b/src/Element/Events/AfterSave.php new file mode 100644 index 00000000000..a21058b355a --- /dev/null +++ b/src/Element/Events/AfterSave.php @@ -0,0 +1,21 @@ + $this->entry->afterSave(false)); + $triggered = false; + Event::listen(function (AfterSave $event) use (&$triggered) { + $triggered = true; + }); + + $this->entry->afterSave(false); + + expect($triggered)->toBeTrue(); +}); + +test('afterSave event receives isNew parameter', function () { + $receivedIsNew = null; + Event::listen(function (AfterSave $event) use (&$receivedIsNew) { + $receivedIsNew = $event->isNew; + }); + + $this->entry->afterSave(false); + + expect($receivedIsNew)->toBeFalse(); }); test('afterPropagate triggers event', function () { diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 4098e7cc1dd..f704991c851 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -24,6 +24,7 @@ use craft\events\RegisterElementTableAttributesEvent; use craft\events\RegisterPreviewTargetsEvent; use craft\events\RenderElementEvent; +use CraftCms\Cms\Element\Events\BeforeSave; use CraftCms\Cms\Element\Events\DefineCacheTags; use CraftCms\Cms\Element\Events\PrepQueryForTableAttribute; use CraftCms\Cms\Element\Events\RegisterActions; @@ -210,6 +211,14 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_BEFORE_SAVE = 'beforeSave'; + /** + * @event ModelEvent The event that is triggered after the element is saved. + * + * @see afterSave() + * @deprecated 6.0.0 Use {@see AfterSave} instead. + */ + public const EVENT_AFTER_SAVE = 'afterSave'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -611,5 +620,24 @@ public static function registerEvents(): void } } }); + + Event::listen(function(AfterSave $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_SAVE)) { + continue; + } + + $yiiEvent = new ModelEvent([ + 'sender' => $event->element, + 'isNew' => $event->isNew, + ]); + + YiiEvent::trigger($class, self::EVENT_AFTER_SAVE, $yiiEvent); + } + }); } } From 5b1615f592b46be919a0168fa6f95e26c47a483b Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:49:01 +0100 Subject: [PATCH 12/31] Convert EVENT_AFTER_PROPAGATE to Laravel event with backwards compatibility - Create AfterPropagate event class in src/Element/Events/ - Update HasLifecycleHooks::afterPropagate() to dispatch Laravel event - Move EVENT_AFTER_PROPAGATE constant to yii2-adapter for backwards compat - Add listener to bridge Laravel event to legacy Yii2 handlers - Update tests to use Laravel event pattern --- src/Element/Concerns/HasLifecycleHooks.php | 37 +------------------ src/Element/Events/AfterPropagate.php | 21 +++++++++++ .../Concerns/HasLifecycleHooksTest.php | 21 ++++++++++- yii2-adapter/legacy/base/Element.php | 29 +++++++++++++++ 4 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 src/Element/Events/AfterPropagate.php diff --git a/src/Element/Concerns/HasLifecycleHooks.php b/src/Element/Concerns/HasLifecycleHooks.php index 8de7bd361d5..fb548e5e5da 100644 --- a/src/Element/Concerns/HasLifecycleHooks.php +++ b/src/Element/Concerns/HasLifecycleHooks.php @@ -7,6 +7,7 @@ use craft\events\ModelEvent; use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementRelations; +use CraftCms\Cms\Element\Events\AfterPropagate; use CraftCms\Cms\Element\Events\AfterSave; use CraftCms\Cms\Element\Events\BeforeSave; @@ -19,35 +20,6 @@ */ trait HasLifecycleHooks { - /** - * @event ModelEvent The event that is triggered after the element is fully saved and propagated to other sites. - * - * If you want to ignore events for drafts or revisions, call [[\craft\helpers\ElementHelper::isDraftOrRevision()]] - * from your event handler: - * - * ```php - * use CraftCms\Cms\Element\Element; - * use CraftCms\Cms\Entry\Elements\Entry; - * use craft\events\ModelEvent; - * use craft\helpers\ElementHelper; - * use yii\base\Event; - * - * Event::on(Entry::class, Element::EVENT_AFTER_PROPAGATE, function(ModelEvent $e) { - * // @var Entry $entry - * $entry = $e->sender; - * - * if (ElementHelper::isDraftOrRevision($entry) { - * return; - * } - * - * // ... - * }); - * ``` - * - * @since 3.2.0 - */ - public const EVENT_AFTER_PROPAGATE = 'afterPropagate'; - /** * @event ModelEvent The event that is triggered before the element is deleted. * @@ -123,12 +95,7 @@ public function afterPropagate(bool $isNew): void $field->afterElementPropagate($this, $isNew); } - // Fire an 'afterPropagate' event - if ($this->hasEventHandlers(Element::EVENT_AFTER_PROPAGATE)) { - $this->trigger(Element::EVENT_AFTER_PROPAGATE, new ModelEvent([ - 'isNew' => $isNew, - ])); - } + event(new AfterPropagate($this, $isNew)); $this->handleDraftSave(); } diff --git a/src/Element/Events/AfterPropagate.php b/src/Element/Events/AfterPropagate.php new file mode 100644 index 00000000000..2fa57192bb6 --- /dev/null +++ b/src/Element/Events/AfterPropagate.php @@ -0,0 +1,21 @@ + $this->entry->afterPropagate(false)); + $triggered = false; + Event::listen(function (AfterPropagate $event) use (&$triggered) { + $triggered = true; + }); + + $this->entry->afterPropagate(false); + + expect($triggered)->toBeTrue(); +}); + +test('afterPropagate event receives isNew parameter', function () { + $receivedIsNew = null; + Event::listen(function (AfterPropagate $event) use (&$receivedIsNew) { + $receivedIsNew = $event->isNew; + }); + + $this->entry->afterPropagate(false); + + expect($receivedIsNew)->toBeFalse(); }); test('beforeDelete triggers event', function () { diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index f704991c851..dc510cf94d8 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -24,6 +24,7 @@ use craft\events\RegisterElementTableAttributesEvent; use craft\events\RegisterPreviewTargetsEvent; use craft\events\RenderElementEvent; +use CraftCms\Cms\Element\Events\AfterSave; use CraftCms\Cms\Element\Events\BeforeSave; use CraftCms\Cms\Element\Events\DefineCacheTags; use CraftCms\Cms\Element\Events\PrepQueryForTableAttribute; @@ -219,6 +220,15 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_AFTER_SAVE = 'afterSave'; + /** + * @event ModelEvent The event that is triggered after the element is fully saved and propagated to other sites. + * + * @see afterPropagate() + * @since 3.2.0 + * @deprecated 6.0.0 Use {@see AfterPropagate} instead. + */ + public const EVENT_AFTER_PROPAGATE = 'afterPropagate'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -639,5 +649,24 @@ public static function registerEvents(): void YiiEvent::trigger($class, self::EVENT_AFTER_SAVE, $yiiEvent); } }); + + Event::listen(function(AfterPropagate $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_PROPAGATE)) { + continue; + } + + $yiiEvent = new ModelEvent([ + 'sender' => $event->element, + 'isNew' => $event->isNew, + ]); + + YiiEvent::trigger($class, self::EVENT_AFTER_PROPAGATE, $yiiEvent); + } + }); } } From 95aea71df4d2cb7a35c47054fc460267a11efc94 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 14:54:58 +0100 Subject: [PATCH 13/31] Convert remaining lifecycle events to Laravel events with backwards compatibility Converts EVENT_BEFORE_DELETE, EVENT_AFTER_DELETE, EVENT_BEFORE_RESTORE, and EVENT_AFTER_RESTORE from Yii2 events to Laravel events in HasLifecycleHooks trait. - Add BeforeDelete, AfterDelete, BeforeRestore, AfterRestore event classes - Move EVENT_* constants to yii2-adapter Element.php for backwards compatibility - Add event listeners in registerEvents() to bridge Laravel events to Yii2 handlers - Update tests to use Laravel events --- src/Element/Concerns/HasLifecycleHooks.php | 64 ++------- src/Element/Events/AfterDelete.php | 19 +++ src/Element/Events/AfterRestore.php | 19 +++ src/Element/Events/BeforeDelete.php | 23 ++++ src/Element/Events/BeforeRestore.php | 23 ++++ .../Concerns/HasLifecycleHooksTest.php | 62 +++++++-- yii2-adapter/legacy/base/Element.php | 123 ++++++++++++++++++ 7 files changed, 267 insertions(+), 66 deletions(-) create mode 100644 src/Element/Events/AfterDelete.php create mode 100644 src/Element/Events/AfterRestore.php create mode 100644 src/Element/Events/BeforeDelete.php create mode 100644 src/Element/Events/BeforeRestore.php diff --git a/src/Element/Concerns/HasLifecycleHooks.php b/src/Element/Concerns/HasLifecycleHooks.php index fb548e5e5da..6dd3920ec0b 100644 --- a/src/Element/Concerns/HasLifecycleHooks.php +++ b/src/Element/Concerns/HasLifecycleHooks.php @@ -4,11 +4,13 @@ namespace CraftCms\Cms\Element\Concerns; -use craft\events\ModelEvent; -use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementRelations; +use CraftCms\Cms\Element\Events\AfterDelete; use CraftCms\Cms\Element\Events\AfterPropagate; +use CraftCms\Cms\Element\Events\AfterRestore; use CraftCms\Cms\Element\Events\AfterSave; +use CraftCms\Cms\Element\Events\BeforeDelete; +use CraftCms\Cms\Element\Events\BeforeRestore; use CraftCms\Cms\Element\Events\BeforeSave; /** @@ -20,34 +22,6 @@ */ trait HasLifecycleHooks { - /** - * @event ModelEvent The event that is triggered before the element is deleted. - * - * You may set [[\yii\base\ModelEvent::$isValid]] to `false` to prevent the element from getting deleted. - */ - public const EVENT_BEFORE_DELETE = 'beforeDelete'; - - /** - * @event \yii\base\Event The event that is triggered after the element is deleted. - */ - public const EVENT_AFTER_DELETE = 'afterDelete'; - - /** - * @event ModelEvent The event that is triggered before the element is restored. - * - * You may set [[\yii\base\ModelEvent::$isValid]] to `false` to prevent the element from getting restored. - * - * @since 3.1.0 - */ - public const EVENT_BEFORE_RESTORE = 'beforeRestore'; - - /** - * @event \yii\base\Event The event that is triggered after the element is restored. - * - * @since 3.1.0 - */ - public const EVENT_AFTER_RESTORE = 'afterRestore'; - /** * {@inheritdoc} * @@ -112,15 +86,9 @@ public function beforeDelete(): bool return false; } - // Fire a 'beforeDelete' event - if ($this->hasEventHandlers(Element::EVENT_BEFORE_DELETE)) { - $event = new ModelEvent; - $this->trigger(Element::EVENT_BEFORE_DELETE, $event); - - return $event->isValid; - } + event($event = new BeforeDelete($this)); - return true; + return $event->isValid; } /** @@ -135,10 +103,7 @@ public function afterDelete(): void $field->afterElementDelete($this); } - // Fire an 'afterDelete' event - if ($this->hasEventHandlers(Element::EVENT_AFTER_DELETE)) { - $this->trigger(Element::EVENT_AFTER_DELETE); - } + event(new AfterDelete($this)); $this->handleRevisionDelete(); $this->handleDraftDelete(); @@ -182,15 +147,9 @@ public function beforeRestore(): bool return false; } - // Fire a 'beforeRestore' event - if ($this->hasEventHandlers(Element::EVENT_BEFORE_RESTORE)) { - $event = new ModelEvent; - $this->trigger(Element::EVENT_BEFORE_RESTORE, $event); - - return $event->isValid; - } + event($event = new BeforeRestore($this)); - return true; + return $event->isValid; } /** @@ -205,9 +164,6 @@ public function afterRestore(): void $field->afterElementRestore($this); } - // Fire an 'afterRestore' event - if ($this->hasEventHandlers(Element::EVENT_AFTER_RESTORE)) { - $this->trigger(Element::EVENT_AFTER_RESTORE); - } + event(new AfterRestore($this)); } } diff --git a/src/Element/Events/AfterDelete.php b/src/Element/Events/AfterDelete.php new file mode 100644 index 00000000000..6e0f8f15b5d --- /dev/null +++ b/src/Element/Events/AfterDelete.php @@ -0,0 +1,19 @@ + $this->entry->beforeDelete()); + $triggered = false; + Event::listen(function (BeforeDelete $event) use (&$triggered) { + $triggered = true; + }); + + $this->entry->beforeDelete(); + + expect($triggered)->toBeTrue(); +}); + +test('beforeDelete event can prevent delete', function () { + Event::listen(function (BeforeDelete $event) { + $event->isValid = false; + }); + + $result = $this->entry->beforeDelete(); + + expect($result)->toBeFalse(); }); test('afterDelete triggers event', function () { - expectYiiEvent(Entry::class, Element::EVENT_AFTER_DELETE, fn () => $this->entry->afterDelete()); + $triggered = false; + Event::listen(function (AfterDelete $event) use (&$triggered) { + $triggered = true; + }); + + $this->entry->afterDelete(); + + expect($triggered)->toBeTrue(); }); test('beforeRestore triggers event', function () { - expectYiiEvent(Entry::class, Element::EVENT_BEFORE_RESTORE, fn () => $this->entry->beforeRestore()); + $triggered = false; + Event::listen(function (BeforeRestore $event) use (&$triggered) { + $triggered = true; + }); + + $this->entry->beforeRestore(); + + expect($triggered)->toBeTrue(); }); -test('afterRestore triggers event', function () { - expectYiiEvent(Entry::class, Element::EVENT_AFTER_RESTORE, fn () => $this->entry->afterRestore()); +test('beforeRestore event can prevent restore', function () { + Event::listen(function (BeforeRestore $event) { + $event->isValid = false; + }); + + $result = $this->entry->beforeRestore(); + + expect($result)->toBeFalse(); }); -function expectYiiEvent(string $class, string $eventName, callable $action): void -{ +test('afterRestore triggers event', function () { $triggered = false; - YiiEvent::on($class, $eventName, function () use (&$triggered) { + Event::listen(function (AfterRestore $event) use (&$triggered) { $triggered = true; }); - $action(); + $this->entry->afterRestore(); expect($triggered)->toBeTrue(); -} +}); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index dc510cf94d8..80434ed398c 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -24,7 +24,12 @@ use craft\events\RegisterElementTableAttributesEvent; use craft\events\RegisterPreviewTargetsEvent; use craft\events\RenderElementEvent; +use CraftCms\Cms\Element\Events\AfterDelete; +use CraftCms\Cms\Element\Events\AfterPropagate; +use CraftCms\Cms\Element\Events\AfterRestore; use CraftCms\Cms\Element\Events\AfterSave; +use CraftCms\Cms\Element\Events\BeforeDelete; +use CraftCms\Cms\Element\Events\BeforeRestore; use CraftCms\Cms\Element\Events\BeforeSave; use CraftCms\Cms\Element\Events\DefineCacheTags; use CraftCms\Cms\Element\Events\PrepQueryForTableAttribute; @@ -229,6 +234,44 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_AFTER_PROPAGATE = 'afterPropagate'; + /** + * @event ModelEvent The event that is triggered before the element is deleted. + * + * You may set [[\yii\base\ModelEvent::$isValid]] to `false` to prevent the element from getting deleted. + * + * @see beforeDelete() + * @deprecated 6.0.0 Use {@see BeforeDelete} instead. + */ + public const EVENT_BEFORE_DELETE = 'beforeDelete'; + + /** + * @event \yii\base\Event The event that is triggered after the element is deleted. + * + * @see afterDelete() + * @deprecated 6.0.0 Use {@see AfterDelete} instead. + */ + public const EVENT_AFTER_DELETE = 'afterDelete'; + + /** + * @event ModelEvent The event that is triggered before the element is restored. + * + * You may set [[\yii\base\ModelEvent::$isValid]] to `false` to prevent the element from getting restored. + * + * @see beforeRestore() + * @since 3.1.0 + * @deprecated 6.0.0 Use {@see BeforeRestore} instead. + */ + public const EVENT_BEFORE_RESTORE = 'beforeRestore'; + + /** + * @event \yii\base\Event The event that is triggered after the element is restored. + * + * @see afterRestore() + * @since 3.1.0 + * @deprecated 6.0.0 Use {@see AfterRestore} instead. + */ + public const EVENT_AFTER_RESTORE = 'afterRestore'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -668,5 +711,85 @@ public static function registerEvents(): void YiiEvent::trigger($class, self::EVENT_AFTER_PROPAGATE, $yiiEvent); } }); + + Event::listen(function(BeforeDelete $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_DELETE)) { + continue; + } + + $yiiEvent = new ModelEvent([ + 'sender' => $event->element, + ]); + + YiiEvent::trigger($class, self::EVENT_BEFORE_DELETE, $yiiEvent); + + if (!$yiiEvent->isValid) { + $event->isValid = false; + } + } + }); + + Event::listen(function(AfterDelete $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_DELETE)) { + continue; + } + + $yiiEvent = new ModelEvent([ + 'sender' => $event->element, + ]); + + YiiEvent::trigger($class, self::EVENT_AFTER_DELETE, $yiiEvent); + } + }); + + Event::listen(function(BeforeRestore $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_RESTORE)) { + continue; + } + + $yiiEvent = new ModelEvent([ + 'sender' => $event->element, + ]); + + YiiEvent::trigger($class, self::EVENT_BEFORE_RESTORE, $yiiEvent); + + if (!$yiiEvent->isValid) { + $event->isValid = false; + } + } + }); + + Event::listen(function(AfterRestore $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_RESTORE)) { + continue; + } + + $yiiEvent = new ModelEvent([ + 'sender' => $event->element, + ]); + + YiiEvent::trigger($class, self::EVENT_AFTER_RESTORE, $yiiEvent); + } + }); } } From ed01fd46a8f39fc0340d0364d9cfbe6b7834f567 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 15:03:02 +0100 Subject: [PATCH 14/31] Convert HasControlPanelUI events to Laravel events with backwards compatibility Converts 9 Control Panel UI events from Yii2 to Laravel events: - EVENT_DEFINE_ADDITIONAL_BUTTONS -> DefineAdditionalButtons - EVENT_DEFINE_ALT_ACTIONS -> DefineAltActions - EVENT_DEFINE_ACTION_MENU_ITEMS -> DefineActionMenuItems - EVENT_DEFINE_SIDEBAR_HTML -> DefineSidebarHtml - EVENT_DEFINE_META_FIELDS_HTML -> DefineMetaFieldsHtml - EVENT_DEFINE_METADATA -> DefineMetadata - EVENT_REGISTER_HTML_ATTRIBUTES -> RegisterHtmlAttributes - EVENT_DEFINE_ATTRIBUTE_HTML -> DefineAttributeHtml - EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML -> DefineInlineAttributeInputHtml - Add new Laravel event classes in src/Element/Events/ - Move EVENT_* constants to yii2-adapter Element.php for backwards compatibility - Add event listeners in registerEvents() to bridge Laravel events to Yii2 handlers - Update tests to use Laravel events --- src/Element/Concerns/HasControlPanelUI.php | 165 ++--------- src/Element/Events/DefineActionMenuItems.php | 26 ++ .../Events/DefineAdditionalButtons.php | 27 ++ src/Element/Events/DefineAltActions.php | 26 ++ src/Element/Events/DefineAttributeHtml.php | 32 ++ .../Events/DefineInlineAttributeInputHtml.php | 32 ++ src/Element/Events/DefineMetaFieldsHtml.php | 29 ++ src/Element/Events/DefineMetadata.php | 26 ++ src/Element/Events/DefineSidebarHtml.php | 28 ++ src/Element/Events/RegisterHtmlAttributes.php | 29 ++ .../Concerns/HasControlPanelUITest.php | 278 ++++++------------ yii2-adapter/legacy/base/Element.php | 273 +++++++++++++++++ 12 files changed, 643 insertions(+), 328 deletions(-) create mode 100644 src/Element/Events/DefineActionMenuItems.php create mode 100644 src/Element/Events/DefineAdditionalButtons.php create mode 100644 src/Element/Events/DefineAltActions.php create mode 100644 src/Element/Events/DefineAttributeHtml.php create mode 100644 src/Element/Events/DefineInlineAttributeInputHtml.php create mode 100644 src/Element/Events/DefineMetaFieldsHtml.php create mode 100644 src/Element/Events/DefineMetadata.php create mode 100644 src/Element/Events/DefineSidebarHtml.php create mode 100644 src/Element/Events/RegisterHtmlAttributes.php diff --git a/src/Element/Concerns/HasControlPanelUI.php b/src/Element/Concerns/HasControlPanelUI.php index a06a6ad08b7..7d73515b71a 100644 --- a/src/Element/Concerns/HasControlPanelUI.php +++ b/src/Element/Concerns/HasControlPanelUI.php @@ -7,15 +7,18 @@ use Craft; use craft\base\NestedElementInterface; use craft\controllers\ElementsController; -use craft\events\DefineAltActionsEvent; -use craft\events\DefineAttributeHtmlEvent; -use craft\events\DefineHtmlEvent; -use craft\events\DefineMenuItemsEvent; -use craft\events\DefineMetadataEvent; -use craft\events\RegisterElementHtmlAttributesEvent; use craft\helpers\Cp; use craft\helpers\ElementHelper; use CraftCms\Cms\Element\ElementAttributeRenderer; +use CraftCms\Cms\Element\Events\DefineActionMenuItems; +use CraftCms\Cms\Element\Events\DefineAdditionalButtons; +use CraftCms\Cms\Element\Events\DefineAltActions; +use CraftCms\Cms\Element\Events\DefineAttributeHtml; +use CraftCms\Cms\Element\Events\DefineInlineAttributeInputHtml; +use CraftCms\Cms\Element\Events\DefineMetadata; +use CraftCms\Cms\Element\Events\DefineMetaFieldsHtml; +use CraftCms\Cms\Element\Events\DefineSidebarHtml; +use CraftCms\Cms\Element\Events\RegisterHtmlAttributes; use CraftCms\Cms\Http\Responses\CpScreenResponse; use CraftCms\Cms\Shared\Enums\Color; use CraftCms\Cms\Support\Arr; @@ -36,81 +39,12 @@ * including edit URLs, sidebar HTML, metadata, action menus, and attribute rendering. * * @property string|null $ref The reference string to this element - * @property array $htmlAttributes Any attributes that should be included in the element’s DOM representation in the control panel + * @property array $htmlAttributes Any attributes that should be included in the element's DOM representation in the control panel * * @internal */ trait HasControlPanelUI { - /** - * @event DefineHtmlEvent The event that is triggered when defining additional buttons that should be shown at the top of the element's edit page. - * - * @see getAdditionalButtons() - * @since 4.0.0 - */ - public const EVENT_DEFINE_ADDITIONAL_BUTTONS = 'defineAdditionalButtons'; - - /** - * @event DefineAltActionsEvent The event that is triggered when defining alternative form actions for the element. - * - * @see getAltActions() - * @since 5.6.0 - */ - public const EVENT_DEFINE_ALT_ACTIONS = 'defineAltActions'; - - /** - * @event DefineMenuItemsEvent The event that is triggered when defining action menu items.. - * - * @see getActionMenuItems() - * @since 5.0.0 - */ - public const EVENT_DEFINE_ACTION_MENU_ITEMS = 'defineActionMenuItems'; - - /** - * @event DefineHtmlEvent The event that is triggered when defining the HTML for the editor sidebar. - * - * @see getSidebarHtml() - * @since 3.7.0 - */ - public const EVENT_DEFINE_SIDEBAR_HTML = 'defineSidebarHtml'; - - /** - * @event DefineHtmlEvent The event that is triggered when defining the HTML for meta fields within the editor sidebar. - * - * @see metaFieldsHtml() - * @since 3.7.0 - */ - public const EVENT_DEFINE_META_FIELDS_HTML = 'defineMetaFieldsHtml'; - - /** - * @event DefineMetadataEvent The event that is triggered when defining the element's metadata info. - * - * @see getMetadata() - * @since 3.7.0 - */ - public const EVENT_DEFINE_METADATA = 'defineMetadata'; - - /** - * @event RegisterElementHtmlAttributesEvent The event that is triggered when registering the HTML attributes that should be included in the element's DOM representation in the control panel. - */ - public const EVENT_REGISTER_HTML_ATTRIBUTES = 'registerHtmlAttributes'; - - /** - * @event DefineAttributeHtmlEvent The event that is triggered when defining an attribute's HTML for table and card views. - * - * @see getAttributeHtml() - * @since 5.0.0 - */ - public const EVENT_DEFINE_ATTRIBUTE_HTML = 'defineAttributeHtml'; - - /** - * @event DefineAttributeHtmlEvent The event that is triggered when defining an attribute's inline input HTML. - * - * @see getInlineAttributeInputHtml() - * @since 5.0.0 - */ - public const EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML = 'defineInlineAttributeInputHtml'; - /** * @see getUiLabel() * @see setUiLabel() @@ -135,12 +69,7 @@ public function prepareEditScreen(Response|CpScreenResponse $response, string $c */ public function getAdditionalButtons(): string|Stringable { - if (! $this->hasEventHandlers(self::EVENT_DEFINE_ADDITIONAL_BUTTONS)) { - return ''; - } - - $event = new DefineHtmlEvent; - $this->trigger(self::EVENT_DEFINE_ADDITIONAL_BUTTONS, $event); + event($event = new DefineAdditionalButtons($this)); return $event->html; } @@ -206,14 +135,7 @@ public function getAltActions(): array } } - if (! $this->hasEventHandlers(self::EVENT_DEFINE_ALT_ACTIONS)) { - return $altActions; - } - - $event = new DefineAltActionsEvent([ - 'altActions' => $altActions, - ]); - $this->trigger(self::EVENT_DEFINE_ALT_ACTIONS, $event); + event($event = new DefineAltActions($this, $altActions)); return $event->altActions; } @@ -228,12 +150,7 @@ public function getActionMenuItems(): array ...array_map(fn (array $item) => $item + ['destructive' => true], $this->destructiveActionMenuItems()), ]; - if (! $this->hasEventHandlers(self::EVENT_DEFINE_ACTION_MENU_ITEMS)) { - return $items; - } - - $event = new DefineMenuItemsEvent(['items' => $items]); - $this->trigger(self::EVENT_DEFINE_ACTION_MENU_ITEMS, $event); + event($event = new DefineActionMenuItems($this, $items)); return $event->items; } @@ -510,15 +427,9 @@ public function getHtmlAttributes(string $context): array ], ]); - // Fire a 'registerHtmlAttributes' event - if ($this->hasEventHandlers(self::EVENT_REGISTER_HTML_ATTRIBUTES)) { - $event = new RegisterElementHtmlAttributesEvent(['htmlAttributes' => $htmlAttributes]); - $this->trigger(self::EVENT_REGISTER_HTML_ATTRIBUTES, $event); + event($event = new RegisterHtmlAttributes($this, $context, $htmlAttributes)); - return $event->htmlAttributes; - } - - return $htmlAttributes; + return $event->htmlAttributes; } /** @@ -538,18 +449,9 @@ protected function htmlAttributes(string $context): array */ public function getAttributeHtml(string $attribute): string|Stringable { - // Fire a 'defineAttributeHtml' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_ATTRIBUTE_HTML)) { - $event = new DefineAttributeHtmlEvent([ - 'attribute' => $attribute, - ]); - $this->trigger(self::EVENT_DEFINE_ATTRIBUTE_HTML, $event); - if (isset($event->html)) { - return $event->html; - } - } + event($event = new DefineAttributeHtml($this, $attribute)); - return $this->attributeHtml($attribute); + return $event->html ?? $this->attributeHtml($attribute); } /** @@ -557,16 +459,9 @@ public function getAttributeHtml(string $attribute): string|Stringable */ public function getInlineAttributeInputHtml(string $attribute): string|Stringable { - // Fire a 'defineInlineAttributeInputHtml' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML)) { - $event = new DefineAttributeHtmlEvent(['attribute' => $attribute]); - $this->trigger(self::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML, $event); - if (isset($event->html)) { - return $event->html; - } - } + event($event = new DefineInlineAttributeInputHtml($this, $attribute)); - return $this->inlineAttributeInputHtml($attribute); + return $event->html ?? $this->inlineAttributeInputHtml($attribute); } /** @@ -641,12 +536,7 @@ public function getSidebarHtml(bool $static): string|Stringable $html = implode("\n", $components); - if (! $this->hasEventHandlers(self::EVENT_DEFINE_SIDEBAR_HTML)) { - return $html; - } - - $event = new DefineHtmlEvent(['html' => $html]); - $this->trigger(self::EVENT_DEFINE_SIDEBAR_HTML, $event); + event($event = new DefineSidebarHtml($this, $static, $html)); return $event->html; } @@ -660,12 +550,7 @@ public function getSidebarHtml(bool $static): string|Stringable */ protected function metaFieldsHtml(bool $static): string|Stringable { - if (! $this->hasEventHandlers(self::EVENT_DEFINE_META_FIELDS_HTML)) { - return ''; - } - - $event = new DefineHtmlEvent(['static' => $static]); - $this->trigger(self::EVENT_DEFINE_META_FIELDS_HTML, $event); + event($event = new DefineMetaFieldsHtml($this, $static)); return $event->html; } @@ -808,12 +693,8 @@ public function getMetadata(): array { $metadata = $this->metadata(); - // Fire a 'defineMetadata' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_METADATA)) { - $event = new DefineMetadataEvent(['metadata' => $metadata]); - $this->trigger(self::EVENT_DEFINE_METADATA, $event); - $metadata = $event->metadata; - } + event($event = new DefineMetadata($this, $metadata)); + $metadata = $event->metadata; $formatter = I18N::getFormatter(); diff --git a/src/Element/Events/DefineActionMenuItems.php b/src/Element/Events/DefineActionMenuItems.php new file mode 100644 index 00000000000..5c2ff1d28e6 --- /dev/null +++ b/src/Element/Events/DefineActionMenuItems.php @@ -0,0 +1,26 @@ +entry->getAdditionalButtons())->toBe(''); }); - test('triggers defineAdditionalButtons event', function () { + test('triggers DefineAdditionalButtons event', function () { $eventTriggered = false; $customHtml = ''; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_ADDITIONAL_BUTTONS, - function (DefineHtmlEvent $event) use (&$eventTriggered, $customHtml) { - $eventTriggered = true; - $event->html = $customHtml; - } - ); + Event::listen(function (DefineAdditionalButtons $event) use (&$eventTriggered, $customHtml) { + $eventTriggered = true; + $event->html = $customHtml; + }); $buttons = $this->entry->getAdditionalButtons(); expect($eventTriggered)->toBeTrue(); expect((string) $buttons)->toBe($customHtml); - - Event::off(Entry::class, Entry::EVENT_DEFINE_ADDITIONAL_BUTTONS); }); }); @@ -97,50 +93,38 @@ function (DefineHtmlEvent $event) use (&$eventTriggered, $customHtml) { expect($continueEditingAction)->toHaveKey('redirect'); }); - test('triggers defineAltActions event', function () { + test('triggers DefineAltActions event', function () { $eventTriggered = false; $customAction = [ 'label' => 'Custom Action', 'action' => 'custom/action', ]; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_ALT_ACTIONS, - function (DefineAltActionsEvent $event) use (&$eventTriggered, $customAction) { - $eventTriggered = true; - $event->altActions[] = $customAction; - } - ); + Event::listen(function (DefineAltActions $event) use (&$eventTriggered, $customAction) { + $eventTriggered = true; + $event->altActions[] = $customAction; + }); $altActions = $this->entry->getAltActions(); expect($eventTriggered)->toBeTrue(); expect($altActions)->toContain($customAction); - - Event::off(Entry::class, Entry::EVENT_DEFINE_ALT_ACTIONS); }); test('event can modify alt actions', function () { - Event::on( - Entry::class, - Entry::EVENT_DEFINE_ALT_ACTIONS, - function (DefineAltActionsEvent $event) { - $event->altActions = [ - [ - 'label' => 'Only Action', - 'action' => 'test/action', - ], - ]; - } - ); + Event::listen(function (DefineAltActions $event) { + $event->altActions = [ + [ + 'label' => 'Only Action', + 'action' => 'test/action', + ], + ]; + }); $altActions = $this->entry->getAltActions(); expect($altActions)->toHaveCount(1); expect($altActions[0]['label'])->toBe('Only Action'); - - Event::off(Entry::class, Entry::EVENT_DEFINE_ALT_ACTIONS); }); }); @@ -161,7 +145,7 @@ function (DefineAltActionsEvent $event) { expect($hasDestructiveItems)->toBeTrue(); }); - test('triggers defineActionMenuItems event', function () { + test('triggers DefineActionMenuItems event', function () { $eventTriggered = false; $customItem = [ 'id' => 'custom-action', @@ -169,43 +153,31 @@ function (DefineAltActionsEvent $event) { 'icon' => 'wand', ]; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_ACTION_MENU_ITEMS, - function (DefineMenuItemsEvent $event) use (&$eventTriggered, $customItem) { - $eventTriggered = true; - $event->items[] = $customItem; - } - ); + Event::listen(function (DefineActionMenuItems $event) use (&$eventTriggered, $customItem) { + $eventTriggered = true; + $event->items[] = $customItem; + }); $items = $this->entry->getActionMenuItems(); expect($eventTriggered)->toBeTrue(); expect($items)->toContain($customItem); - - Event::off(Entry::class, Entry::EVENT_DEFINE_ACTION_MENU_ITEMS); }); test('event can modify menu items', function () { - Event::on( - Entry::class, - Entry::EVENT_DEFINE_ACTION_MENU_ITEMS, - function (DefineMenuItemsEvent $event) { - $event->items = [ - [ - 'id' => 'only-item', - 'label' => 'Only Item', - ], - ]; - } - ); + Event::listen(function (DefineActionMenuItems $event) { + $event->items = [ + [ + 'id' => 'only-item', + 'label' => 'Only Item', + ], + ]; + }); $items = $this->entry->getActionMenuItems(); expect($items)->toHaveCount(1); expect($items[0]['label'])->toBe('Only Item'); - - Event::off(Entry::class, Entry::EVENT_DEFINE_ACTION_MENU_ITEMS); }); }); @@ -222,47 +194,35 @@ function (DefineMenuItemsEvent $event) { expect($attributes['data'])->toHaveKey('disallow-status'); }); - test('triggers registerHtmlAttributes event', function () { + test('triggers RegisterHtmlAttributes event', function () { $eventTriggered = false; $customAttribute = 'custom-value'; - Event::on( - Entry::class, - Entry::EVENT_REGISTER_HTML_ATTRIBUTES, - function (RegisterElementHtmlAttributesEvent $event) use (&$eventTriggered, $customAttribute) { - $eventTriggered = true; - $event->htmlAttributes['data']['custom'] = $customAttribute; - } - ); + Event::listen(function (RegisterHtmlAttributes $event) use (&$eventTriggered, $customAttribute) { + $eventTriggered = true; + $event->htmlAttributes['data']['custom'] = $customAttribute; + }); $attributes = $this->entry->getHtmlAttributes('index'); expect($eventTriggered)->toBeTrue(); expect($attributes['data']['custom'])->toBe($customAttribute); - - Event::off(Entry::class, Entry::EVENT_REGISTER_HTML_ATTRIBUTES); }); test('event can modify html attributes', function () { - Event::on( - Entry::class, - Entry::EVENT_REGISTER_HTML_ATTRIBUTES, - function (RegisterElementHtmlAttributesEvent $event) { - $event->htmlAttributes = [ - 'class' => 'custom-class', - 'data' => [ - 'test' => 'value', - ], - ]; - } - ); + Event::listen(function (RegisterHtmlAttributes $event) { + $event->htmlAttributes = [ + 'class' => 'custom-class', + 'data' => [ + 'test' => 'value', + ], + ]; + }); $attributes = $this->entry->getHtmlAttributes('index'); expect($attributes)->toHaveKey('class', 'custom-class'); expect($attributes['data'])->toHaveKey('test', 'value'); - - Event::off(Entry::class, Entry::EVENT_REGISTER_HTML_ATTRIBUTES); }); }); @@ -273,45 +233,33 @@ function (RegisterElementHtmlAttributesEvent $event) { expect($html)->toBeString(); }); - test('triggers defineAttributeHtml event', function () { + test('triggers DefineAttributeHtml event', function () { $eventTriggered = false; $customHtml = 'Custom HTML'; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_ATTRIBUTE_HTML, - function (DefineAttributeHtmlEvent $event) use (&$eventTriggered, $customHtml) { - $eventTriggered = true; - if ($event->attribute === 'title') { - $event->html = $customHtml; - } + Event::listen(function (DefineAttributeHtml $event) use (&$eventTriggered, $customHtml) { + $eventTriggered = true; + if ($event->attribute === 'title') { + $event->html = $customHtml; } - ); + }); $html = $this->entry->getAttributeHtml('title'); expect($eventTriggered)->toBeTrue(); expect((string) $html)->toBe($customHtml); - - Event::off(Entry::class, Entry::EVENT_DEFINE_ATTRIBUTE_HTML); }); test('event receives correct attribute name', function () { $capturedAttribute = null; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_ATTRIBUTE_HTML, - function (DefineAttributeHtmlEvent $event) use (&$capturedAttribute) { - $capturedAttribute = $event->attribute; - } - ); + Event::listen(function (DefineAttributeHtml $event) use (&$capturedAttribute) { + $capturedAttribute = $event->attribute; + }); $this->entry->getAttributeHtml('slug'); expect($capturedAttribute)->toBe('slug'); - - Event::off(Entry::class, Entry::EVENT_DEFINE_ATTRIBUTE_HTML); }); }); @@ -322,45 +270,33 @@ function (DefineAttributeHtmlEvent $event) use (&$capturedAttribute) { expect($html)->toBeString(); }); - test('triggers defineInlineAttributeInputHtml event', function () { + test('triggers DefineInlineAttributeInputHtml event', function () { $eventTriggered = false; $customHtml = ''; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML, - function (DefineAttributeHtmlEvent $event) use (&$eventTriggered, $customHtml) { - $eventTriggered = true; - if ($event->attribute === 'title') { - $event->html = $customHtml; - } + Event::listen(function (DefineInlineAttributeInputHtml $event) use (&$eventTriggered, $customHtml) { + $eventTriggered = true; + if ($event->attribute === 'title') { + $event->html = $customHtml; } - ); + }); $html = $this->entry->getInlineAttributeInputHtml('title'); expect($eventTriggered)->toBeTrue(); expect((string) $html)->toBe($customHtml); - - Event::off(Entry::class, Entry::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML); }); test('event receives correct attribute name', function () { $capturedAttribute = null; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML, - function (DefineAttributeHtmlEvent $event) use (&$capturedAttribute) { - $capturedAttribute = $event->attribute; - } - ); + Event::listen(function (DefineInlineAttributeInputHtml $event) use (&$capturedAttribute) { + $capturedAttribute = $event->attribute; + }); $this->entry->getInlineAttributeInputHtml('slug'); expect($capturedAttribute)->toBe('slug'); - - Event::off(Entry::class, Entry::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML); }); }); @@ -377,41 +313,29 @@ function (DefineAttributeHtmlEvent $event) use (&$capturedAttribute) { expect($html)->toBeString(); }); - test('triggers defineSidebarHtml event', function () { + test('triggers DefineSidebarHtml event', function () { $eventTriggered = false; $customHtml = '
Custom
'; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_SIDEBAR_HTML, - function (DefineHtmlEvent $event) use (&$eventTriggered, $customHtml) { - $eventTriggered = true; - $event->html = $customHtml; - } - ); + Event::listen(function (DefineSidebarHtml $event) use (&$eventTriggered, $customHtml) { + $eventTriggered = true; + $event->html = $customHtml; + }); $html = $this->entry->getSidebarHtml(false); expect($eventTriggered)->toBeTrue(); expect((string) $html)->toBe($customHtml); - - Event::off(Entry::class, Entry::EVENT_DEFINE_SIDEBAR_HTML); }); test('event can append to existing html', function () { - Event::on( - Entry::class, - Entry::EVENT_DEFINE_SIDEBAR_HTML, - function (DefineHtmlEvent $event) { - $event->html .= '
Appended
'; - } - ); + Event::listen(function (DefineSidebarHtml $event) { + $event->html .= '
Appended
'; + }); $html = $this->entry->getSidebarHtml(false); expect((string) $html)->toContain('Appended'); - - Event::off(Entry::class, Entry::EVENT_DEFINE_SIDEBAR_HTML); }); }); @@ -446,45 +370,33 @@ function (DefineHtmlEvent $event) { expect($metadata)->toHaveKey('Updated at'); }); - test('triggers defineMetadata event', function () { + test('triggers DefineMetadata event', function () { $eventTriggered = false; - Event::on( - Entry::class, - Entry::EVENT_DEFINE_METADATA, - function (DefineMetadataEvent $event) use (&$eventTriggered) { - $eventTriggered = true; - $event->metadata['Custom'] = 'Custom Value'; - } - ); + Event::listen(function (DefineMetadata $event) use (&$eventTriggered) { + $eventTriggered = true; + $event->metadata['Custom'] = 'Custom Value'; + }); $metadata = $this->entry->getMetadata(); expect($eventTriggered)->toBeTrue(); expect($metadata)->toHaveKey('Custom'); expect($metadata['Custom'])->toBe('Custom Value'); - - Event::off(Entry::class, Entry::EVENT_DEFINE_METADATA); }); test('event can modify existing metadata', function () { - Event::on( - Entry::class, - Entry::EVENT_DEFINE_METADATA, - function (DefineMetadataEvent $event) { - $event->metadata = [ - 'Only Key' => 'Only Value', - ]; - } - ); + Event::listen(function (DefineMetadata $event) { + $event->metadata = [ + 'Only Key' => 'Only Value', + ]; + }); $metadata = $this->entry->getMetadata(); expect($metadata)->toHaveKey('Only Key'); expect($metadata)->toHaveKey('ID'); // Should still have merged defaults expect($metadata)->toHaveKey('Status'); - - Event::off(Entry::class, Entry::EVENT_DEFINE_METADATA); }); test('metadata values can be callables', function () { @@ -499,13 +411,9 @@ function (DefineMetadataEvent $event) { }); test('callable metadata can return false to omit', function () { - Event::on( - Entry::class, - Entry::EVENT_DEFINE_METADATA, - function (DefineMetadataEvent $event) { - $event->metadata['Hidden'] = fn () => false; - } - ); + Event::listen(function (DefineMetadata $event) { + $event->metadata['Hidden'] = fn () => false; + }); $metadata = $this->entry->getMetadata(); @@ -514,8 +422,6 @@ function (DefineMetadataEvent $event) { // The callable returns false, which indicates it should be omitted when rendered $hiddenValue = call_user_func($metadata['Hidden']); expect($hiddenValue)->toBeFalse(); - - Event::off(Entry::class, Entry::EVENT_DEFINE_METADATA); }); }); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 80434ed398c..5701f84d580 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -272,6 +272,85 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_AFTER_RESTORE = 'afterRestore'; + /** + * @event DefineHtmlEvent The event that is triggered when defining additional buttons that should be shown at the top of the element's edit page. + * + * @see getAdditionalButtons() + * @since 4.0.0 + * @deprecated 6.0.0 Use {@see DefineAdditionalButtons} instead. + */ + public const EVENT_DEFINE_ADDITIONAL_BUTTONS = 'defineAdditionalButtons'; + + /** + * @event DefineAltActionsEvent The event that is triggered when defining alternative form actions for the element. + * + * @see getAltActions() + * @since 5.6.0 + * @deprecated 6.0.0 Use {@see DefineAltActions} instead. + */ + public const EVENT_DEFINE_ALT_ACTIONS = 'defineAltActions'; + + /** + * @event DefineMenuItemsEvent The event that is triggered when defining action menu items. + * + * @see getActionMenuItems() + * @since 5.0.0 + * @deprecated 6.0.0 Use {@see DefineActionMenuItems} instead. + */ + public const EVENT_DEFINE_ACTION_MENU_ITEMS = 'defineActionMenuItems'; + + /** + * @event DefineHtmlEvent The event that is triggered when defining the HTML for the editor sidebar. + * + * @see getSidebarHtml() + * @since 3.7.0 + * @deprecated 6.0.0 Use {@see DefineSidebarHtml} instead. + */ + public const EVENT_DEFINE_SIDEBAR_HTML = 'defineSidebarHtml'; + + /** + * @event DefineHtmlEvent The event that is triggered when defining the HTML for meta fields within the editor sidebar. + * + * @see metaFieldsHtml() + * @since 3.7.0 + * @deprecated 6.0.0 Use {@see DefineMetaFieldsHtml} instead. + */ + public const EVENT_DEFINE_META_FIELDS_HTML = 'defineMetaFieldsHtml'; + + /** + * @event DefineMetadataEvent The event that is triggered when defining the element's metadata info. + * + * @see getMetadata() + * @since 3.7.0 + * @deprecated 6.0.0 Use {@see DefineMetadata} instead. + */ + public const EVENT_DEFINE_METADATA = 'defineMetadata'; + + /** + * @event RegisterElementHtmlAttributesEvent The event that is triggered when registering the HTML attributes that should be included in the element's DOM representation in the control panel. + * + * @deprecated 6.0.0 Use {@see RegisterHtmlAttributes} instead. + */ + public const EVENT_REGISTER_HTML_ATTRIBUTES = 'registerHtmlAttributes'; + + /** + * @event DefineAttributeHtmlEvent The event that is triggered when defining an attribute's HTML for table and card views. + * + * @see getAttributeHtml() + * @since 5.0.0 + * @deprecated 6.0.0 Use {@see DefineAttributeHtml} instead. + */ + public const EVENT_DEFINE_ATTRIBUTE_HTML = 'defineAttributeHtml'; + + /** + * @event DefineAttributeHtmlEvent The event that is triggered when defining an attribute's inline input HTML. + * + * @see getInlineAttributeInputHtml() + * @since 5.0.0 + * @deprecated 6.0.0 Use {@see DefineInlineAttributeInputHtml} instead. + */ + public const EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML = 'defineInlineAttributeInputHtml'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -791,5 +870,199 @@ public static function registerEvents(): void YiiEvent::trigger($class, self::EVENT_AFTER_RESTORE, $yiiEvent); } }); + + Event::listen(function(DefineAdditionalButtons $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ADDITIONAL_BUTTONS)) { + continue; + } + + $yiiEvent = new DefineHtmlEvent([ + 'sender' => $event->element, + 'html' => $event->html, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_ADDITIONAL_BUTTONS, $yiiEvent); + + $event->html = $yiiEvent->html; + } + }); + + Event::listen(function(DefineAltActions $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ALT_ACTIONS)) { + continue; + } + + $yiiEvent = new DefineAltActionsEvent([ + 'sender' => $event->element, + 'altActions' => $event->altActions, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_ALT_ACTIONS, $yiiEvent); + + $event->altActions = $yiiEvent->altActions; + } + }); + + Event::listen(function(DefineActionMenuItems $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ACTION_MENU_ITEMS)) { + continue; + } + + $yiiEvent = new DefineMenuItemsEvent([ + 'sender' => $event->element, + 'items' => $event->items, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_ACTION_MENU_ITEMS, $yiiEvent); + + $event->items = $yiiEvent->items; + } + }); + + Event::listen(function(DefineSidebarHtml $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_SIDEBAR_HTML)) { + continue; + } + + $yiiEvent = new DefineHtmlEvent([ + 'sender' => $event->element, + 'html' => $event->html, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_SIDEBAR_HTML, $yiiEvent); + + $event->html = $yiiEvent->html; + } + }); + + Event::listen(function(DefineMetaFieldsHtml $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_META_FIELDS_HTML)) { + continue; + } + + $yiiEvent = new DefineHtmlEvent([ + 'sender' => $event->element, + 'static' => $event->static, + 'html' => $event->html, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_META_FIELDS_HTML, $yiiEvent); + + $event->html = $yiiEvent->html; + } + }); + + Event::listen(function(DefineMetadata $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_METADATA)) { + continue; + } + + $yiiEvent = new DefineMetadataEvent([ + 'sender' => $event->element, + 'metadata' => $event->metadata, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_METADATA, $yiiEvent); + + $event->metadata = $yiiEvent->metadata; + } + }); + + Event::listen(function(RegisterHtmlAttributes $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_HTML_ATTRIBUTES)) { + continue; + } + + $yiiEvent = new RegisterElementHtmlAttributesEvent([ + 'sender' => $event->element, + 'htmlAttributes' => $event->htmlAttributes, + ]); + + YiiEvent::trigger($class, self::EVENT_REGISTER_HTML_ATTRIBUTES, $yiiEvent); + + $event->htmlAttributes = $yiiEvent->htmlAttributes; + } + }); + + Event::listen(function(DefineAttributeHtml $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ATTRIBUTE_HTML)) { + continue; + } + + $yiiEvent = new DefineAttributeHtmlEvent([ + 'sender' => $event->element, + 'attribute' => $event->attribute, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_ATTRIBUTE_HTML, $yiiEvent); + + if (isset($yiiEvent->html)) { + $event->html = $yiiEvent->html; + } + } + }); + + Event::listen(function(DefineInlineAttributeInputHtml $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML)) { + continue; + } + + $yiiEvent = new DefineAttributeHtmlEvent([ + 'sender' => $event->element, + 'attribute' => $event->attribute, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML, $yiiEvent); + + if (isset($yiiEvent->html)) { + $event->html = $yiiEvent->html; + } + } + }); } } From 923fcb90cc948e0d755d1c3dd388b0dd491ca450 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 15:07:53 +0100 Subject: [PATCH 15/31] Convert HasRoutesAndUrls events to Laravel events with backwards compatibility Converts 3 route and URL events from Yii2 to Laravel events: - EVENT_SET_ROUTE -> SetRoute - EVENT_BEFORE_DEFINE_URL -> BeforeDefineUrl - EVENT_DEFINE_URL -> DefineUrl - Add new Laravel event classes in src/Element/Events/ - Move EVENT_* constants to yii2-adapter Element.php for backwards compatibility - Add event listeners in registerEvents() to bridge Laravel events to Yii2 handlers - Update tests to use Laravel events --- src/Element/Concerns/HasRoutesAndUrls.php | 133 ++---------------- src/Element/Events/BeforeDefineUrl.php | 35 +++++ src/Element/Events/DefineUrl.php | 31 ++++ src/Element/Events/SetRoute.php | 32 +++++ .../Element/Concerns/HasRoutesAndUrlsTest.php | 63 +++------ yii2-adapter/legacy/base/Element.php | 101 +++++++++++++ 6 files changed, 235 insertions(+), 160 deletions(-) create mode 100644 src/Element/Events/BeforeDefineUrl.php create mode 100644 src/Element/Events/DefineUrl.php create mode 100644 src/Element/Events/SetRoute.php diff --git a/src/Element/Concerns/HasRoutesAndUrls.php b/src/Element/Concerns/HasRoutesAndUrls.php index b1cf6fecc63..d9c964b92bf 100644 --- a/src/Element/Concerns/HasRoutesAndUrls.php +++ b/src/Element/Concerns/HasRoutesAndUrls.php @@ -5,13 +5,14 @@ namespace CraftCms\Cms\Element\Concerns; use craft\base\NestedElementInterface; -use craft\events\DefineUrlEvent; -use craft\events\SetElementRouteEvent; use craft\helpers\ElementHelper; use craft\helpers\Template; use craft\helpers\UrlHelper; use craft\web\twig\AllowedInSandbox; use CraftCms\Cms\Element\Element; +use CraftCms\Cms\Element\Events\BeforeDefineUrl; +use CraftCms\Cms\Element\Events\DefineUrl; +use CraftCms\Cms\Element\Events\SetRoute; use CraftCms\Cms\Support\Html; use Twig\Markup; @@ -33,99 +34,7 @@ trait HasRoutesAndUrls { /** - * @event SetElementRouteEvent The event that is triggered when defining the route that should be used when this element’s URL is requested. - * - * Set [[Event::$handled]] to `true` to explicitly tell the element that a route has been set (even if you’re - * setting it to `null`). - * - * ```php - * Event::on(craft\elements\Entry::class, craft\base\Element::EVENT_SET_ROUTE, function(craft\events\SetElementRouteEvent $e) { - * // @var craft\elements\Entry $entry - * $entry = $e->sender; - * - * if ($entry->uri === 'pricing') { - * $e->route = 'module/pricing/index'; - * - * // Explicitly tell the element that a route has been set, - * // and prevent other event handlers from running, and tell - * $e->handled = true; - * } - * }); - * ``` - */ - public const string EVENT_SET_ROUTE = 'setRoute'; - - /** - * @event DefineUrlEvent The event that is triggered before defining the element’s URL. - * - * It can be used to provide a custom URL, completely bypassing the default URL generation. - * - * ```php - * use CraftCms\Cms\Element\Element; - * use CraftCms\Cms\Entry\Elements\Entry; - * use craft\events\DefineUrlEvent; - * use craft\helpers\UrlHelper; - * use yii\base\Event; - * - * Event::on( - * Entry::class, - * Element::EVENT_BEFORE_DEFINE_URL, - * function(DefineUrlEvent $e - * ) { - * // @var Entry $entry - * $entry = $e->sender; - * - * $event->url = '...'; - * }); - * ``` - * - * To prevent the element from getting a URL, ensure `$event->url` is set to `null`, - * and set `$event->handled` to `true`. - * - * Note that [[EVENT_DEFINE_URL]] will still be called regardless of what happens with this event. - * - * @see getUrl() - * @since 4.4.6 - */ - public const string EVENT_BEFORE_DEFINE_URL = 'beforeDefineUrl'; - - /** - * @event DefineUrlEvent The event that is triggered when defining the element’s URL. - * - * ```php - * use CraftCms\Cms\Element\Element; - * use CraftCms\Cms\Entry\Elements\Entry; - * use craft\events\DefineUrlEvent; - * use craft\helpers\UrlHelper; - * use yii\base\Event; - * - * Event::on( - * Entry::class, - * Element::EVENT_DEFINE_URL, - * function(DefineUrlEvent $e - * ) { - * // @var Entry $entry - * $entry = $e->sender; - * - * // Add a custom query string param to the URL - * if ($event->value !== null) { - * $event->url = UrlHelper::urlWithParams($event->url, [ - * 'foo' => 'bar', - * ]); - * } - * }); - * ``` - * - * To prevent the element from getting a URL, ensure `$event->url` is set to `null`, - * and set `$event->handled` to `true`. - * - * @see getUrl() - * @since 4.3.0 - */ - public const string EVENT_DEFINE_URL = 'defineUrl'; - - /** - * @var string|null The element’s URI + * @var string|null The element's URI */ #[AllowedInSandbox] public ?string $uri = null; @@ -152,12 +61,9 @@ public function getUriFormat(): ?string public function getRoute(): mixed { // Fire a 'setRoute' event - if ($this->hasEventHandlers(self::EVENT_SET_ROUTE)) { - $event = new SetElementRouteEvent; - $this->trigger(self::EVENT_SET_ROUTE, $event); - if ($event->handled || $event->route !== null) { - return $event->route ?: null; - } + event($event = new SetRoute($this)); + if ($event->handled || $event->route !== null) { + return $event->route ?: null; } if ($this instanceof NestedElementInterface) { @@ -195,31 +101,22 @@ public function getIsHomepage(): bool */ public function getUrl(): ?string { - $url = null; - $handled = false; - // Fire a 'beforeDefineUrl' event - if ($this->hasEventHandlers(self::EVENT_BEFORE_DEFINE_URL)) { - $event = new DefineUrlEvent; - $this->trigger(self::EVENT_BEFORE_DEFINE_URL, $event); - $url = $event->url; - $handled = $event->handled; - } + event($beforeEvent = new BeforeDefineUrl($this)); + $url = $beforeEvent->url; + $handled = $beforeEvent->handled; - // If DefineAssetUrlEvent::$url is set to null, only respect that if $handled is true + // If BeforeDefineUrl::$url is set to null, only respect that if $handled is true if ($url === null && ! $handled && isset($this->uri)) { $path = $this->getIsHomepage() ? '' : $this->uri; $url = UrlHelper::siteUrl($path, null, null, $this->siteId); } // Fire a 'defineUrl' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_URL)) { - $event = new DefineUrlEvent(['url' => $url]); - $this->trigger(self::EVENT_DEFINE_URL, $event); - // If DefineAssetUrlEvent::$url is set to null, only respect that if $handled is true - if ($event->url !== null || $event->handled) { - $url = $event->url; - } + event($event = new DefineUrl($this, $url)); + // If DefineUrl::$url is set to null, only respect that if $handled is true + if ($event->url !== null || $event->handled) { + $url = $event->url; } if ($url === null) { diff --git a/src/Element/Events/BeforeDefineUrl.php b/src/Element/Events/BeforeDefineUrl.php new file mode 100644 index 00000000000..9855f89d7e5 --- /dev/null +++ b/src/Element/Events/BeforeDefineUrl.php @@ -0,0 +1,35 @@ +primarySiteId = Sites::getPrimarySite()->id; }); -afterEach(function () { - Event::off(TestRoutableElement::class, Element::EVENT_SET_ROUTE); - Event::off(TestRoutableElement::class, Element::EVENT_BEFORE_DEFINE_URL); - Event::off(TestRoutableElement::class, Element::EVENT_DEFINE_URL); -}); - describe('getUriFormat', function () { test('returns null by default', function () { $element = new TestRoutableElement; @@ -86,33 +81,25 @@ protected function route(): array|string|null expect($element->getRoute())->toBe('my/custom/route'); }); - test('EVENT_SET_ROUTE event can override route', function () { + test('SetRoute event can override route', function () { $element = new TestRoutableElement; $element->setCustomRoute('original-route'); - Event::on( - TestRoutableElement::class, - Element::EVENT_SET_ROUTE, - function (SetElementRouteEvent $event) { - $event->route = 'event-override-route'; - } - ); + Event::listen(function (SetRoute $event) { + $event->route = 'event-override-route'; + }); expect($element->getRoute())->toBe('event-override-route'); }); - test('EVENT_SET_ROUTE event can return null with handled flag', function () { + test('SetRoute event can return null with handled flag', function () { $element = new TestRoutableElement; $element->setCustomRoute('original-route'); - Event::on( - TestRoutableElement::class, - Element::EVENT_SET_ROUTE, - function (SetElementRouteEvent $event) { - $event->route = null; - $event->handled = true; - } - ); + Event::listen(function (SetRoute $event) { + $event->route = null; + $event->handled = true; + }); expect($element->getRoute())->toBeNull(); }); @@ -160,34 +147,26 @@ function (SetElementRouteEvent $event) { expect($url)->not->toContain(Element::HOMEPAGE_URI); }); - test('EVENT_BEFORE_DEFINE_URL can set custom URL', function () { + test('BeforeDefineUrl event can set custom URL', function () { $element = new TestRoutableElement; $element->siteId = $this->primarySiteId; $element->uri = 'test-path'; - Event::on( - TestRoutableElement::class, - Element::EVENT_BEFORE_DEFINE_URL, - function (DefineUrlEvent $event) { - $event->url = 'https://custom-url.com/path'; - } - ); + Event::listen(function (BeforeDefineUrl $event) { + $event->url = 'https://custom-url.com/path'; + }); expect($element->getUrl())->toBe('https://custom-url.com/path'); }); - test('EVENT_DEFINE_URL can modify URL', function () { + test('DefineUrl event can modify URL', function () { $element = new TestRoutableElement; $element->siteId = $this->primarySiteId; $element->uri = 'test-path'; - Event::on( - TestRoutableElement::class, - Element::EVENT_DEFINE_URL, - function (DefineUrlEvent $event) { - $event->url = $event->url.'?modified=true'; - } - ); + Event::listen(function (DefineUrl $event) { + $event->url = $event->url.'?modified=true'; + }); $url = $element->getUrl(); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 5701f84d580..02c806d2084 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -10,6 +10,7 @@ namespace craft\base; use craft\base\Event as YiiEvent; +use craft\events\DefineUrlEvent; use craft\events\DefineValueEvent; use craft\events\ElementIndexTableAttributeEvent; use craft\events\RegisterElementActionsEvent; @@ -24,6 +25,7 @@ use craft\events\RegisterElementTableAttributesEvent; use craft\events\RegisterPreviewTargetsEvent; use craft\events\RenderElementEvent; +use craft\events\SetElementRouteEvent; use CraftCms\Cms\Element\Events\AfterDelete; use CraftCms\Cms\Element\Events\AfterPropagate; use CraftCms\Cms\Element\Events\AfterRestore; @@ -351,6 +353,33 @@ abstract class Element extends \CraftCms\Cms\Element\Element */ public const EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML = 'defineInlineAttributeInputHtml'; + /** + * @event SetElementRouteEvent The event that is triggered when defining the route that should be used when this element's URL is requested. + * + * @see getRoute() + * @since 3.0.0 + * @deprecated 6.0.0 Use {@see SetRoute} instead. + */ + public const EVENT_SET_ROUTE = 'setRoute'; + + /** + * @event DefineUrlEvent The event that is triggered before defining the element's URL. + * + * @see getUrl() + * @since 4.4.6 + * @deprecated 6.0.0 Use {@see BeforeDefineUrl} instead. + */ + public const EVENT_BEFORE_DEFINE_URL = 'beforeDefineUrl'; + + /** + * @event DefineUrlEvent The event that is triggered when defining the element's URL. + * + * @see getUrl() + * @since 4.3.0 + * @deprecated 6.0.0 Use {@see DefineUrl} instead. + */ + public const EVENT_DEFINE_URL = 'defineUrl'; + public static function registerEvents(): void { // Find all classes that extend Element @@ -1064,5 +1093,77 @@ public static function registerEvents(): void } } }); + + Event::listen(function(SetRoute $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_SET_ROUTE)) { + continue; + } + + $yiiEvent = new SetElementRouteEvent([ + 'sender' => $event->element, + 'route' => $event->route, + ]); + + YiiEvent::trigger($class, self::EVENT_SET_ROUTE, $yiiEvent); + + $event->route = $yiiEvent->route; + if ($yiiEvent->handled) { + $event->handled = true; + } + } + }); + + Event::listen(function(BeforeDefineUrl $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_DEFINE_URL)) { + continue; + } + + $yiiEvent = new DefineUrlEvent([ + 'sender' => $event->element, + 'url' => $event->url, + ]); + + YiiEvent::trigger($class, self::EVENT_BEFORE_DEFINE_URL, $yiiEvent); + + $event->url = $yiiEvent->url; + if ($yiiEvent->handled) { + $event->handled = true; + } + } + }); + + Event::listen(function(DefineUrl $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_URL)) { + continue; + } + + $yiiEvent = new DefineUrlEvent([ + 'sender' => $event->element, + 'url' => $event->url, + ]); + + YiiEvent::trigger($class, self::EVENT_DEFINE_URL, $yiiEvent); + + $event->url = $yiiEvent->url; + if ($yiiEvent->handled) { + $event->handled = true; + } + } + }); } } From 687d7cfec6530f9f7120a39c90678351fb7ba222 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 15:11:28 +0100 Subject: [PATCH 16/31] Convert Structurable events to Laravel events with backwards compatibility Converts 2 structure events from Yii2 to Laravel events: - EVENT_BEFORE_MOVE_IN_STRUCTURE -> BeforeMoveInStructure - EVENT_AFTER_MOVE_IN_STRUCTURE -> AfterMoveInStructure - Add new Laravel event classes in src/Element/Events/ - Move EVENT_* constants to yii2-adapter Element.php for backwards compatibility - Add event listeners in registerEvents() to bridge Laravel events to Yii2 handlers --- src/Element/Concerns/Structurable.php | 30 ++-------- src/Element/Events/AfterMoveInStructure.php | 26 +++++++++ src/Element/Events/BeforeMoveInStructure.php | 30 ++++++++++ yii2-adapter/legacy/base/Element.php | 60 ++++++++++++++++++++ 4 files changed, 121 insertions(+), 25 deletions(-) create mode 100644 src/Element/Events/AfterMoveInStructure.php create mode 100644 src/Element/Events/BeforeMoveInStructure.php diff --git a/src/Element/Concerns/Structurable.php b/src/Element/Concerns/Structurable.php index 8bb196636f6..120559e8fea 100644 --- a/src/Element/Concerns/Structurable.php +++ b/src/Element/Concerns/Structurable.php @@ -6,9 +6,10 @@ use Craft; use craft\base\ElementInterface; -use craft\events\ElementStructureEvent; use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementCollection; +use CraftCms\Cms\Element\Events\AfterMoveInStructure; +use CraftCms\Cms\Element\Events\BeforeMoveInStructure; use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface; use CraftCms\Cms\Element\Queries\ElementQuery; @@ -35,18 +36,6 @@ */ trait Structurable { - /** - * @event ElementStructureEvent The event that is triggered before the element is moved in a structure. - * - * You may set [[\yii\base\ModelEvent::$isValid]] to `false` to prevent the element from getting moved. - */ - public const string EVENT_BEFORE_MOVE_IN_STRUCTURE = 'beforeMoveInStructure'; - - /** - * @event ElementStructureEvent The event that is triggered after the element is moved in a structure. - */ - public const string EVENT_AFTER_MOVE_IN_STRUCTURE = 'afterMoveInStructure'; - public ?int $structureId = null; public ?int $root = null; @@ -372,14 +361,9 @@ public function isNextSiblingOf(ElementInterface $element): bool public function beforeMoveInStructure(int $structureId): bool { // Fire a 'beforeMoveInStructure' event - if ($this->hasEventHandlers(self::EVENT_BEFORE_MOVE_IN_STRUCTURE)) { - $event = new ElementStructureEvent(['structureId' => $structureId]); - $this->trigger(self::EVENT_BEFORE_MOVE_IN_STRUCTURE, $event); - - return $event->isValid; - } + event($event = new BeforeMoveInStructure($this, $structureId)); - return true; + return $event->isValid; } /** @@ -388,11 +372,7 @@ public function beforeMoveInStructure(int $structureId): bool public function afterMoveInStructure(int $structureId): void { // Fire an 'afterMoveInStructure' event - if ($this->hasEventHandlers(self::EVENT_AFTER_MOVE_IN_STRUCTURE)) { - $this->trigger(self::EVENT_AFTER_MOVE_IN_STRUCTURE, new ElementStructureEvent([ - 'structureId' => $structureId, - ])); - } + event(new AfterMoveInStructure($this, $structureId)); // Invalidate caches for this element Craft::$app->getElements()->invalidateCachesForElement($this); diff --git a/src/Element/Events/AfterMoveInStructure.php b/src/Element/Events/AfterMoveInStructure.php new file mode 100644 index 00000000000..f288aa5ddf1 --- /dev/null +++ b/src/Element/Events/AfterMoveInStructure.php @@ -0,0 +1,26 @@ +element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_MOVE_IN_STRUCTURE)) { + continue; + } + + $yiiEvent = new ElementStructureEvent([ + 'sender' => $event->element, + 'structureId' => $event->structureId, + ]); + + YiiEvent::trigger($class, self::EVENT_BEFORE_MOVE_IN_STRUCTURE, $yiiEvent); + + if (!$yiiEvent->isValid) { + $event->isValid = false; + } + } + }); + + Event::listen(function(AfterMoveInStructure $event) use ($elementClasses) { + foreach ($elementClasses as $class) { + if (!is_a($event->element, $class)) { + continue; + } + + if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_MOVE_IN_STRUCTURE)) { + continue; + } + + $yiiEvent = new ElementStructureEvent([ + 'sender' => $event->element, + 'structureId' => $event->structureId, + ]); + + YiiEvent::trigger($class, self::EVENT_AFTER_MOVE_IN_STRUCTURE, $yiiEvent); + } + }); } } From 52c7ff08b365e9ae1f99249eca10ef7b175ec6e4 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 15:17:46 +0100 Subject: [PATCH 17/31] Move deprecated authorization event constants to yii2-adapter Moves 6 deprecated EVENT_AUTHORIZE_* constants from src/Element/Element.php to yii2-adapter/legacy/base/Element.php for backwards compatibility: - EVENT_AUTHORIZE_VIEW - EVENT_AUTHORIZE_SAVE - EVENT_AUTHORIZE_CREATE_DRAFTS - EVENT_AUTHORIZE_DUPLICATE - EVENT_AUTHORIZE_DELETE - EVENT_AUTHORIZE_DELETE_FOR_SITE These events were already deprecated in 4.3.0 in favor of Elements service events. Also fixes Drafts.php to reference YiiElement::EVENT_AFTER_PROPAGATE correctly. --- src/Element/Drafts.php | 3 +- src/Element/Element.php | 133 +-------------------------- yii2-adapter/legacy/base/Element.php | 54 +++++++++++ 3 files changed, 57 insertions(+), 133 deletions(-) diff --git a/src/Element/Drafts.php b/src/Element/Drafts.php index fd100e86216..b249b75e265 100644 --- a/src/Element/Drafts.php +++ b/src/Element/Drafts.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element; use Craft; +use craft\base\Element as YiiElement; use craft\base\ElementInterface; use craft\behaviors\EventBehavior; use craft\events\ModelEvent; @@ -123,7 +124,7 @@ public function createDraft( /** @TODO: Remove behavior */ $newAttributes['behaviors']['duplicateOwnershipAfterPropagate'] = new EventBehavior([ - Element::EVENT_AFTER_PROPAGATE => function (ModelEvent $event) use ($canonical) { + YiiElement::EVENT_AFTER_PROPAGATE => function (ModelEvent $event) use ($canonical) { /** @var ElementInterface $draft */ $draft = $event->sender; diff --git a/src/Element/Element.php b/src/Element/Element.php index df036cf73ea..5712c128f57 100644 --- a/src/Element/Element.php +++ b/src/Element/Element.php @@ -19,7 +19,6 @@ use CraftCms\Cms\Validation\Attributes\Ruleset; use CraftCms\Cms\Validation\Concerns\ValidatesWithRuleset; use DateTime; -use Deprecated; use Illuminate\Support\Facades\Validator as ValidatorFacade; use Illuminate\Support\Traits\Macroable; use Illuminate\Validation\Validator as LaravelValidator; @@ -27,7 +26,6 @@ use Throwable; use Traversable; use yii\base\ArrayableTrait; -use yii\base\Event; use yii\base\InvalidCallException; use yii\base\UnknownPropertyException; @@ -87,137 +85,8 @@ abstract class Element extends Component implements ElementInterface public const string SCENARIO_LIVE = 'live'; - // Events - // ------------------------------------------------------------------------- - - /** - * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to view the element’s edit page. - * - * To authorize the user, set [[AuthorizationCheckEvent::$authorized]] to `true`. - * - * ```php - * Event::on( - * Entry::class, - * Element::EVENT_AUTHORIZE_VIEW, - * function(AuthorizationCheckEvent $event) { - * $event->authorized = true; - * } - * ); - * ``` - * - * @see canView() - * @since 4.0.0 - */ - #[Deprecated(message: 'in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_VIEW]] should be used instead.')] - public const EVENT_AUTHORIZE_VIEW = 'authorizeView'; - - /** - * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to save the element in its current state. - * - * To authorize the user, set [[AuthorizationCheckEvent::$authorized]] to `true`. - * - * ```php - * Event::on( - * Entry::class, - * Element::EVENT_AUTHORIZE_SAVE, - * function(AuthorizationCheckEvent $event) { - * $event->authorized = true; - * } - * ); - * ``` - * - * @see canSave() - * @since 4.0.0 - */ - #[Deprecated(message: 'in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_SAVE]] should be used instead.')] - public const EVENT_AUTHORIZE_SAVE = 'authorizeSave'; - - /** - * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to create drafts for the element. - * - * To authorize the user, set [[AuthorizationCheckEvent::$authorized]] to `true`. - * - * ```php - * Event::on( - * Entry::class, - * Element::EVENT_AUTHORIZE_CREATE_DRAFTS, - * function(AuthorizationCheckEvent $event) { - * $event->authorized = true; - * } - * ); - * ``` - * - * @see canCreateDrafts() - * @since 4.0.0 - */ - #[Deprecated(message: 'in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_CREATE_DRAFTS]] should be used instead.')] - public const EVENT_AUTHORIZE_CREATE_DRAFTS = 'authorizeCreateDrafts'; - - /** - * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to duplicate the element. - * - * To authorize the user, set [[AuthorizationCheckEvent::$authorized]] to `true`. - * - * ```php - * Event::on( - * Entry::class, - * Element::EVENT_AUTHORIZE_DUPLICATE, - * function(AuthorizationCheckEvent $event) { - * $event->authorized = true; - * } - * ); - * ``` - * - * @see canDuplicate() - * @since 4.0.0 - */ - #[Deprecated(message: 'in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_DUPLICATE]] should be used instead.')] - public const EVENT_AUTHORIZE_DUPLICATE = 'authorizeDuplicate'; - - /** - * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to delete the element. - * - * To authorize the user, set [[AuthorizationCheckEvent::$authorized]] to `true`. - * - * ```php - * Event::on( - * Entry::class, - * Element::EVENT_AUTHORIZE_DELETE, - * function(AuthorizationCheckEvent $event) { - * $event->authorized = true; - * } - * ); - * ``` - * - * @see canDelete() - * @since 4.0.0 - */ - #[Deprecated(message: 'in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_DELETE]] should be used instead.')] - public const EVENT_AUTHORIZE_DELETE = 'authorizeDelete'; - - /** - * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to delete the element for its current site. - * - * To authorize the user, set [[AuthorizationCheckEvent::$authorized]] to `true`. - * - * ```php - * Event::on( - * Entry::class, - * Element::EVENT_AUTHORIZE_DELETE_FOR_SITE, - * function(AuthorizationCheckEvent $event) { - * $event->authorized = true; - * } - * ); - * ``` - * - * @see canDeleteForSite() - * @since 4.0.0 - */ - #[Deprecated(message: 'in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_DELETE_FOR_SITE]] should be used instead.')] - public const EVENT_AUTHORIZE_DELETE_FOR_SITE = 'authorizeDeleteForSite'; - /** - * @var int|null The element’s ID + * @var int|null The element's ID */ #[AllowedInSandbox] public ?int $id = null; diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index 5c54e8544d6..ebab584e4d1 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -55,6 +55,60 @@ */ abstract class Element extends \CraftCms\Cms\Element\Element { + /** + * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to view the element's edit page. + * + * @see canView() + * @since 4.0.0 + * @deprecated in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_VIEW]] should be used instead. + */ + public const EVENT_AUTHORIZE_VIEW = 'authorizeView'; + + /** + * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to save the element in its current state. + * + * @see canSave() + * @since 4.0.0 + * @deprecated in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_SAVE]] should be used instead. + */ + public const EVENT_AUTHORIZE_SAVE = 'authorizeSave'; + + /** + * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to create drafts for the element. + * + * @see canCreateDrafts() + * @since 4.0.0 + * @deprecated in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_CREATE_DRAFTS]] should be used instead. + */ + public const EVENT_AUTHORIZE_CREATE_DRAFTS = 'authorizeCreateDrafts'; + + /** + * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to duplicate the element. + * + * @see canDuplicate() + * @since 4.0.0 + * @deprecated in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_DUPLICATE]] should be used instead. + */ + public const EVENT_AUTHORIZE_DUPLICATE = 'authorizeDuplicate'; + + /** + * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to delete the element. + * + * @see canDelete() + * @since 4.0.0 + * @deprecated in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_DELETE]] should be used instead. + */ + public const EVENT_AUTHORIZE_DELETE = 'authorizeDelete'; + + /** + * @event AuthorizationCheckEvent The event that is triggered when determining whether a user is authorized to delete the element for its current site. + * + * @see canDeleteForSite() + * @since 4.0.0 + * @deprecated in 4.3.0. [[\craft\services\Elements::EVENT_AUTHORIZE_DELETE_FOR_SITE]] should be used instead. + */ + public const EVENT_AUTHORIZE_DELETE_FOR_SITE = 'authorizeDeleteForSite'; + /** * @event DefineValueEvent The event that is triggered when defining the cache tags that should be cleared when * this element is saved. From 608ebfc19c4371f63ffbac84d41e9c756c708f4c Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 15:44:12 +0100 Subject: [PATCH 18/31] Cleanup --- src/Element/Events/AfterMoveInStructure.php | 6 - src/Element/Events/BeforeDefineUrl.php | 7 +- src/Element/Events/BeforeDelete.php | 5 +- src/Element/Events/BeforeMoveInStructure.php | 11 +- src/Element/Events/BeforeRestore.php | 5 +- src/Element/Events/BeforeSave.php | 5 +- src/Element/Events/DefineActionMenuItems.php | 6 - .../Events/DefineAdditionalButtons.php | 6 - src/Element/Events/DefineAltActions.php | 6 - src/Element/Events/DefineAttributeHtml.php | 7 - src/Element/Events/DefineEagerLoadingMap.php | 4 +- .../Events/DefineInlineAttributeInputHtml.php | 2 - src/Element/Events/DefineKeywords.php | 2 - src/Element/Events/DefineMetaFieldsHtml.php | 2 - src/Element/Events/DefineMetadata.php | 2 - src/Element/Events/DefineSidebarHtml.php | 2 - src/Element/Events/DefineUrl.php | 7 +- .../Events/PrepQueryForTableAttribute.php | 5 +- src/Element/Events/RegisterActions.php | 6 +- src/Element/Events/RegisterCardAttributes.php | 5 +- .../Events/RegisterDefaultCardAttributes.php | 6 +- .../Events/RegisterDefaultTableAttributes.php | 6 +- src/Element/Events/RegisterExporters.php | 6 +- src/Element/Events/RegisterFieldLayouts.php | 3 +- src/Element/Events/RegisterHtmlAttributes.php | 2 - .../Events/RegisterSearchableAttributes.php | 8 +- src/Element/Events/RegisterSortOptions.php | 8 +- src/Element/Events/RegisterSources.php | 4 +- .../Events/RegisterTableAttributes.php | 6 +- src/Element/Events/Render.php | 4 +- src/Element/Events/SetEagerLoadedElements.php | 9 +- src/Element/Events/SetRoute.php | 7 +- .../Concerns/HasLifecycleHooksTest.php | 3 + yii2-adapter/legacy/base/Element.php | 196 +++++------------- yii2-adapter/legacy/services/Elements.php | 8 +- 35 files changed, 116 insertions(+), 261 deletions(-) diff --git a/src/Element/Events/AfterMoveInStructure.php b/src/Element/Events/AfterMoveInStructure.php index f288aa5ddf1..68b3ee4e45e 100644 --- a/src/Element/Events/AfterMoveInStructure.php +++ b/src/Element/Events/AfterMoveInStructure.php @@ -10,15 +10,9 @@ * @event AfterMoveInStructure The event that is triggered after the element is moved in a structure. * * {@see \CraftCms\Cms\Element\Concerns\Structurable::afterMoveInStructure()} - * - * @since 6.0.0 */ final class AfterMoveInStructure { - /** - * @param ElementInterface $element The element - * @param int $structureId The structure ID - */ public function __construct( public ElementInterface $element, public int $structureId, diff --git a/src/Element/Events/BeforeDefineUrl.php b/src/Element/Events/BeforeDefineUrl.php index 9855f89d7e5..fcc9c4e77e7 100644 --- a/src/Element/Events/BeforeDefineUrl.php +++ b/src/Element/Events/BeforeDefineUrl.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element\Events; use craft\base\ElementInterface; +use CraftCms\Cms\Shared\Concerns\HandleableEvent; /** * @event BeforeDefineUrl The event that is triggered before defining the element's URL. @@ -17,19 +18,17 @@ * Note that DefineUrl will still be called regardless of what happens with this event. * * {@see \CraftCms\Cms\Element\Concerns\HasRoutesAndUrls::getUrl()} - * - * @since 6.0.0 */ final class BeforeDefineUrl { + use HandleableEvent; + /** * @param ElementInterface $element The element * @param string|null $url The URL - * @param bool $handled Whether the event has been handled */ public function __construct( public ElementInterface $element, public ?string $url = null, - public bool $handled = false, ) {} } diff --git a/src/Element/Events/BeforeDelete.php b/src/Element/Events/BeforeDelete.php index 6adeb349572..7538ed1328b 100644 --- a/src/Element/Events/BeforeDelete.php +++ b/src/Element/Events/BeforeDelete.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element\Events; use craft\base\ElementInterface; +use CraftCms\Cms\Shared\Concerns\ValidatableEvent; /** * @event BeforeDelete The event that is triggered before the element is deleted. @@ -15,9 +16,9 @@ */ final class BeforeDelete { + use ValidatableEvent; + public function __construct( public ElementInterface $element, - /** @var bool Whether the delete should proceed */ - public bool $isValid = true, ) {} } diff --git a/src/Element/Events/BeforeMoveInStructure.php b/src/Element/Events/BeforeMoveInStructure.php index a7419997874..4b14a7ad656 100644 --- a/src/Element/Events/BeforeMoveInStructure.php +++ b/src/Element/Events/BeforeMoveInStructure.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element\Events; use craft\base\ElementInterface; +use CraftCms\Cms\Shared\Concerns\ValidatableEvent; /** * @event BeforeMoveInStructure The event that is triggered before the element is moved in a structure. @@ -12,19 +13,13 @@ * Set `$isValid` to `false` to prevent the element from getting moved. * * {@see \CraftCms\Cms\Element\Concerns\Structurable::beforeMoveInStructure()} - * - * @since 6.0.0 */ final class BeforeMoveInStructure { - /** - * @param ElementInterface $element The element - * @param int $structureId The structure ID - * @param bool $isValid Whether the move is valid - */ + use ValidatableEvent; + public function __construct( public ElementInterface $element, public int $structureId, - public bool $isValid = true, ) {} } diff --git a/src/Element/Events/BeforeRestore.php b/src/Element/Events/BeforeRestore.php index 40ae7d40535..7bb2448b343 100644 --- a/src/Element/Events/BeforeRestore.php +++ b/src/Element/Events/BeforeRestore.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element\Events; use craft\base\ElementInterface; +use CraftCms\Cms\Shared\Concerns\ValidatableEvent; /** * @event BeforeRestore The event that is triggered before the element is restored. @@ -15,9 +16,9 @@ */ final class BeforeRestore { + use ValidatableEvent; + public function __construct( public ElementInterface $element, - /** @var bool Whether the restore should proceed */ - public bool $isValid = true, ) {} } diff --git a/src/Element/Events/BeforeSave.php b/src/Element/Events/BeforeSave.php index e51b71f3ecf..186e1480c0d 100644 --- a/src/Element/Events/BeforeSave.php +++ b/src/Element/Events/BeforeSave.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element\Events; use craft\base\ElementInterface; +use CraftCms\Cms\Shared\Concerns\ValidatableEvent; /** * @event BeforeSave The event that is triggered before the element is saved. @@ -15,11 +16,11 @@ */ final class BeforeSave { + use ValidatableEvent; + public function __construct( public ElementInterface $element, /** @var bool Whether the element is brand new */ public bool $isNew, - /** @var bool Whether the save should proceed */ - public bool $isValid = true, ) {} } diff --git a/src/Element/Events/DefineActionMenuItems.php b/src/Element/Events/DefineActionMenuItems.php index 5c2ff1d28e6..78f18884452 100644 --- a/src/Element/Events/DefineActionMenuItems.php +++ b/src/Element/Events/DefineActionMenuItems.php @@ -10,15 +10,9 @@ * @event DefineActionMenuItems The event that is triggered when defining action menu items. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getActionMenuItems()} - * - * @since 6.0.0 */ final class DefineActionMenuItems { - /** - * @param ElementInterface $element The element - * @param array $items The action menu items - */ public function __construct( public ElementInterface $element, public array $items = [], diff --git a/src/Element/Events/DefineAdditionalButtons.php b/src/Element/Events/DefineAdditionalButtons.php index 6ddf6bde7b4..6761f1e6814 100644 --- a/src/Element/Events/DefineAdditionalButtons.php +++ b/src/Element/Events/DefineAdditionalButtons.php @@ -11,15 +11,9 @@ * that should be shown at the top of the element's edit page. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getAdditionalButtons()} - * - * @since 6.0.0 */ final class DefineAdditionalButtons { - /** - * @param ElementInterface $element The element - * @param string $html The HTML for additional buttons - */ public function __construct( public ElementInterface $element, public string $html = '', diff --git a/src/Element/Events/DefineAltActions.php b/src/Element/Events/DefineAltActions.php index 1de3cf146b6..0eb6c4d48cb 100644 --- a/src/Element/Events/DefineAltActions.php +++ b/src/Element/Events/DefineAltActions.php @@ -10,15 +10,9 @@ * @event DefineAltActions The event that is triggered when defining alternative form actions for the element. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getAltActions()} - * - * @since 6.0.0 */ final class DefineAltActions { - /** - * @param ElementInterface $element The element - * @param array $altActions The alternative actions - */ public function __construct( public ElementInterface $element, public array $altActions = [], diff --git a/src/Element/Events/DefineAttributeHtml.php b/src/Element/Events/DefineAttributeHtml.php index 17f8c05d6cd..b0e4a27b3d0 100644 --- a/src/Element/Events/DefineAttributeHtml.php +++ b/src/Element/Events/DefineAttributeHtml.php @@ -14,16 +14,9 @@ * If `html` is set, it will be used instead of the default attribute HTML. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getAttributeHtml()} - * - * @since 6.0.0 */ final class DefineAttributeHtml { - /** - * @param ElementInterface $element The element - * @param string $attribute The attribute name - * @param string|Stringable|null $html The HTML to use (if set, short-circuits default rendering) - */ public function __construct( public ElementInterface $element, public string $attribute, diff --git a/src/Element/Events/DefineEagerLoadingMap.php b/src/Element/Events/DefineEagerLoadingMap.php index 21b25615e78..8b76deb74e5 100644 --- a/src/Element/Events/DefineEagerLoadingMap.php +++ b/src/Element/Events/DefineEagerLoadingMap.php @@ -10,13 +10,11 @@ * DefineEagerLoadingMap event is triggered when defining an eager-loading map. * * Set `elementType` and `map` to define a custom eager-loading map for the handle. - * - * @since 6.0.0 */ final class DefineEagerLoadingMap { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class being queried + * @param class-string $elementType The element type class being queried * @param ElementInterface[] $sourceElements An array of the source elements * @param string $handle The property handle used to identify which target elements should be included in the map * @param class-string|null $targetElementType The element type class to eager-load diff --git a/src/Element/Events/DefineInlineAttributeInputHtml.php b/src/Element/Events/DefineInlineAttributeInputHtml.php index 11a4e326e34..7d730324027 100644 --- a/src/Element/Events/DefineInlineAttributeInputHtml.php +++ b/src/Element/Events/DefineInlineAttributeInputHtml.php @@ -14,8 +14,6 @@ * If `html` is set, it will be used instead of the default inline input HTML. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getInlineAttributeInputHtml()} - * - * @since 6.0.0 */ final class DefineInlineAttributeInputHtml { diff --git a/src/Element/Events/DefineKeywords.php b/src/Element/Events/DefineKeywords.php index 2052d4a75b8..5ca700dd88d 100644 --- a/src/Element/Events/DefineKeywords.php +++ b/src/Element/Events/DefineKeywords.php @@ -10,8 +10,6 @@ * DefineKeywords event is triggered when defining the search keywords for an element attribute. * * If `handled` is set to `true`, the custom `keywords` value will be used instead of the default. - * - * @since 6.0.0 */ final class DefineKeywords { diff --git a/src/Element/Events/DefineMetaFieldsHtml.php b/src/Element/Events/DefineMetaFieldsHtml.php index 0a6012c0ef6..36e8da7ded0 100644 --- a/src/Element/Events/DefineMetaFieldsHtml.php +++ b/src/Element/Events/DefineMetaFieldsHtml.php @@ -11,8 +11,6 @@ * within the editor sidebar. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::metaFieldsHtml()} - * - * @since 6.0.0 */ final class DefineMetaFieldsHtml { diff --git a/src/Element/Events/DefineMetadata.php b/src/Element/Events/DefineMetadata.php index 175eb3afe16..8e1bd9eacec 100644 --- a/src/Element/Events/DefineMetadata.php +++ b/src/Element/Events/DefineMetadata.php @@ -10,8 +10,6 @@ * @event DefineMetadata The event that is triggered when defining the element's metadata info. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getMetadata()} - * - * @since 6.0.0 */ final class DefineMetadata { diff --git a/src/Element/Events/DefineSidebarHtml.php b/src/Element/Events/DefineSidebarHtml.php index 0f41955feb8..287df28d286 100644 --- a/src/Element/Events/DefineSidebarHtml.php +++ b/src/Element/Events/DefineSidebarHtml.php @@ -10,8 +10,6 @@ * @event DefineSidebarHtml The event that is triggered when defining the HTML for the editor sidebar. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getSidebarHtml()} - * - * @since 6.0.0 */ final class DefineSidebarHtml { diff --git a/src/Element/Events/DefineUrl.php b/src/Element/Events/DefineUrl.php index cd473e207d3..ca5966461bb 100644 --- a/src/Element/Events/DefineUrl.php +++ b/src/Element/Events/DefineUrl.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element\Events; use craft\base\ElementInterface; +use CraftCms\Cms\Shared\Concerns\HandleableEvent; /** * @event DefineUrl The event that is triggered when defining the element's URL. @@ -13,19 +14,17 @@ * and set `$handled` to `true`. * * {@see \CraftCms\Cms\Element\Concerns\HasRoutesAndUrls::getUrl()} - * - * @since 6.0.0 */ final class DefineUrl { + use HandleableEvent; + /** * @param ElementInterface $element The element * @param string|null $url The URL - * @param bool $handled Whether the event has been handled */ public function __construct( public ElementInterface $element, public ?string $url = null, - public bool $handled = false, ) {} } diff --git a/src/Element/Events/PrepQueryForTableAttribute.php b/src/Element/Events/PrepQueryForTableAttribute.php index b55b2732b02..6ebe5cb6cfb 100644 --- a/src/Element/Events/PrepQueryForTableAttribute.php +++ b/src/Element/Events/PrepQueryForTableAttribute.php @@ -4,19 +4,18 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface; /** * PrepQueryForTableAttribute event is triggered when preparing an element query for a table attribute. * * If `handled` is set to `true`, the default query preparation will be skipped. - * - * @since 6.0.0 */ final class PrepQueryForTableAttribute { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param ElementQueryInterface $query The element query * @param string $attribute The attribute name * @param bool $handled Whether the event has been handled diff --git a/src/Element/Events/RegisterActions.php b/src/Element/Events/RegisterActions.php index f97e91c63f4..e58d9295693 100644 --- a/src/Element/Events/RegisterActions.php +++ b/src/Element/Events/RegisterActions.php @@ -4,15 +4,15 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** * RegisterActions event is triggered when registering the available bulk actions for an element type. - * - * @since 6.0.0 */ final class RegisterActions { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param string $source The selected source's key * @param array $actions List of registered bulk actions for the element type */ diff --git a/src/Element/Events/RegisterCardAttributes.php b/src/Element/Events/RegisterCardAttributes.php index a2d78b8b3d5..c1014924636 100644 --- a/src/Element/Events/RegisterCardAttributes.php +++ b/src/Element/Events/RegisterCardAttributes.php @@ -4,17 +4,16 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; use craft\models\FieldLayout; /** * RegisterCardAttributes event is triggered when registering the card attributes for an element type. - * - * @since 6.0.0 */ final class RegisterCardAttributes { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param array $cardAttributes The card attributes * @param FieldLayout|null $fieldLayout The field layout */ diff --git a/src/Element/Events/RegisterDefaultCardAttributes.php b/src/Element/Events/RegisterDefaultCardAttributes.php index 7cd179297da..b3d4fe529d6 100644 --- a/src/Element/Events/RegisterDefaultCardAttributes.php +++ b/src/Element/Events/RegisterDefaultCardAttributes.php @@ -4,15 +4,15 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** * RegisterDefaultCardAttributes event is triggered when registering the default card attributes for an element type. - * - * @since 6.0.0 */ final class RegisterDefaultCardAttributes { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param array $cardAttributes The default card attribute keys */ public function __construct( diff --git a/src/Element/Events/RegisterDefaultTableAttributes.php b/src/Element/Events/RegisterDefaultTableAttributes.php index ac0068dc1c0..83f03d41076 100644 --- a/src/Element/Events/RegisterDefaultTableAttributes.php +++ b/src/Element/Events/RegisterDefaultTableAttributes.php @@ -4,15 +4,15 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** * RegisterDefaultTableAttributes event is triggered when registering the default table attributes for an element type. - * - * @since 6.0.0 */ final class RegisterDefaultTableAttributes { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param string $source The source key * @param array $tableAttributes The default table attribute keys */ diff --git a/src/Element/Events/RegisterExporters.php b/src/Element/Events/RegisterExporters.php index 19515583133..22b8c339409 100644 --- a/src/Element/Events/RegisterExporters.php +++ b/src/Element/Events/RegisterExporters.php @@ -4,15 +4,15 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** * RegisterExporters event is triggered when registering the available exporters for an element type. - * - * @since 6.0.0 */ final class RegisterExporters { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param string $source The selected source's key * @param array $exporters List of registered exporters for the element type */ diff --git a/src/Element/Events/RegisterFieldLayouts.php b/src/Element/Events/RegisterFieldLayouts.php index e32111c3a43..ba11f57d81f 100644 --- a/src/Element/Events/RegisterFieldLayouts.php +++ b/src/Element/Events/RegisterFieldLayouts.php @@ -4,6 +4,7 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; use craft\models\FieldLayout; /** @@ -15,7 +16,7 @@ final class RegisterFieldLayouts { /** - * @param class-string $elementType The element type class + * @param class-string $elementType The element type class * @param string|null $source The selected source's key, or null if all known field layouts should be returned * @param FieldLayout[] $fieldLayouts The registered field layouts */ diff --git a/src/Element/Events/RegisterHtmlAttributes.php b/src/Element/Events/RegisterHtmlAttributes.php index 54c84732f5d..822bafadc16 100644 --- a/src/Element/Events/RegisterHtmlAttributes.php +++ b/src/Element/Events/RegisterHtmlAttributes.php @@ -11,8 +11,6 @@ * that should be included in the element's DOM representation in the control panel. * * {@see \CraftCms\Cms\Element\Concerns\HasControlPanelUI::getHtmlAttributes()} - * - * @since 6.0.0 */ final class RegisterHtmlAttributes { diff --git a/src/Element/Events/RegisterSearchableAttributes.php b/src/Element/Events/RegisterSearchableAttributes.php index 1230d35e55e..3044863914b 100644 --- a/src/Element/Events/RegisterSearchableAttributes.php +++ b/src/Element/Events/RegisterSearchableAttributes.php @@ -4,15 +4,15 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** - * RegisterSearchableAttributes event is triggered when registering the searchable attributes for an element type. - * - * @since 6.0.0 + * @event RegisterSearchableAttributes event is triggered when registering the searchable attributes for an element type. */ final class RegisterSearchableAttributes { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param array $attributes The searchable attributes */ public function __construct( diff --git a/src/Element/Events/RegisterSortOptions.php b/src/Element/Events/RegisterSortOptions.php index 6d8178f1003..2d939aa8454 100644 --- a/src/Element/Events/RegisterSortOptions.php +++ b/src/Element/Events/RegisterSortOptions.php @@ -4,15 +4,15 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** - * RegisterSortOptions event is triggered when registering the sort options for an element type. - * - * @since 6.0.0 + * @event RegisterSortOptions event is triggered when registering the sort options for an element type. */ final class RegisterSortOptions { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param array $sortOptions The sort options */ public function __construct( diff --git a/src/Element/Events/RegisterSources.php b/src/Element/Events/RegisterSources.php index 34442906403..c03a9345954 100644 --- a/src/Element/Events/RegisterSources.php +++ b/src/Element/Events/RegisterSources.php @@ -4,6 +4,8 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** * @event RegisterSources The event that is triggered when registering the available sources for the element type. * @@ -12,7 +14,7 @@ final class RegisterSources { /** - * @param class-string $elementType The element type class + * @param class-string $elementType The element type class * @param string $context The context ('index', 'modal', 'field', or 'settings') * @param array $sources The registered sources */ diff --git a/src/Element/Events/RegisterTableAttributes.php b/src/Element/Events/RegisterTableAttributes.php index aea8d1bbc8e..7a55baaa788 100644 --- a/src/Element/Events/RegisterTableAttributes.php +++ b/src/Element/Events/RegisterTableAttributes.php @@ -4,15 +4,15 @@ namespace CraftCms\Cms\Element\Events; +use craft\base\ElementInterface; + /** * RegisterTableAttributes event is triggered when registering the table attributes for an element type. - * - * @since 6.0.0 */ final class RegisterTableAttributes { /** - * @param class-string<\CraftCms\Cms\Element\ElementInterface> $elementType The element type class + * @param class-string $elementType The element type class * @param array $tableAttributes The table attributes */ public function __construct( diff --git a/src/Element/Events/Render.php b/src/Element/Events/Render.php index 38d8667c3ed..7df6d5ba27d 100644 --- a/src/Element/Events/Render.php +++ b/src/Element/Events/Render.php @@ -7,11 +7,9 @@ use craft\base\ElementInterface; /** - * Render event is triggered before an element is rendered. + * @event Render event is triggered before an element is rendered. * * If `output` is set, it will be used as the rendered output instead of looking for templates. - * - * @since 6.0.0 */ final class Render { diff --git a/src/Element/Events/SetEagerLoadedElements.php b/src/Element/Events/SetEagerLoadedElements.php index 2730c3fdd49..79e58e2ecb3 100644 --- a/src/Element/Events/SetEagerLoadedElements.php +++ b/src/Element/Events/SetEagerLoadedElements.php @@ -6,29 +6,28 @@ use craft\base\ElementInterface; use craft\elements\db\EagerLoadPlan; +use CraftCms\Cms\Shared\Concerns\HandleableEvent; /** - * SetEagerLoadedElements event is triggered when setting eager-loaded elements. + * @event SetEagerLoadedElements event is triggered when setting eager-loaded elements. * * Set `handled` to `true` to prevent the elements from getting stored to the * private `$_eagerLoadedElements` array. - * - * @since 6.0.0 */ final class SetEagerLoadedElements { + use HandleableEvent; + /** * @param ElementInterface $element The element the eager-loaded elements are being set on * @param string $handle The handle that was used to eager-load the elements * @param ElementInterface[] $elements The eager-loaded elements * @param EagerLoadPlan $plan The eager-loading plan - * @param bool $handled Whether the event has been handled */ public function __construct( public ElementInterface $element, public string $handle, public array $elements, public EagerLoadPlan $plan, - public bool $handled = false, ) {} } diff --git a/src/Element/Events/SetRoute.php b/src/Element/Events/SetRoute.php index 2e8d4188684..f9591ff5510 100644 --- a/src/Element/Events/SetRoute.php +++ b/src/Element/Events/SetRoute.php @@ -5,6 +5,7 @@ namespace CraftCms\Cms\Element\Events; use craft\base\ElementInterface; +use CraftCms\Cms\Shared\Concerns\HandleableEvent; /** * @event SetRoute The event that is triggered when defining the route that should be @@ -14,19 +15,17 @@ * (even if you're setting it to `null`). * * {@see \CraftCms\Cms\Element\Concerns\HasRoutesAndUrls::getRoute()} - * - * @since 6.0.0 */ final class SetRoute { + use HandleableEvent; + /** * @param ElementInterface $element The element * @param mixed $route The route that should be used for the element, or `null` if no special action should be taken - * @param bool $handled Whether the event has been handled */ public function __construct( public ElementInterface $element, public mixed $route = null, - public bool $handled = false, ) {} } diff --git a/tests/Element/Concerns/HasLifecycleHooksTest.php b/tests/Element/Concerns/HasLifecycleHooksTest.php index 5c96a94a29b..70cdb032228 100644 --- a/tests/Element/Concerns/HasLifecycleHooksTest.php +++ b/tests/Element/Concerns/HasLifecycleHooksTest.php @@ -38,6 +38,9 @@ $event->isValid = false; }); + // Prevent revision + $this->entry->id = null; + $result = $this->entry->beforeSave(false); expect($result)->toBeFalse(); diff --git a/yii2-adapter/legacy/base/Element.php b/yii2-adapter/legacy/base/Element.php index ebab584e4d1..be2e91cb9f1 100644 --- a/yii2-adapter/legacy/base/Element.php +++ b/yii2-adapter/legacy/base/Element.php @@ -10,30 +10,63 @@ namespace craft\base; use craft\base\Event as YiiEvent; +use craft\elements\Address; +use craft\elements\Asset; +use craft\elements\Category; +use craft\elements\ContentBlock; +use craft\elements\Entry; +use craft\elements\GlobalSet; +use craft\elements\Tag; +use craft\elements\User; +use craft\events\DefineAltActionsEvent; +use craft\events\DefineAttributeHtmlEvent; +use craft\events\DefineAttributeKeywordsEvent; +use craft\events\DefineEagerLoadingMapEvent; +use craft\events\DefineHtmlEvent; +use craft\events\DefineMenuItemsEvent; +use craft\events\DefineMetadataEvent; use craft\events\DefineUrlEvent; use craft\events\DefineValueEvent; use craft\events\ElementIndexTableAttributeEvent; +use craft\events\ElementStructureEvent; +use craft\events\ModelEvent; use craft\events\RegisterElementActionsEvent; use craft\events\RegisterElementCardAttributesEvent; use craft\events\RegisterElementDefaultCardAttributesEvent; use craft\events\RegisterElementDefaultTableAttributesEvent; use craft\events\RegisterElementExportersEvent; use craft\events\RegisterElementFieldLayoutsEvent; +use craft\events\RegisterElementHtmlAttributesEvent; use craft\events\RegisterElementSearchableAttributesEvent; use craft\events\RegisterElementSortOptionsEvent; use craft\events\RegisterElementSourcesEvent; use craft\events\RegisterElementTableAttributesEvent; use craft\events\RegisterPreviewTargetsEvent; use craft\events\RenderElementEvent; +use craft\events\SetEagerLoadedElementsEvent; use craft\events\SetElementRouteEvent; use CraftCms\Cms\Element\Events\AfterDelete; +use CraftCms\Cms\Element\Events\AfterMoveInStructure; use CraftCms\Cms\Element\Events\AfterPropagate; use CraftCms\Cms\Element\Events\AfterRestore; use CraftCms\Cms\Element\Events\AfterSave; +use CraftCms\Cms\Element\Events\BeforeDefineUrl; use CraftCms\Cms\Element\Events\BeforeDelete; +use CraftCms\Cms\Element\Events\BeforeMoveInStructure; use CraftCms\Cms\Element\Events\BeforeRestore; use CraftCms\Cms\Element\Events\BeforeSave; +use CraftCms\Cms\Element\Events\DefineActionMenuItems; +use CraftCms\Cms\Element\Events\DefineAdditionalButtons; +use CraftCms\Cms\Element\Events\DefineAltActions; +use CraftCms\Cms\Element\Events\DefineAttributeHtml; use CraftCms\Cms\Element\Events\DefineCacheTags; +use CraftCms\Cms\Element\Events\DefineEagerLoadingMap; +use CraftCms\Cms\Element\Events\DefineInlineAttributeInputHtml; +use CraftCms\Cms\Element\Events\DefineKeywords; +use CraftCms\Cms\Element\Events\DefineMetadata; +use CraftCms\Cms\Element\Events\DefineMetaFieldsHtml; +use CraftCms\Cms\Element\Events\DefineSidebarHtml; +use CraftCms\Cms\Element\Events\DefineUrl; use CraftCms\Cms\Element\Events\PrepQueryForTableAttribute; use CraftCms\Cms\Element\Events\RegisterActions; use CraftCms\Cms\Element\Events\RegisterCardAttributes; @@ -41,12 +74,15 @@ use CraftCms\Cms\Element\Events\RegisterDefaultTableAttributes; use CraftCms\Cms\Element\Events\RegisterExporters; use CraftCms\Cms\Element\Events\RegisterFieldLayouts; +use CraftCms\Cms\Element\Events\RegisterHtmlAttributes; use CraftCms\Cms\Element\Events\RegisterPreviewTargets; use CraftCms\Cms\Element\Events\RegisterSearchableAttributes; use CraftCms\Cms\Element\Events\RegisterSortOptions; use CraftCms\Cms\Element\Events\RegisterSources; use CraftCms\Cms\Element\Events\RegisterTableAttributes; use CraftCms\Cms\Element\Events\Render; +use CraftCms\Cms\Element\Events\SetEagerLoadedElements; +use CraftCms\Cms\Element\Events\SetRoute; use Illuminate\Support\Facades\Event; /** @@ -456,7 +492,17 @@ public static function registerEvents(): void { // Find all classes that extend Element $classes = get_declared_classes(); - $elementClasses = []; + $elementClasses = [ + Address::class, + Asset::class, + Category::class, + ContentBlock::class, + Entry::class, + GlobalSet::class, + Tag::class, + User::class, + ]; + foreach ($classes as $class) { if (is_subclass_of($class, self::class)) { $elementClasses[] = $class; @@ -482,10 +528,6 @@ public static function registerEvents(): void Event::listen(function(RegisterSources $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_SOURCES)) { continue; } @@ -503,10 +545,6 @@ public static function registerEvents(): void Event::listen(function(RegisterFieldLayouts $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_FIELD_LAYOUTS)) { continue; } @@ -524,10 +562,6 @@ public static function registerEvents(): void Event::listen(function(RegisterPreviewTargets $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_PREVIEW_TARGETS)) { continue; } @@ -545,10 +579,6 @@ public static function registerEvents(): void Event::listen(function(RegisterActions $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_ACTIONS)) { continue; } @@ -566,10 +596,6 @@ public static function registerEvents(): void Event::listen(function(RegisterExporters $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_EXPORTERS)) { continue; } @@ -587,10 +613,6 @@ public static function registerEvents(): void Event::listen(function(Render $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_RENDER)) { continue; } @@ -613,10 +635,6 @@ public static function registerEvents(): void Event::listen(function(DefineKeywords $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_KEYWORDS)) { continue; } @@ -638,10 +656,6 @@ public static function registerEvents(): void Event::listen(function(RegisterSortOptions $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_SORT_OPTIONS)) { continue; } @@ -658,10 +672,6 @@ public static function registerEvents(): void Event::listen(function(RegisterTableAttributes $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_TABLE_ATTRIBUTES)) { continue; } @@ -678,10 +688,6 @@ public static function registerEvents(): void Event::listen(function(RegisterDefaultTableAttributes $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES)) { continue; } @@ -699,10 +705,6 @@ public static function registerEvents(): void Event::listen(function(RegisterCardAttributes $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_CARD_ATTRIBUTES)) { continue; } @@ -720,10 +722,6 @@ public static function registerEvents(): void Event::listen(function(RegisterDefaultCardAttributes $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_DEFAULT_CARD_ATTRIBUTES)) { continue; } @@ -740,10 +738,6 @@ public static function registerEvents(): void Event::listen(function(RegisterSearchableAttributes $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_SEARCHABLE_ATTRIBUTES)) { continue; } @@ -760,10 +754,6 @@ public static function registerEvents(): void Event::listen(function(PrepQueryForTableAttribute $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_PREP_QUERY_FOR_TABLE_ATTRIBUTE)) { continue; } @@ -783,10 +773,6 @@ public static function registerEvents(): void Event::listen(function(DefineEagerLoadingMap $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if ($class !== $event->elementType && !is_subclass_of($event->elementType, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_EAGER_LOADING_MAP)) { continue; } @@ -808,10 +794,6 @@ public static function registerEvents(): void Event::listen(function(SetEagerLoadedElements $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_SET_EAGER_LOADED_ELEMENTS)) { continue; } @@ -833,10 +815,6 @@ public static function registerEvents(): void Event::listen(function(BeforeSave $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_SAVE)) { continue; } @@ -856,10 +834,6 @@ public static function registerEvents(): void Event::listen(function(AfterSave $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_SAVE)) { continue; } @@ -875,10 +849,6 @@ public static function registerEvents(): void Event::listen(function(AfterPropagate $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_PROPAGATE)) { continue; } @@ -894,10 +864,6 @@ public static function registerEvents(): void Event::listen(function(BeforeDelete $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_DELETE)) { continue; } @@ -916,10 +882,6 @@ public static function registerEvents(): void Event::listen(function(AfterDelete $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_DELETE)) { continue; } @@ -934,10 +896,6 @@ public static function registerEvents(): void Event::listen(function(BeforeRestore $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_RESTORE)) { continue; } @@ -956,10 +914,6 @@ public static function registerEvents(): void Event::listen(function(AfterRestore $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_RESTORE)) { continue; } @@ -974,10 +928,6 @@ public static function registerEvents(): void Event::listen(function(DefineAdditionalButtons $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ADDITIONAL_BUTTONS)) { continue; } @@ -995,10 +945,6 @@ public static function registerEvents(): void Event::listen(function(DefineAltActions $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ALT_ACTIONS)) { continue; } @@ -1016,10 +962,6 @@ public static function registerEvents(): void Event::listen(function(DefineActionMenuItems $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ACTION_MENU_ITEMS)) { continue; } @@ -1037,10 +979,6 @@ public static function registerEvents(): void Event::listen(function(DefineSidebarHtml $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_SIDEBAR_HTML)) { continue; } @@ -1058,10 +996,6 @@ public static function registerEvents(): void Event::listen(function(DefineMetaFieldsHtml $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_META_FIELDS_HTML)) { continue; } @@ -1080,10 +1014,6 @@ public static function registerEvents(): void Event::listen(function(DefineMetadata $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_METADATA)) { continue; } @@ -1101,10 +1031,6 @@ public static function registerEvents(): void Event::listen(function(RegisterHtmlAttributes $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_REGISTER_HTML_ATTRIBUTES)) { continue; } @@ -1122,10 +1048,6 @@ public static function registerEvents(): void Event::listen(function(DefineAttributeHtml $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_ATTRIBUTE_HTML)) { continue; } @@ -1145,10 +1067,6 @@ public static function registerEvents(): void Event::listen(function(DefineInlineAttributeInputHtml $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML)) { continue; } @@ -1168,10 +1086,6 @@ public static function registerEvents(): void Event::listen(function(SetRoute $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_SET_ROUTE)) { continue; } @@ -1192,10 +1106,6 @@ public static function registerEvents(): void Event::listen(function(BeforeDefineUrl $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_DEFINE_URL)) { continue; } @@ -1216,10 +1126,6 @@ public static function registerEvents(): void Event::listen(function(DefineUrl $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_DEFINE_URL)) { continue; } @@ -1240,10 +1146,6 @@ public static function registerEvents(): void Event::listen(function(BeforeMoveInStructure $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_BEFORE_MOVE_IN_STRUCTURE)) { continue; } @@ -1263,10 +1165,6 @@ public static function registerEvents(): void Event::listen(function(AfterMoveInStructure $event) use ($elementClasses) { foreach ($elementClasses as $class) { - if (!is_a($event->element, $class)) { - continue; - } - if (!YiiEvent::hasHandlers($class, self::EVENT_AFTER_MOVE_IN_STRUCTURE)) { continue; } diff --git a/yii2-adapter/legacy/services/Elements.php b/yii2-adapter/legacy/services/Elements.php index d530b79791d..dde2830c28d 100644 --- a/yii2-adapter/legacy/services/Elements.php +++ b/yii2-adapter/legacy/services/Elements.php @@ -48,6 +48,7 @@ use CraftCms\Cms\Element\Drafts; use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\ElementCollection; +use CraftCms\Cms\Element\Events\AfterPropagate; use CraftCms\Cms\Element\Exceptions\InvalidElementException; use CraftCms\Cms\Element\Models\Element as ElementModel; use CraftCms\Cms\Element\Models\ElementSiteSettings; @@ -76,6 +77,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; use Throwable; @@ -4229,7 +4231,11 @@ private function _saveElementInternal( $siteElements[$element->siteId] = $element; $siteSettingsRecords[$element->siteId] = $siteSettingsRecord; - $element->on(Element::EVENT_AFTER_PROPAGATE, function() use ($generatedFields, $siteElements, $siteSettingsRecords) { + Event::listen(function (AfterPropagate $event) use ($element, $generatedFields, $siteElements, $siteSettingsRecords) { + if ($event->element->id !== $element->id) { + return; + } + foreach ($siteElements as $siteId => $siteElement) { $siteSettingsRecord = $siteSettingsRecords[$siteId]; $content = $siteSettingsRecord->content ?? []; From d7af878f9540c3c7ef71324ddfb1c1f18030019b Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 15:50:31 +0100 Subject: [PATCH 19/31] Add deprecation notices --- yii2-adapter/legacy/events/DefineAltActionsEvent.php | 1 + yii2-adapter/legacy/events/DefineAttributeHtmlEvent.php | 1 + yii2-adapter/legacy/events/DefineAttributeKeywordsEvent.php | 1 + yii2-adapter/legacy/events/DefineEagerLoadingMapEvent.php | 1 + yii2-adapter/legacy/events/DefineHtmlEvent.php | 1 + yii2-adapter/legacy/events/DefineMenuItemsEvent.php | 1 + yii2-adapter/legacy/events/DefineMetadataEvent.php | 1 + yii2-adapter/legacy/events/DefineUrlEvent.php | 1 + yii2-adapter/legacy/events/DefineValueEvent.php | 1 + yii2-adapter/legacy/events/ElementIndexTableAttributeEvent.php | 1 + yii2-adapter/legacy/events/ElementStructureEvent.php | 1 + yii2-adapter/legacy/events/ModelEvent.php | 1 + yii2-adapter/legacy/events/RegisterElementActionsEvent.php | 1 + .../legacy/events/RegisterElementCardAttributesEvent.php | 1 + .../legacy/events/RegisterElementDefaultCardAttributesEvent.php | 1 + .../legacy/events/RegisterElementDefaultTableAttributesEvent.php | 1 + yii2-adapter/legacy/events/RegisterElementExportersEvent.php | 1 + yii2-adapter/legacy/events/RegisterElementFieldLayoutsEvent.php | 1 + .../legacy/events/RegisterElementHtmlAttributesEvent.php | 1 + .../legacy/events/RegisterElementSearchableAttributesEvent.php | 1 + yii2-adapter/legacy/events/RegisterElementSortOptionsEvent.php | 1 + yii2-adapter/legacy/events/RegisterElementSourcesEvent.php | 1 + .../legacy/events/RegisterElementTableAttributesEvent.php | 1 + yii2-adapter/legacy/events/RegisterPreviewTargetsEvent.php | 1 + yii2-adapter/legacy/events/RenderElementEvent.php | 1 + yii2-adapter/legacy/events/SetEagerLoadedElementsEvent.php | 1 + yii2-adapter/legacy/events/SetElementRouteEvent.php | 1 + 27 files changed, 27 insertions(+) diff --git a/yii2-adapter/legacy/events/DefineAltActionsEvent.php b/yii2-adapter/legacy/events/DefineAltActionsEvent.php index 7b0c1020373..f1a4b12227d 100644 --- a/yii2-adapter/legacy/events/DefineAltActionsEvent.php +++ b/yii2-adapter/legacy/events/DefineAltActionsEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 5.6.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineAltActions} instead. */ class DefineAltActionsEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineAttributeHtmlEvent.php b/yii2-adapter/legacy/events/DefineAttributeHtmlEvent.php index 427767214af..40a1bd3d4aa 100644 --- a/yii2-adapter/legacy/events/DefineAttributeHtmlEvent.php +++ b/yii2-adapter/legacy/events/DefineAttributeHtmlEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 5.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineAttributeHtml} or {@see \CraftCms\Cms\Element\Events\DefineInlineAttributeInputHtml} instead. */ class DefineAttributeHtmlEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineAttributeKeywordsEvent.php b/yii2-adapter/legacy/events/DefineAttributeKeywordsEvent.php index 81fd6a26174..ebcc7482f69 100644 --- a/yii2-adapter/legacy/events/DefineAttributeKeywordsEvent.php +++ b/yii2-adapter/legacy/events/DefineAttributeKeywordsEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.5.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineKeywords} instead. */ class DefineAttributeKeywordsEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineEagerLoadingMapEvent.php b/yii2-adapter/legacy/events/DefineEagerLoadingMapEvent.php index a5f09d38826..d597e6fcedf 100644 --- a/yii2-adapter/legacy/events/DefineEagerLoadingMapEvent.php +++ b/yii2-adapter/legacy/events/DefineEagerLoadingMapEvent.php @@ -15,6 +15,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.1.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineEagerLoadingMap} instead. * @phpstan-import-type EagerLoadingMapItem from ElementInterface */ class DefineEagerLoadingMapEvent extends Event diff --git a/yii2-adapter/legacy/events/DefineHtmlEvent.php b/yii2-adapter/legacy/events/DefineHtmlEvent.php index 7a9c464cb7a..3d560e285f4 100644 --- a/yii2-adapter/legacy/events/DefineHtmlEvent.php +++ b/yii2-adapter/legacy/events/DefineHtmlEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.7.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineAdditionalButtons}, {@see \CraftCms\Cms\Element\Events\DefineSidebarHtml}, or {@see \CraftCms\Cms\Element\Events\DefineMetaFieldsHtml} instead. */ class DefineHtmlEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineMenuItemsEvent.php b/yii2-adapter/legacy/events/DefineMenuItemsEvent.php index a88b0787c74..46b05e61b6f 100644 --- a/yii2-adapter/legacy/events/DefineMenuItemsEvent.php +++ b/yii2-adapter/legacy/events/DefineMenuItemsEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 5.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineActionMenuItems} instead. */ class DefineMenuItemsEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineMetadataEvent.php b/yii2-adapter/legacy/events/DefineMetadataEvent.php index 95ae1a7c7b4..ecd39301cf2 100644 --- a/yii2-adapter/legacy/events/DefineMetadataEvent.php +++ b/yii2-adapter/legacy/events/DefineMetadataEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.7.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineMetadata} instead. */ class DefineMetadataEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineUrlEvent.php b/yii2-adapter/legacy/events/DefineUrlEvent.php index adc9fa74534..60513f73234 100644 --- a/yii2-adapter/legacy/events/DefineUrlEvent.php +++ b/yii2-adapter/legacy/events/DefineUrlEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 4.3.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\BeforeDefineUrl} or {@see \CraftCms\Cms\Element\Events\DefineUrl} instead. */ class DefineUrlEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineValueEvent.php b/yii2-adapter/legacy/events/DefineValueEvent.php index e689af26583..341c5bd78b4 100644 --- a/yii2-adapter/legacy/events/DefineValueEvent.php +++ b/yii2-adapter/legacy/events/DefineValueEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.7.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\DefineCacheTags} instead. */ class DefineValueEvent extends Event { diff --git a/yii2-adapter/legacy/events/ElementIndexTableAttributeEvent.php b/yii2-adapter/legacy/events/ElementIndexTableAttributeEvent.php index 191c21cae4e..82878a2151a 100644 --- a/yii2-adapter/legacy/events/ElementIndexTableAttributeEvent.php +++ b/yii2-adapter/legacy/events/ElementIndexTableAttributeEvent.php @@ -17,6 +17,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.7.14 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\PrepQueryForTableAttribute} instead. */ class ElementIndexTableAttributeEvent extends Event { diff --git a/yii2-adapter/legacy/events/ElementStructureEvent.php b/yii2-adapter/legacy/events/ElementStructureEvent.php index 4073b408ec2..37a8f3deac8 100644 --- a/yii2-adapter/legacy/events/ElementStructureEvent.php +++ b/yii2-adapter/legacy/events/ElementStructureEvent.php @@ -12,6 +12,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\BeforeMoveInStructure} or {@see \CraftCms\Cms\Element\Events\AfterMoveInStructure} instead. */ class ElementStructureEvent extends ModelEvent { diff --git a/yii2-adapter/legacy/events/ModelEvent.php b/yii2-adapter/legacy/events/ModelEvent.php index d45411c760d..f28882445b1 100644 --- a/yii2-adapter/legacy/events/ModelEvent.php +++ b/yii2-adapter/legacy/events/ModelEvent.php @@ -12,6 +12,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\BeforeSave}, {@see \CraftCms\Cms\Element\Events\AfterSave}, {@see \CraftCms\Cms\Element\Events\AfterPropagate}, {@see \CraftCms\Cms\Element\Events\BeforeDelete}, {@see \CraftCms\Cms\Element\Events\AfterDelete}, {@see \CraftCms\Cms\Element\Events\BeforeRestore}, or {@see \CraftCms\Cms\Element\Events\AfterRestore} instead. */ class ModelEvent extends \yii\base\ModelEvent { diff --git a/yii2-adapter/legacy/events/RegisterElementActionsEvent.php b/yii2-adapter/legacy/events/RegisterElementActionsEvent.php index e2ab7c4d15a..b3d6307273d 100644 --- a/yii2-adapter/legacy/events/RegisterElementActionsEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementActionsEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterActions} instead. */ class RegisterElementActionsEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementCardAttributesEvent.php b/yii2-adapter/legacy/events/RegisterElementCardAttributesEvent.php index 7b0bc84782b..8afcb7eb4a6 100644 --- a/yii2-adapter/legacy/events/RegisterElementCardAttributesEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementCardAttributesEvent.php @@ -15,6 +15,7 @@ * * @author Pixel & Tonic, Inc. * @since 5.5.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterCardAttributes} instead. */ class RegisterElementCardAttributesEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementDefaultCardAttributesEvent.php b/yii2-adapter/legacy/events/RegisterElementDefaultCardAttributesEvent.php index c4b50789003..8afbfd64de4 100644 --- a/yii2-adapter/legacy/events/RegisterElementDefaultCardAttributesEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementDefaultCardAttributesEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 5.5.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterDefaultCardAttributes} instead. */ class RegisterElementDefaultCardAttributesEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementDefaultTableAttributesEvent.php b/yii2-adapter/legacy/events/RegisterElementDefaultTableAttributesEvent.php index 533f1c819fe..8b61cec6961 100644 --- a/yii2-adapter/legacy/events/RegisterElementDefaultTableAttributesEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementDefaultTableAttributesEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterDefaultTableAttributes} instead. */ class RegisterElementDefaultTableAttributesEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementExportersEvent.php b/yii2-adapter/legacy/events/RegisterElementExportersEvent.php index 21bb8197d19..9c84116d996 100644 --- a/yii2-adapter/legacy/events/RegisterElementExportersEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementExportersEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.4.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterExporters} instead. */ class RegisterElementExportersEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementFieldLayoutsEvent.php b/yii2-adapter/legacy/events/RegisterElementFieldLayoutsEvent.php index f588f30e300..0e8e697108f 100644 --- a/yii2-adapter/legacy/events/RegisterElementFieldLayoutsEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementFieldLayoutsEvent.php @@ -15,6 +15,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.5.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterFieldLayouts} instead. */ class RegisterElementFieldLayoutsEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementHtmlAttributesEvent.php b/yii2-adapter/legacy/events/RegisterElementHtmlAttributesEvent.php index 6dd153ebb31..a7f5fb50af9 100644 --- a/yii2-adapter/legacy/events/RegisterElementHtmlAttributesEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementHtmlAttributesEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterHtmlAttributes} instead. */ class RegisterElementHtmlAttributesEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementSearchableAttributesEvent.php b/yii2-adapter/legacy/events/RegisterElementSearchableAttributesEvent.php index e4b6958d3c7..288c3e5cc51 100644 --- a/yii2-adapter/legacy/events/RegisterElementSearchableAttributesEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementSearchableAttributesEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterSearchableAttributes} instead. */ class RegisterElementSearchableAttributesEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementSortOptionsEvent.php b/yii2-adapter/legacy/events/RegisterElementSortOptionsEvent.php index a49c5f70737..662ca671f58 100644 --- a/yii2-adapter/legacy/events/RegisterElementSortOptionsEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementSortOptionsEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterSortOptions} instead. */ class RegisterElementSortOptionsEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementSourcesEvent.php b/yii2-adapter/legacy/events/RegisterElementSourcesEvent.php index d9f8ab3d311..b3578347d2c 100644 --- a/yii2-adapter/legacy/events/RegisterElementSourcesEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementSourcesEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterSources} instead. */ class RegisterElementSourcesEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterElementTableAttributesEvent.php b/yii2-adapter/legacy/events/RegisterElementTableAttributesEvent.php index 6258cd177dc..0134b8b453b 100644 --- a/yii2-adapter/legacy/events/RegisterElementTableAttributesEvent.php +++ b/yii2-adapter/legacy/events/RegisterElementTableAttributesEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterTableAttributes} instead. */ class RegisterElementTableAttributesEvent extends Event { diff --git a/yii2-adapter/legacy/events/RegisterPreviewTargetsEvent.php b/yii2-adapter/legacy/events/RegisterPreviewTargetsEvent.php index e438d454d93..9a6538db695 100644 --- a/yii2-adapter/legacy/events/RegisterPreviewTargetsEvent.php +++ b/yii2-adapter/legacy/events/RegisterPreviewTargetsEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.2.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\RegisterPreviewTargets} instead. */ class RegisterPreviewTargetsEvent extends Event { diff --git a/yii2-adapter/legacy/events/RenderElementEvent.php b/yii2-adapter/legacy/events/RenderElementEvent.php index 79df2cc35cf..1b2684ccb1f 100644 --- a/yii2-adapter/legacy/events/RenderElementEvent.php +++ b/yii2-adapter/legacy/events/RenderElementEvent.php @@ -15,6 +15,7 @@ * * @author Pixel & Tonic, Inc. * @since 5.8.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\Render} instead. */ class RenderElementEvent extends Event { diff --git a/yii2-adapter/legacy/events/SetEagerLoadedElementsEvent.php b/yii2-adapter/legacy/events/SetEagerLoadedElementsEvent.php index bb16fbaa98a..28661a6ac37 100644 --- a/yii2-adapter/legacy/events/SetEagerLoadedElementsEvent.php +++ b/yii2-adapter/legacy/events/SetEagerLoadedElementsEvent.php @@ -16,6 +16,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.5.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\SetEagerLoadedElements} instead. */ class SetEagerLoadedElementsEvent extends Event { diff --git a/yii2-adapter/legacy/events/SetElementRouteEvent.php b/yii2-adapter/legacy/events/SetElementRouteEvent.php index fc49ff2b5a5..34c3db3d8b0 100644 --- a/yii2-adapter/legacy/events/SetElementRouteEvent.php +++ b/yii2-adapter/legacy/events/SetElementRouteEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 Use {@see \CraftCms\Cms\Element\Events\SetRoute} instead. */ class SetElementRouteEvent extends Event { From 2357b3f1903c84c1090ecea84efd6c766bd073f0 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 15:54:03 +0100 Subject: [PATCH 20/31] fix-cs --- yii2-adapter/legacy/services/Elements.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yii2-adapter/legacy/services/Elements.php b/yii2-adapter/legacy/services/Elements.php index dde2830c28d..d1f82866cfd 100644 --- a/yii2-adapter/legacy/services/Elements.php +++ b/yii2-adapter/legacy/services/Elements.php @@ -4231,7 +4231,7 @@ private function _saveElementInternal( $siteElements[$element->siteId] = $element; $siteSettingsRecords[$element->siteId] = $siteSettingsRecord; - Event::listen(function (AfterPropagate $event) use ($element, $generatedFields, $siteElements, $siteSettingsRecords) { + Event::listen(function(AfterPropagate $event) use ($element, $generatedFields, $siteElements, $siteSettingsRecords) { if ($event->element->id !== $element->id) { return; } From 3e920876b01fad784a7451ec79cabdd10bb1f591 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 16:02:25 +0100 Subject: [PATCH 21/31] Use legacy element in legacy tests --- yii2-adapter/tests/unit/elements/EntryElementTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/yii2-adapter/tests/unit/elements/EntryElementTest.php b/yii2-adapter/tests/unit/elements/EntryElementTest.php index 845777dee05..8ac476b136e 100644 --- a/yii2-adapter/tests/unit/elements/EntryElementTest.php +++ b/yii2-adapter/tests/unit/elements/EntryElementTest.php @@ -10,7 +10,6 @@ use craft\events\DefineUrlEvent; use craft\helpers\UrlHelper; use craft\test\TestCase; -use CraftCms\Cms\Element\Element; use CraftCms\Cms\Entry\Elements\Entry; use UnitTester; @@ -41,11 +40,11 @@ public function testGetUrl(string|callable|null $expected, ?string $uri, ?callab $entry->uri = $uri; if ($beforeEvent) { - $entry->on(Element::EVENT_BEFORE_DEFINE_URL, $beforeEvent); + $entry->on(\craft\base\Element::EVENT_BEFORE_DEFINE_URL, $beforeEvent); } if ($afterEvent) { - $entry->on(Element::EVENT_DEFINE_URL, $afterEvent); + $entry->on(\craft\base\Element::EVENT_DEFINE_URL, $afterEvent); } if (is_callable($expected)) { From d56340b6b000d25ff90e689b8b8e54ce78bb15e7 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 16:16:18 +0100 Subject: [PATCH 22/31] Fix event listeners --- tests/Element/Concerns/HasLifecycleHooksTest.php | 4 +++- yii2-adapter/tests/unit/elements/EntryElementTest.php | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/Element/Concerns/HasLifecycleHooksTest.php b/tests/Element/Concerns/HasLifecycleHooksTest.php index 70cdb032228..f0073f089a5 100644 --- a/tests/Element/Concerns/HasLifecycleHooksTest.php +++ b/tests/Element/Concerns/HasLifecycleHooksTest.php @@ -49,7 +49,9 @@ test('beforeSave event receives isNew parameter', function () { $receivedIsNew = null; Event::listen(function (BeforeSave $event) use (&$receivedIsNew) { - $receivedIsNew = $event->isNew; + if ($event->element->id === $this->entry->id) { + $receivedIsNew = $event->isNew; + } }); $this->entry->beforeSave(true); diff --git a/yii2-adapter/tests/unit/elements/EntryElementTest.php b/yii2-adapter/tests/unit/elements/EntryElementTest.php index 8ac476b136e..6c273f57172 100644 --- a/yii2-adapter/tests/unit/elements/EntryElementTest.php +++ b/yii2-adapter/tests/unit/elements/EntryElementTest.php @@ -7,6 +7,8 @@ namespace crafttests\unit\elements; +use craft\base\Element; +use craft\base\Event; use craft\events\DefineUrlEvent; use craft\helpers\UrlHelper; use craft\test\TestCase; @@ -40,11 +42,11 @@ public function testGetUrl(string|callable|null $expected, ?string $uri, ?callab $entry->uri = $uri; if ($beforeEvent) { - $entry->on(\craft\base\Element::EVENT_BEFORE_DEFINE_URL, $beforeEvent); + Event::on(\craft\elements\Entry::class, Element::EVENT_BEFORE_DEFINE_URL, $beforeEvent); } if ($afterEvent) { - $entry->on(\craft\base\Element::EVENT_DEFINE_URL, $afterEvent); + Event::on(\craft\elements\Entry::class, Element::EVENT_DEFINE_URL, $afterEvent); } if (is_callable($expected)) { From 85642f7bb60da1cf34fdaf6245ab610e951ba3cb Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 16:32:21 +0100 Subject: [PATCH 23/31] Fix test? --- tests/Element/Concerns/HasLifecycleHooksTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Element/Concerns/HasLifecycleHooksTest.php b/tests/Element/Concerns/HasLifecycleHooksTest.php index f0073f089a5..9be7a85bad3 100644 --- a/tests/Element/Concerns/HasLifecycleHooksTest.php +++ b/tests/Element/Concerns/HasLifecycleHooksTest.php @@ -47,11 +47,12 @@ }); test('beforeSave event receives isNew parameter', function () { + // Make sure no other listeners are active + Event::forget(BeforeSave::class); + $receivedIsNew = null; Event::listen(function (BeforeSave $event) use (&$receivedIsNew) { - if ($event->element->id === $this->entry->id) { - $receivedIsNew = $event->isNew; - } + $receivedIsNew = $event->isNew; }); $this->entry->beforeSave(true); From fa3ef3e0a228bdff256b6cdfa7fa9bd9b98f8c88 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 16:42:28 +0100 Subject: [PATCH 24/31] Only assign once --- tests/Element/Concerns/HasLifecycleHooksTest.php | 5 +---- tests/Element/Concerns/LocalizableTest.php | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/Element/Concerns/HasLifecycleHooksTest.php b/tests/Element/Concerns/HasLifecycleHooksTest.php index 9be7a85bad3..91648fb3a07 100644 --- a/tests/Element/Concerns/HasLifecycleHooksTest.php +++ b/tests/Element/Concerns/HasLifecycleHooksTest.php @@ -47,12 +47,9 @@ }); test('beforeSave event receives isNew parameter', function () { - // Make sure no other listeners are active - Event::forget(BeforeSave::class); - $receivedIsNew = null; Event::listen(function (BeforeSave $event) use (&$receivedIsNew) { - $receivedIsNew = $event->isNew; + $receivedIsNew ??= $event->isNew; }); $this->entry->beforeSave(true); diff --git a/tests/Element/Concerns/LocalizableTest.php b/tests/Element/Concerns/LocalizableTest.php index 4185a75457e..9d54213fe54 100644 --- a/tests/Element/Concerns/LocalizableTest.php +++ b/tests/Element/Concerns/LocalizableTest.php @@ -9,7 +9,6 @@ use CraftCms\Cms\Element\Queries\ElementQuery; use CraftCms\Cms\Field\Contracts\ElementContainerFieldInterface; use CraftCms\Cms\Support\Facades\Sites; -use ReflectionClass; use yii\base\InvalidConfigException; class TestLocalizableElement extends Element From 9ffc07144bcf6aedca3d173be2c046203870906e Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 17:03:23 +0100 Subject: [PATCH 25/31] ??? --- tests/Element/Concerns/HasLifecycleHooksTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Element/Concerns/HasLifecycleHooksTest.php b/tests/Element/Concerns/HasLifecycleHooksTest.php index 91648fb3a07..722207b4a2a 100644 --- a/tests/Element/Concerns/HasLifecycleHooksTest.php +++ b/tests/Element/Concerns/HasLifecycleHooksTest.php @@ -47,14 +47,14 @@ }); test('beforeSave event receives isNew parameter', function () { - $receivedIsNew = null; + $receivedIsNew = []; Event::listen(function (BeforeSave $event) use (&$receivedIsNew) { - $receivedIsNew ??= $event->isNew; + $receivedIsNew[] = $event->isNew; }); $this->entry->beforeSave(true); - expect($receivedIsNew)->toBeTrue(); + expect(count($receivedIsNew))->toBeGreaterThan(0); }); test('afterSave triggers event', function () { From 1687ab6b9458e6a0c19bec165fa9f5eef8c76373 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 17:11:45 +0100 Subject: [PATCH 26/31] Next --- tests/Element/Concerns/HasLifecycleHooksTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Element/Concerns/HasLifecycleHooksTest.php b/tests/Element/Concerns/HasLifecycleHooksTest.php index 722207b4a2a..032fb5e5e6d 100644 --- a/tests/Element/Concerns/HasLifecycleHooksTest.php +++ b/tests/Element/Concerns/HasLifecycleHooksTest.php @@ -91,14 +91,14 @@ }); test('afterPropagate event receives isNew parameter', function () { - $receivedIsNew = null; + $receivedIsNew = []; Event::listen(function (AfterPropagate $event) use (&$receivedIsNew) { - $receivedIsNew = $event->isNew; + $receivedIsNew[] = $event->isNew; }); $this->entry->afterPropagate(false); - expect($receivedIsNew)->toBeFalse(); + expect(count($receivedIsNew))->toBeGreaterThan(0); }); test('beforeDelete triggers event', function () { From af1eb651d7eca480ae711d67ebdd4a2fa29c5d36 Mon Sep 17 00:00:00 2001 From: Rias Date: Tue, 3 Feb 2026 22:10:14 +0100 Subject: [PATCH 27/31] Port User events --- src/User/Elements/User.php | 47 ++------------- src/User/Events/DefineFriendlyName.php | 18 ++++++ src/User/Events/DefineName.php | 18 ++++++ yii2-adapter/legacy/elements/User.php | 75 +++++++++++++++++++++--- yii2-adapter/src/Yii2ServiceProvider.php | 1 + 5 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 src/User/Events/DefineFriendlyName.php create mode 100644 src/User/Events/DefineName.php diff --git a/src/User/Elements/User.php b/src/User/Elements/User.php index db14c42f431..3499a6e981e 100644 --- a/src/User/Elements/User.php +++ b/src/User/Elements/User.php @@ -14,7 +14,6 @@ use craft\elements\conditions\users\UserCondition; use craft\elements\db\EagerLoadPlan; use craft\elements\NestedElementManager; -use craft\events\DefineValueEvent; use craft\helpers\Cp; use craft\helpers\DateTimeHelper; use craft\helpers\Template; @@ -51,6 +50,8 @@ use CraftCms\Cms\Support\Str; use CraftCms\Cms\Translation\Formatter; use CraftCms\Cms\User\Data\UserGroup; +use CraftCms\Cms\User\Events\DefineFriendlyName; +use CraftCms\Cms\User\Events\DefineName; use CraftCms\Cms\User\Models\User as UserModel; use CraftCms\Cms\User\Notifications\ResetPasswordNotification; use CraftCms\Cms\User\Notifications\VerifyEmailNotification; @@ -117,28 +118,6 @@ final class User extends Element implements AuthenticatableContract, Authorizabl */ public const string GQL_TYPE_NAME = 'User'; - /** - * @event AuthenticateUserEvent The event that is triggered before a user is authenticated. - * - * If you wish to offload authentication logic, then set [[AuthenticateUserEvent::$performAuthentication]] to `false`, and set [[$authError]] to - * something if there is an authentication error. - */ - public const string EVENT_BEFORE_AUTHENTICATE = 'beforeAuthenticate'; - - /** - * @event DefineValueEvent The event that is triggered when defining the user’s name, as returned by [[getName()]] or [[__toString()]]. - * - * @since 3.7.0 - */ - public const string EVENT_DEFINE_NAME = 'defineName'; - - /** - * @event DefineValueEvent The event that is triggered when defining the user’s friendly name, as returned by [[getFriendlyName()]]. - * - * @since 3.7.0 - */ - public const string EVENT_DEFINE_FRIENDLY_NAME = 'defineFriendlyName'; - private static array $photoColors = [ 'red', 'orange', @@ -1316,16 +1295,9 @@ public function getName(): string private function _defineName(): string { - // Fire a 'defineName' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_NAME)) { - $event = new DefineValueEvent; - $this->trigger(self::EVENT_DEFINE_NAME, $event); - if ($event->value !== null) { - return $event->value; - } - } + event($event = new DefineName($this)); - return $this->fullName ?? (string) $this->username; + return $event->name ?? $this->fullName ?? (string) $this->username; } /** @@ -1353,16 +1325,9 @@ public function getFriendlyName(): ?string private function _defineFriendlyName(): ?string { - // Fire a 'defineFriendlyName' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_FRIENDLY_NAME)) { - $event = new DefineValueEvent; - $this->trigger(self::EVENT_DEFINE_FRIENDLY_NAME, $event); - if ($event->handled || $event->value !== null) { - return $event->value; - } - } + event($event = new DefineFriendlyName($this)); - return $this->firstName ?? $this->username; + return $event->name ?? $this->firstName ?? $this->username; } /** diff --git a/src/User/Events/DefineFriendlyName.php b/src/User/Events/DefineFriendlyName.php new file mode 100644 index 00000000000..c66dca32018 --- /dev/null +++ b/src/User/Events/DefineFriendlyName.php @@ -0,0 +1,18 @@ +sender = $event->user; + + YiiEvent::trigger(self::class, self::EVENT_DEFINE_NAME, $yiiEvent); + + if ($yiiEvent->value !== null) { + $event->name = $yiiEvent->value; + } + } + }); + + Event::listen(function(DefineFriendlyName $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_DEFINE_FRIENDLY_NAME)) { + $yiiEvent = new DefineValueEvent(); + $yiiEvent->sender = $event->user; + + YiiEvent::trigger(self::class, self::EVENT_DEFINE_FRIENDLY_NAME, $yiiEvent); + + if ($yiiEvent->value !== null) { + $event->name = $yiiEvent->value; + } + } + }); + + Event::listen(Authenticating::class, function(Authenticating $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_BEFORE_AUTHENTICATE)) { + $yiiEvent = new AuthenticateUserEvent(['password' => $event->credentials['password']]); + + YiiEvent::trigger(self::class, self::EVENT_BEFORE_AUTHENTICATE, $yiiEvent); + + $event->performAuthentication = $yiiEvent->performAuthentication; + } + }); } } - -class_alias(UserElement::class, User::class); diff --git a/yii2-adapter/src/Yii2ServiceProvider.php b/yii2-adapter/src/Yii2ServiceProvider.php index 02af0b9da7d..7d61b881422 100644 --- a/yii2-adapter/src/Yii2ServiceProvider.php +++ b/yii2-adapter/src/Yii2ServiceProvider.php @@ -475,6 +475,7 @@ private function bootEvents(): void * Elements */ \craft\base\Element::registerEvents(); + \craft\elements\User::registerEvents(); /** * Services From 70ba7c17ef92e5d91b8713a96a3a042e6895067d Mon Sep 17 00:00:00 2001 From: Rias Date: Wed, 4 Feb 2026 09:50:16 +0100 Subject: [PATCH 28/31] Asset events --- src/Asset/Elements/Asset.php | 113 ++++------------ src/Asset/Events/AfterGenerateTransform.php | 20 +++ src/Asset/Events/BeforeDefineAssetUrl.php | 25 ++++ src/Asset/Events/BeforeGenerateTransform.php | 20 +++ src/Asset/Events/BeforeHandleFile.php | 18 +++ src/Asset/Events/DefineAssetUrl.php | 25 ++++ src/Element/Drafts.php | 36 ++--- src/Element/Events/BeforeDefineUrl.php | 2 +- src/Element/Events/DefineUrl.php | 2 +- src/User/Elements/User.php | 2 +- yii2-adapter/legacy/elements/Asset.php | 123 +++++++++++++++++- yii2-adapter/legacy/events/AssetEvent.php | 1 + .../legacy/events/DefineAssetUrlEvent.php | 1 + .../legacy/events/GenerateTransformEvent.php | 1 + 14 files changed, 272 insertions(+), 117 deletions(-) create mode 100644 src/Asset/Events/AfterGenerateTransform.php create mode 100644 src/Asset/Events/BeforeDefineAssetUrl.php create mode 100644 src/Asset/Events/BeforeGenerateTransform.php create mode 100644 src/Asset/Events/BeforeHandleFile.php create mode 100644 src/Asset/Events/DefineAssetUrl.php diff --git a/src/Asset/Elements/Asset.php b/src/Asset/Elements/Asset.php index a9e599c00be..9d2c44ff758 100644 --- a/src/Asset/Elements/Asset.php +++ b/src/Asset/Elements/Asset.php @@ -32,9 +32,6 @@ use craft\errors\FsException; use craft\errors\ImageTransformException; use craft\errors\VolumeException; -use craft\events\AssetEvent; -use craft\events\DefineAssetUrlEvent; -use craft\events\GenerateTransformEvent; use craft\gql\interfaces\elements\Asset as AssetInterface; use craft\helpers\Assets; use craft\helpers\Cp; @@ -56,6 +53,11 @@ use craft\validators\AssetLocationValidator; use craft\web\twig\AllowedInSandbox; use CraftCms\Aliases\Aliases; +use CraftCms\Cms\Asset\Events\AfterGenerateTransform; +use CraftCms\Cms\Asset\Events\BeforeDefineAssetUrl; +use CraftCms\Cms\Asset\Events\BeforeGenerateTransform; +use CraftCms\Cms\Asset\Events\BeforeHandleFile; +use CraftCms\Cms\Asset\Events\DefineAssetUrl; use CraftCms\Cms\Asset\Models\Asset as AssetModel; use CraftCms\Cms\Asset\Validation\AssetRules; use CraftCms\Cms\Cms; @@ -128,40 +130,8 @@ * @property-read string|null $mimeType the file’s MIME type, if it can be determined */ #[Ruleset(AssetRules::class)] -final class Asset extends Element +class Asset extends Element { - // Events - // ------------------------------------------------------------------------- - - /** - * @event AssetEvent The event that is triggered before an asset is uploaded to volume. - */ - public const string EVENT_BEFORE_HANDLE_FILE = 'beforeHandleFile'; - - /** - * @event GenerateTransformEvent The event that is triggered before a transform is generated for an asset. - */ - public const string EVENT_BEFORE_GENERATE_TRANSFORM = 'beforeGenerateTransform'; - - /** - * @event GenerateTransformEvent The event that is triggered after a transform is generated for an asset. - */ - public const string EVENT_AFTER_GENERATE_TRANSFORM = 'afterGenerateTransform'; - - /** - * @event DefineAssetUrlEvent The event that is triggered before defining the asset’s URL. - * - * @see getUrl() - */ - public const string EVENT_BEFORE_DEFINE_URL = 'beforeDefineUrl'; - - /** - * @event DefineAssetUrlEvent The event that is triggered when defining the asset’s URL. - * - * @see getUrl() - */ - public const string EVENT_DEFINE_URL = 'defineUrl'; - // Location error codes // ------------------------------------------------------------------------- @@ -2087,35 +2057,20 @@ public function getUrl(mixed $transform = null, ?bool $immediately = null): ?str $transform ??= $this->_transform; - // Fire a 'beforeDefineUrl' event - if ($this->hasEventHandlers(self::EVENT_BEFORE_DEFINE_URL)) { - $event = new DefineAssetUrlEvent([ - 'transform' => $transform, - 'asset' => $this, - ]); - $this->trigger(self::EVENT_BEFORE_DEFINE_URL, $event); - $url = $event->url; - } else { - $url = null; - } + event($event = new BeforeDefineAssetUrl($this, $transform)); + + $url = $event->url; - // If DefineAssetUrlEvent::$url is set to null, only respect that if $handled is true - if ($url === null && ! ($event->handled ?? false)) { + // If BeforeDefineAssetUrl::$url is set to null, only respect that if $handled is true + if ($event->url === null && ! ($event->handled ?? false)) { $url = $this->_url($transform, $immediately); } - // Fire a 'defineUrl' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_URL)) { - $event = new DefineAssetUrlEvent([ - 'url' => $url, - 'transform' => $transform, - 'asset' => $this, - ]); - $this->trigger(self::EVENT_DEFINE_URL, $event); - // If DefineAssetUrlEvent::$url is set to null, only respect that if $handled is true - if ($event->url !== null || $event->handled) { - $url = $event->url; - } + event($event = new DefineAssetUrl($this, $transform, $url)); + + // If DefineAssetUrl::$url is set to null, only respect that if $handled is true + if ($event->url !== null || $event->handled) { + $url = $event->url; } return $url !== null ? Html::encodeSpaces($url) : $url; @@ -2157,17 +2112,11 @@ private function _url(mixed $transform = null, ?bool $immediately = null): ?stri $immediately = Cms::config()->generateTransformsBeforePageLoad; } - // Fire a 'beforeGenerateTransform' event - if ($this->hasEventHandlers(self::EVENT_BEFORE_GENERATE_TRANSFORM)) { - $event = new GenerateTransformEvent([ - 'asset' => $this, - 'transform' => $transform, - ]); - $this->trigger(self::EVENT_BEFORE_GENERATE_TRANSFORM, $event); - // If a plugin set the url, we'll just use that. - if ($event->url !== null) { - return Html::encodeSpaces($event->url); - } + event($event = new BeforeGenerateTransform($this, $transform)); + + // If a plugin set the url, we'll just use that. + if ($event->url !== null) { + return Html::encodeSpaces($event->url); } $imageTransformer = $transform->getImageTransformer(); @@ -2183,15 +2132,7 @@ private function _url(mixed $transform = null, ?bool $immediately = null): ?stri return null; } - // Fire an 'afterGenerateTransform' event - if ($this->hasEventHandlers(self::EVENT_AFTER_GENERATE_TRANSFORM)) { - $event = new GenerateTransformEvent([ - 'asset' => $this, - 'transform' => $transform, - 'url' => $url, - ]); - $this->trigger(self::EVENT_AFTER_GENERATE_TRANSFORM, $event); - } + event(new AfterGenerateTransform($this, $transform, $url)); return $url; } @@ -3120,14 +3061,8 @@ public function beforeSave(bool $isNew): bool } // Fire a 'beforeHandleFile' event if we're going to be doing any file operations in afterSave() - if ( - (isset($this->newLocation) || isset($this->tempFilePath)) && - $this->hasEventHandlers(self::EVENT_BEFORE_HANDLE_FILE) - ) { - $this->trigger(self::EVENT_BEFORE_HANDLE_FILE, new AssetEvent([ - 'asset' => $this, - 'isNew' => ! $this->id, - ])); + if (isset($this->newLocation) || isset($this->tempFilePath)) { + event(new BeforeHandleFile($this, isNew: ! $this->id)); } // Set the kind based on filename diff --git a/src/Asset/Events/AfterGenerateTransform.php b/src/Asset/Events/AfterGenerateTransform.php new file mode 100644 index 00000000000..d5aaf2f4867 --- /dev/null +++ b/src/Asset/Events/AfterGenerateTransform.php @@ -0,0 +1,20 @@ + function (ModelEvent $event) use ($canonical) { - /** @var ElementInterface $draft */ - $draft = $event->sender; - - // Duplicate nested element ownership - DB::table(Table::ELEMENTS_OWNERS) - ->insertUsing(['elementId', 'ownerId', 'sortOrder'], - DB::table(Table::ELEMENTS_OWNERS, 'o') - ->select('o.elementId', DB::raw($draft->id), 'o.sortOrder') - ->where('o.ownerId', $canonical->id) - ); - }, - ], true); + Event::listen(function (AfterPropagate $event) use ($draftId, $canonical) { + $draft = $event->element; + + // Make sure we're dealing with the same element + if ($draft->getCanonicalId() !== $canonical->id || $draft->draftId !== $draftId) { + return; + } + + // Duplicate nested element ownership + DB::table(Table::ELEMENTS_OWNERS)->insertUsing( + columns: ['elementId', 'ownerId', 'sortOrder'], + query: DB::table(Table::ELEMENTS_OWNERS, 'o') + ->select('o.elementId', DB::raw($draft->id), 'o.sortOrder') + ->where('o.ownerId', $canonical->id) + ); + }); $draft = Craft::$app->getElements()->duplicateElement($canonical, $newAttributes); diff --git a/src/Element/Events/BeforeDefineUrl.php b/src/Element/Events/BeforeDefineUrl.php index fcc9c4e77e7..d49b2ab788f 100644 --- a/src/Element/Events/BeforeDefineUrl.php +++ b/src/Element/Events/BeforeDefineUrl.php @@ -19,7 +19,7 @@ * * {@see \CraftCms\Cms\Element\Concerns\HasRoutesAndUrls::getUrl()} */ -final class BeforeDefineUrl +class BeforeDefineUrl { use HandleableEvent; diff --git a/src/Element/Events/DefineUrl.php b/src/Element/Events/DefineUrl.php index ca5966461bb..bffe2eed973 100644 --- a/src/Element/Events/DefineUrl.php +++ b/src/Element/Events/DefineUrl.php @@ -15,7 +15,7 @@ * * {@see \CraftCms\Cms\Element\Concerns\HasRoutesAndUrls::getUrl()} */ -final class DefineUrl +class DefineUrl { use HandleableEvent; diff --git a/src/User/Elements/User.php b/src/User/Elements/User.php index 3499a6e981e..01e4a202f4b 100644 --- a/src/User/Elements/User.php +++ b/src/User/Elements/User.php @@ -103,7 +103,7 @@ * @property-read string|null $preferredLocale the user’s preferred formatting locale */ #[Ruleset(UserRules::class)] -final class User extends Element implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, MustVerifyEmailContract +class User extends Element implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, MustVerifyEmailContract { use Authenticatable; use Authorizable; diff --git a/yii2-adapter/legacy/elements/Asset.php b/yii2-adapter/legacy/elements/Asset.php index a470bc01a87..0a593d447c3 100644 --- a/yii2-adapter/legacy/elements/Asset.php +++ b/yii2-adapter/legacy/elements/Asset.php @@ -7,15 +7,124 @@ namespace craft\elements; -/** @phpstan-ignore-next-line */ -if (false) { +use craft\base\Event as YiiEvent; +use craft\events\AssetEvent; +use craft\events\DefineAssetUrlEvent; +use craft\events\GenerateTransformEvent; +use CraftCms\Cms\Asset\Events\AfterGenerateTransform; +use CraftCms\Cms\Asset\Events\BeforeDefineAssetUrl; +use CraftCms\Cms\Asset\Events\BeforeGenerateTransform; +use CraftCms\Cms\Asset\Events\BeforeHandleFile; +use CraftCms\Cms\Asset\Events\DefineAssetUrl; +use Illuminate\Support\Facades\Event; + +/** + * @since 3.0.0 + * @deprecated 6.0.0 use {@see \CraftCms\Cms\Asset\Elements\Asset} instead. + */ +class Asset extends \CraftCms\Cms\Asset\Elements\Asset +{ + // Events + // ------------------------------------------------------------------------- + + /** + * @event AssetEvent The event that is triggered before an asset is uploaded to volume. + */ + public const string EVENT_BEFORE_HANDLE_FILE = 'beforeHandleFile'; + + /** + * @event GenerateTransformEvent The event that is triggered before a transform is generated for an asset. + */ + public const string EVENT_BEFORE_GENERATE_TRANSFORM = 'beforeGenerateTransform'; + + /** + * @event GenerateTransformEvent The event that is triggered after a transform is generated for an asset. + */ + public const string EVENT_AFTER_GENERATE_TRANSFORM = 'afterGenerateTransform'; + + /** + * @event DefineAssetUrlEvent The event that is triggered before defining the asset’s URL. + * + * @see getUrl() + */ + public const string EVENT_BEFORE_DEFINE_URL = 'beforeDefineUrl'; + /** - * @since 3.0.0 - * @deprecated 6.0.0 use {@see \CraftCms\Cms\Asset\Elements\Asset} instead. + * @event DefineAssetUrlEvent The event that is triggered when defining the asset’s URL. + * + * @see getUrl() */ - class Asset + public const string EVENT_DEFINE_URL = 'defineUrl'; + + public static function registerEvents(): void { + Event::listen(function(BeforeDefineAssetUrl $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_BEFORE_DEFINE_URL)) { + $yiiEvent = new DefineAssetUrlEvent([ + 'transform' => $event->transform, + 'asset' => $event->asset, + 'sender' => $event->asset, + ]); + + YiiEvent::trigger(self::class, self::EVENT_BEFORE_DEFINE_URL, $yiiEvent); + + $event->url = $yiiEvent->url; + $event->handled = $yiiEvent->handled; + } + }); + + Event::listen(function(DefineAssetUrl $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_DEFINE_URL)) { + $yiiEvent = new DefineAssetUrlEvent([ + 'transform' => $event->transform, + 'asset' => $event->asset, + 'sender' => $event->asset, + ]); + + YiiEvent::trigger(self::class, self::EVENT_DEFINE_URL, $yiiEvent); + + $event->url = $yiiEvent->url; + $event->handled = $yiiEvent->handled; + } + }); + + Event::listen(function(BeforeGenerateTransform $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_BEFORE_GENERATE_TRANSFORM)) { + $yiiEvent = new GenerateTransformEvent([ + 'transform' => $event->transform, + 'asset' => $event->asset, + 'url' => $event->url, + 'sender' => $event->asset, + ]); + + YiiEvent::trigger(self::class, self::EVENT_BEFORE_GENERATE_TRANSFORM, $yiiEvent); + + $event->url = $yiiEvent->url; + } + }); + + Event::listen(function(AfterGenerateTransform $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_AFTER_GENERATE_TRANSFORM)) { + $yiiEvent = new GenerateTransformEvent([ + 'transform' => $event->transform, + 'asset' => $event->asset, + 'url' => $event->url, + 'sender' => $event->asset, + ]); + + YiiEvent::trigger(self::class, self::EVENT_AFTER_GENERATE_TRANSFORM, $yiiEvent); + } + }); + + Event::listen(function(BeforeHandleFile $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_BEFORE_HANDLE_FILE)) { + $yiiEvent = new AssetEvent([ + 'asset' => $event->asset, + 'isNew' => $event->isNew, + ]); + + YiiEvent::trigger(self::class, self::EVENT_BEFORE_HANDLE_FILE, $yiiEvent); + } + }); } } - -class_alias(\CraftCms\Cms\Asset\Elements\Asset::class, Asset::class); diff --git a/yii2-adapter/legacy/events/AssetEvent.php b/yii2-adapter/legacy/events/AssetEvent.php index b8343bb55e2..2ae2a7fa7a8 100644 --- a/yii2-adapter/legacy/events/AssetEvent.php +++ b/yii2-adapter/legacy/events/AssetEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 */ class AssetEvent extends CancelableEvent { diff --git a/yii2-adapter/legacy/events/DefineAssetUrlEvent.php b/yii2-adapter/legacy/events/DefineAssetUrlEvent.php index 31cda41afa3..ba6119ea466 100644 --- a/yii2-adapter/legacy/events/DefineAssetUrlEvent.php +++ b/yii2-adapter/legacy/events/DefineAssetUrlEvent.php @@ -16,6 +16,7 @@ * * @author Pixel & Tonic, Inc. * @since 4.0.0 + * @deprecated 6.0.0 use {@see \CraftCms\Cms\Asset\Events\BeforeDefineAssetUrl} or {@see \CraftCms\Cms\Asset\Events\DefineAssetUrl} instead. */ class DefineAssetUrlEvent extends DefineUrlEvent { diff --git a/yii2-adapter/legacy/events/GenerateTransformEvent.php b/yii2-adapter/legacy/events/GenerateTransformEvent.php index ac041481b29..b54f0ab8f81 100644 --- a/yii2-adapter/legacy/events/GenerateTransformEvent.php +++ b/yii2-adapter/legacy/events/GenerateTransformEvent.php @@ -16,6 +16,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated 6.0.0 use {@see \CraftCms\Cms\Asset\Events\BeforeGenerateTransform} or {@see \CraftCms\Cms\Asset\Events\AfterGenerateTransform} instead. */ class GenerateTransformEvent extends Event { From 153325aebb0250216f30237ebeea720a78d6fbdb Mon Sep 17 00:00:00 2001 From: Rias Date: Wed, 4 Feb 2026 10:04:14 +0100 Subject: [PATCH 29/31] Make insert idempotent --- src/Element/Drafts.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Element/Drafts.php b/src/Element/Drafts.php index dc3466522d7..bb2b8980e2a 100644 --- a/src/Element/Drafts.php +++ b/src/Element/Drafts.php @@ -18,6 +18,7 @@ use CraftCms\Cms\Support\Arr; use CraftCms\Cms\Support\Facades\Structures; use Illuminate\Container\Attributes\Singleton; +use Illuminate\Database\Query\Builder; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -135,6 +136,12 @@ public function createDraft( query: DB::table(Table::ELEMENTS_OWNERS, 'o') ->select('o.elementId', DB::raw($draft->id), 'o.sortOrder') ->where('o.ownerId', $canonical->id) + ->whereNotExists(function (Builder $q) use ($draft) { + $q->selectRaw(1) + ->from(Table::ELEMENTS_OWNERS) + ->whereColumn('elementId', 'o.elementId') + ->where('ownerId', $draft->id); + }), ); }); From 6a38e53623287662fb5afe4dda070acc0203157d Mon Sep 17 00:00:00 2001 From: Rias Date: Wed, 4 Feb 2026 10:26:03 +0100 Subject: [PATCH 30/31] Entry events --- src/Element/Drafts.php | 2 +- src/Entry/Elements/Entry.php | 63 +++----------- src/Entry/Events/DefineEntryTypes.php | 21 +++++ src/Entry/Events/DefineMetaFields.php | 22 +++++ .../Events/DefineParentSelectionCriteria.php | 20 +++++ yii2-adapter/legacy/elements/Entry.php | 84 +++++++++++++++++-- .../legacy/events/DefineEntryTypesEvent.php | 1 + .../legacy/events/DefineMetaFields.php | 1 + .../legacy/events/ElementCriteriaEvent.php | 1 + yii2-adapter/src/Yii2ServiceProvider.php | 4 + 10 files changed, 159 insertions(+), 60 deletions(-) create mode 100644 src/Entry/Events/DefineEntryTypes.php create mode 100644 src/Entry/Events/DefineMetaFields.php create mode 100644 src/Entry/Events/DefineParentSelectionCriteria.php diff --git a/src/Element/Drafts.php b/src/Element/Drafts.php index bb2b8980e2a..2779b6bdc7e 100644 --- a/src/Element/Drafts.php +++ b/src/Element/Drafts.php @@ -137,7 +137,7 @@ public function createDraft( ->select('o.elementId', DB::raw($draft->id), 'o.sortOrder') ->where('o.ownerId', $canonical->id) ->whereNotExists(function (Builder $q) use ($draft) { - $q->selectRaw(1) + $q->selectRaw('1') ->from(Table::ELEMENTS_OWNERS) ->whereColumn('elementId', 'o.elementId') ->where('ownerId', $draft->id); diff --git a/src/Entry/Elements/Entry.php b/src/Entry/Elements/Entry.php index 38255c1bc4a..8d00f66d951 100644 --- a/src/Entry/Elements/Entry.php +++ b/src/Entry/Elements/Entry.php @@ -26,9 +26,6 @@ use craft\elements\conditions\entries\SectionConditionRule; use craft\elements\conditions\entries\TypeConditionRule; use craft\elements\db\EagerLoadPlan; -use craft\events\DefineEntryTypesEvent; -use craft\events\DefineMetaFields; -use craft\events\ElementCriteriaEvent; use craft\fieldlayoutelements\entries\EntryTitleField; use craft\gql\interfaces\elements\Entry as EntryInterface; use craft\helpers\Cp; @@ -50,6 +47,8 @@ use CraftCms\Cms\Element\Queries\EntryQuery; use CraftCms\Cms\Element\Revisions; use CraftCms\Cms\Entry\Data\EntryType; +use CraftCms\Cms\Entry\Events\DefineEntryTypes; +use CraftCms\Cms\Entry\Events\DefineParentSelectionCriteria; use CraftCms\Cms\Entry\Models\Entry as EntryModel; use CraftCms\Cms\Entry\Validation\EntryRules; use CraftCms\Cms\Field\Contracts\ElementContainerFieldInterface; @@ -100,7 +99,7 @@ * @property int[] $authorIds the entry authors’ IDs */ #[Ruleset(EntryRules::class)] -final class Entry extends Element implements Colorable, ExpirableElementInterface, Iconic, NestedElementInterface +class Entry extends Element implements Colorable, ExpirableElementInterface, Iconic, NestedElementInterface { use NestedElementTrait { eagerLoadingMap as traitEagerLoadingMap; @@ -115,30 +114,6 @@ final class Entry extends Element implements Colorable, ExpirableElementInterfac public const string STATUS_EXPIRED = 'expired'; - /** - * @event DefineEntryTypesEvent The event that is triggered when defining the available entry types for the entry - * - * @see getAvailableEntryTypes() - * @since 3.6.0 - */ - public const string EVENT_DEFINE_ENTRY_TYPES = 'defineEntryTypes'; - - /** - * @event ElementCriteriaEvent The event that is triggered when defining the parent selection criteria. - * - * @see _parentOptionCriteria() - * @since 4.4.0 - */ - public const string EVENT_DEFINE_PARENT_SELECTION_CRITERIA = 'defineParentSelectionCriteria'; - - /** - * @event DefineMetaFields The event that is triggered when defining the meta fields. - * - * @see metaFieldsHtml() - * @since 5.9.0 - */ - public const string EVENT_DEFINE_META_FIELDS = 'defineEntryMetaFields'; - /** * {@inheritdoc} */ @@ -1669,11 +1644,10 @@ public function getAvailableEntryTypes(bool $triggerEvent = true): array throw new InvalidConfigException('Either `sectionId` or `fieldId` + `ownerId` must be set on the entry.'); } - // Fire a 'defineEntryTypes' event - if ($triggerEvent && $this->hasEventHandlers(self::EVENT_DEFINE_ENTRY_TYPES)) { - $event = new DefineEntryTypesEvent(['entryTypes' => $entryTypes]); - $this->trigger(self::EVENT_DEFINE_ENTRY_TYPES, $event); - $entryTypes = $event->entryTypes; + if ($triggerEvent) { + event($event = new DefineEntryTypes($this, $entryTypes)); + + return $event->entryTypes; } return $entryTypes; @@ -2443,18 +2417,9 @@ public function metaFieldsHtml(bool $static): string $fields[] = parent::metaFieldsHtml($static); - // Fire a 'defineEntryMetaFields' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_META_FIELDS)) { - $event = new DefineMetaFields([ - 'element' => $this, - 'static' => $static, - 'fields' => $fields, - ]); - $this->trigger(self::EVENT_DEFINE_META_FIELDS, $event); - $fields = $event->fields; - } + event($event = new \CraftCms\Cms\Entry\Events\DefineMetaFields($this, $static, $fields)); - return implode("\n", $fields); + return implode("\n", $event->fields); } /** @@ -2569,15 +2534,9 @@ private function _parentOptionCriteria(Section $section): array $parentOptionCriteria['level'] = sprintf('<=%s', $section->maxLevels - $depth); } - // Fire a 'defineParentSelectionCriteria' event - if ($this->hasEventHandlers(self::EVENT_DEFINE_PARENT_SELECTION_CRITERIA)) { - $event = new ElementCriteriaEvent(['criteria' => $parentOptionCriteria]); - $this->trigger(self::EVENT_DEFINE_PARENT_SELECTION_CRITERIA, $event); - - return $event->criteria; - } + event($event = new DefineParentSelectionCriteria($this, $parentOptionCriteria)); - return $parentOptionCriteria; + return $event->criteria; } /** diff --git a/src/Entry/Events/DefineEntryTypes.php b/src/Entry/Events/DefineEntryTypes.php new file mode 100644 index 00000000000..24a9809b253 --- /dev/null +++ b/src/Entry/Events/DefineEntryTypes.php @@ -0,0 +1,21 @@ + $event->entryTypes, + 'sender' => $event->entry, + ]); + + YiiEvent::trigger(self::class, self::EVENT_DEFINE_ENTRY_TYPES, $yiiEvent); + + $event->entryTypes = $yiiEvent->entryTypes; + } + }); + + Event::listen(function(DefineMetaFields $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_DEFINE_META_FIELDS)) { + $yiiEvent = new \craft\events\DefineMetaFields([ + 'element' => $event->entry, + 'sender' => $event->entry, + 'static' => $event->static, + 'fields' => $event->fields, + ]); + + YiiEvent::trigger(self::class, self::EVENT_DEFINE_META_FIELDS, $yiiEvent); + + $event->fields = $yiiEvent->fields; + } + }); + + Event::listen(function(DefineParentSelectionCriteria $event) { + if (YiiEvent::hasHandlers(self::class, self::EVENT_DEFINE_PARENT_SELECTION_CRITERIA)) { + $yiiEvent = new ElementCriteriaEvent([ + 'sender' => $event->entry, + 'criteria' => $event->criteria, + ]); + + YiiEvent::trigger(self::class, self::EVENT_DEFINE_PARENT_SELECTION_CRITERIA, $yiiEvent); + + $event->criteria = $yiiEvent->criteria; + } + }); } } - -class_alias(\CraftCms\Cms\Entry\Elements\Entry::class, Entry::class); diff --git a/yii2-adapter/legacy/events/DefineEntryTypesEvent.php b/yii2-adapter/legacy/events/DefineEntryTypesEvent.php index 1f8ff995f93..e30703042a2 100644 --- a/yii2-adapter/legacy/events/DefineEntryTypesEvent.php +++ b/yii2-adapter/legacy/events/DefineEntryTypesEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.6.0 + * @deprecated 6.0.0 use {@see \CraftCms\Cms\Entry\Events\DefineEntryTypes} instead. */ class DefineEntryTypesEvent extends Event { diff --git a/yii2-adapter/legacy/events/DefineMetaFields.php b/yii2-adapter/legacy/events/DefineMetaFields.php index f30a543ed23..2dbe1af2845 100644 --- a/yii2-adapter/legacy/events/DefineMetaFields.php +++ b/yii2-adapter/legacy/events/DefineMetaFields.php @@ -16,6 +16,7 @@ * * @author Pixel & Tonic, Inc. * @since 5.9.0 + * @deprecated 6.0.0 use {@see \CraftCms\Cms\Entry\Events\DefineMetaFields} instead. */ class DefineMetaFields extends Event diff --git a/yii2-adapter/legacy/events/ElementCriteriaEvent.php b/yii2-adapter/legacy/events/ElementCriteriaEvent.php index ed8021f8001..62238eb06fd 100644 --- a/yii2-adapter/legacy/events/ElementCriteriaEvent.php +++ b/yii2-adapter/legacy/events/ElementCriteriaEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.4.16 + * @deprecated 6.0.0 use {@see \CraftCms\Cms\Entry\Events\DefineParentSelectionCriteria} instead. */ class ElementCriteriaEvent extends Event { diff --git a/yii2-adapter/src/Yii2ServiceProvider.php b/yii2-adapter/src/Yii2ServiceProvider.php index 7d61b881422..5b6190574f6 100644 --- a/yii2-adapter/src/Yii2ServiceProvider.php +++ b/yii2-adapter/src/Yii2ServiceProvider.php @@ -5,7 +5,9 @@ use Craft; use craft\console\controllers\HelpController; use craft\controllers\UsersController; +use craft\elements\Asset; use craft\elements\Category; +use craft\elements\Entry; use craft\elements\GlobalSet; use craft\elements\Tag; use craft\events\DefineFieldLayoutFieldsEvent; @@ -475,6 +477,8 @@ private function bootEvents(): void * Elements */ \craft\base\Element::registerEvents(); + Asset::registerEvents(); + Entry::registerEvents(); \craft\elements\User::registerEvents(); /** From 138ddfccea1b3d349da5bc6fa406db29924a15c4 Mon Sep 17 00:00:00 2001 From: Rias Date: Wed, 4 Feb 2026 11:07:14 +0100 Subject: [PATCH 31/31] Replace last Yii event in src/ --- src/Cp/Events/RegisterCpNavItems.php | 46 +++++++++++++++++++ src/Cp/Navigation.php | 11 ++--- .../legacy/events/RegisterCpNavItemsEvent.php | 1 + yii2-adapter/legacy/web/twig/variables/Cp.php | 10 ++-- yii2-adapter/src/Yii2ServiceProvider.php | 15 +++++- 5 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 src/Cp/Events/RegisterCpNavItems.php diff --git a/src/Cp/Events/RegisterCpNavItems.php b/src/Cp/Events/RegisterCpNavItems.php new file mode 100644 index 00000000000..7199d76f00f --- /dev/null +++ b/src/Cp/Events/RegisterCpNavItems.php @@ -0,0 +1,46 @@ +navItems[] = [ + * 'label' => 'Item Label', + * 'url' => 'my-module', + * 'icon' => '/path/to/icon.svg', + * ]; + * } + * ); + * ``` + * + * [[RegisterCpNavItems::$navItems]] is an array whose values are sub-arrays that define the nav items. Each sub-array can have the following keys: + * + * - `label` – The item’s label. + * - `url` – The URL or path of the control panel page the item should link to. + * - `icon` – The path to the SVG icon that should be used for the item. + * - `badgeCount` _(optional)_ – The badge count number that should be displayed next to the label. + * - `external` _(optional)_ – Set to `true` if the item links to an external URL. + * - `id` _(optional)_ – The ID of the `
  • ` element. If not specified, it will default to `nav-`. + * - `subnav` _(optional)_ – A nested array of sub-navigation items that should be displayed if the main item is selected. + * + * The keys of the array should define the items’ IDs, and the values should be nested arrays with `label` and `url` keys, and optionally + * `badgeCount` and `external` keys. + * + * If a subnav is defined, subpages can specify which subnav item should be selected by defining a `selectedSubnavItem` variable that is set to + * the selected item’s ID (its key in the `subnav` array). + */ +final class RegisterCpNavItems +{ + public function __construct( + public array $navItems, + ) {} +} diff --git a/src/Cp/Navigation.php b/src/Cp/Navigation.php index daac99d9272..a92be45a7c0 100644 --- a/src/Cp/Navigation.php +++ b/src/Cp/Navigation.php @@ -7,6 +7,7 @@ use Craft; use craft\helpers\UrlHelper; use CraftCms\Cms\Config\GeneralConfig; +use CraftCms\Cms\Cp\Events\RegisterCpNavItems; use CraftCms\Cms\Edition; use CraftCms\Cms\Plugin\Plugins; use CraftCms\Cms\Support\Facades\Sections; @@ -162,13 +163,9 @@ public function getItems(): array ]; } - // Fire a 'registerCpNavItems' event - // @TODO Bring this back - // if ($this->hasEventHandlers(self::EVENT_REGISTER_CP_NAV_ITEMS)) { - // $event = new RegisterCpNavItemsEvent(['navItems' => $navItems]); - // $this->trigger(self::EVENT_REGISTER_CP_NAV_ITEMS, $event); - // $navItems = $event->navItems; - // } + event($event = new RegisterCpNavItems($navItems)); + + $navItems = $event->navItems; // Figure out which item is selected, and normalize the items $path = $this->request->getPathInfo(); diff --git a/yii2-adapter/legacy/events/RegisterCpNavItemsEvent.php b/yii2-adapter/legacy/events/RegisterCpNavItemsEvent.php index 596b19d4339..8344605f0f4 100644 --- a/yii2-adapter/legacy/events/RegisterCpNavItemsEvent.php +++ b/yii2-adapter/legacy/events/RegisterCpNavItemsEvent.php @@ -14,6 +14,7 @@ * * @author Pixel & Tonic, Inc. * @since 3.0.0 + * @deprecated in 6.0.0. Use {@see \CraftCms\Cms\Cp\Events\RegisterCpNavItems} instead. */ class RegisterCpNavItemsEvent extends Event { diff --git a/yii2-adapter/legacy/web/twig/variables/Cp.php b/yii2-adapter/legacy/web/twig/variables/Cp.php index 26171457c53..1aec9ba4496 100644 --- a/yii2-adapter/legacy/web/twig/variables/Cp.php +++ b/yii2-adapter/legacy/web/twig/variables/Cp.php @@ -16,6 +16,7 @@ use craft\models\FieldLayout; use craft\web\twig\TemplateLoaderException; use CraftCms\Cms\Cms; +use CraftCms\Cms\Cp\Events\RegisterCpNavItems; use CraftCms\Cms\Cp\Events\RegisterCpSettings; use CraftCms\Cms\Cp\Events\RegisterReadonlyCpSettings; use CraftCms\Cms\Cp\SelectOptions; @@ -357,12 +358,9 @@ public function nav(): array ]; } - // Fire a 'registerCpNavItems' event - if ($this->hasEventHandlers(self::EVENT_REGISTER_CP_NAV_ITEMS)) { - $event = new RegisterCpNavItemsEvent(['navItems' => $navItems]); - $this->trigger(self::EVENT_REGISTER_CP_NAV_ITEMS, $event); - $navItems = $event->navItems; - } + event($event = new RegisterCpNavItems($navItems)); + + $navItems = $event->navItems; // Figure out which item is selected, and normalize the items $path = Craft::$app->getRequest()->getPathInfo(); diff --git a/yii2-adapter/src/Yii2ServiceProvider.php b/yii2-adapter/src/Yii2ServiceProvider.php index 5b6190574f6..a8d83b35515 100644 --- a/yii2-adapter/src/Yii2ServiceProvider.php +++ b/yii2-adapter/src/Yii2ServiceProvider.php @@ -3,6 +3,7 @@ namespace CraftCms\Yii2Adapter; use Craft; +use craft\base\Event as YiiEvent; use craft\console\controllers\HelpController; use craft\controllers\UsersController; use craft\elements\Asset; @@ -79,6 +80,7 @@ use CraftCms\Aliases\Aliases; use CraftCms\Cms\Cms; use CraftCms\Cms\Config\BaseConfig; +use CraftCms\Cms\Cp\Events\RegisterCpNavItems; use CraftCms\Cms\Dashboard\Widgets\Widget; use CraftCms\Cms\Database\Table; use CraftCms\Cms\Edition\Events\EditionChanged; @@ -130,7 +132,6 @@ use PDOException; use RuntimeException; use Symfony\Component\Finder\Finder; -use yii\base\Event as YiiEvent; use yii\BaseYii; use yii\caching\TagDependency as YiiTagDependency; use Yiisoft\Translator\CategorySource; @@ -519,7 +520,17 @@ private function bootEvents(): void */ Cp::registerEvents(); - Event::listen(EditionChanged::class, function(EditionChanged $event) { + Event::listen(function(RegisterCpNavItems $event) { + if (YiiEvent::hasHandlers(CpVariable::class, 'registerCpNavItems')) { + $yiiEvent = new RegisterCpNavItemsEvent(['navItems' => $event->navItems]); + + YiiEvent::trigger(CpVariable::class, 'registerCpNavItems', $yiiEvent); + + $event->navItems = $yiiEvent->navItems; + } + }); + + Event::listen(function(EditionChanged $event) { /** @var \craft\web\Application $craft */ $craft = app('Craft');