Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d8b8a3d
Add validated component type catalogs
riasvdv Jul 25, 2026
6334d3c
Fold auth registration into AuthMethods
riasvdv Jul 25, 2026
b6ce1ea
Register asset file kinds explicitly
riasvdv Jul 25, 2026
c59994a
Register element types explicitly
riasvdv Jul 25, 2026
2981b17
Register field and link types explicitly
riasvdv Jul 25, 2026
470e8e3
Register native field layout fields explicitly
riasvdv Jul 25, 2026
5227075
Register dashboard widget types explicitly
riasvdv Jul 25, 2026
4aa5c75
Register filesystem types explicitly
riasvdv Jul 25, 2026
43d81c1
Register image transformers explicitly
riasvdv Jul 25, 2026
e281536
Register utility types and cache options explicitly
riasvdv Jul 25, 2026
2c767ca
Register GraphQL extensions explicitly
riasvdv Jul 25, 2026
573e89f
Move control panel registration into Settings
riasvdv Jul 25, 2026
d59568a
Move system message registration into SystemMessages
riasvdv Jul 25, 2026
b59a07f
Register user permission groups explicitly
riasvdv Jul 25, 2026
0ec380b
Register template roots and cache collectors explicitly
riasvdv Jul 25, 2026
6b1fc67
Register migration tracks explicitly
riasvdv Jul 25, 2026
315736f
Discover resave commands through Artisan
riasvdv Jul 25, 2026
bd90724
Wire legacy registration compatibility
riasvdv Jul 25, 2026
e8e89f1
Update registry migration references
riasvdv Jul 25, 2026
b431c9c
Clarify test suite commands
riasvdv Jul 25, 2026
8c13bff
Merge remote-tracking branch 'origin/6.x' into feature/registries
riasvdv Jul 25, 2026
8739a8d
Fix registry compatibility checks
riasvdv Jul 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ This is a large codebase with some large files. Search narrowly before reading f
### PHP

```bash
composer tests # Run all Pest tests
composer tests # Run main tests only (tests/)
composer tests-adapter # Run yii2-adapter tests only
./vendor/bin/pest path/to/TestFile.php # Run a single test file
./vendor/bin/pest --filter "test description" # Run tests matching a name
./vendor/bin/pest tests/path/to/TestFile.php # Run a main test file
composer tests-adapter -- yii2-adapter/tests-laravel/path/to/TestFile.php
composer tests-adapter -- --filter "test description"
composer fix-cs # Run Rector + Pint + ECS (auto-fixes code style)
composer phpstan # Run PHPStan static analysis (level 5)
composer ci # Full CI pipeline: pint, rector, phpstan, tests, tests-adapter
Expand All @@ -44,6 +45,7 @@ npm run test:ui # Vitest tests for the @craftcms/ui package

## Testing

- Main and adapter tests are separate Pest suites. Never mix `tests/...` and `yii2-adapter/tests-laravel/...` in one Pest invocation. Run adapter tests through `composer tests-adapter`; passing the adapter PHPUnit configuration alone is insufficient because Pest also needs the adapter test directory to load `yii2-adapter/tests-laravel/Pest.php`.
- Pest tests using `tests/TestCase.php` or `yii2-adapter/tests-laravel/TestCase.php` share a database lock. If another process has the lock, the next process will wait and print `Another Pest process is already using the shared test database. Waiting for the lock...`.
- `tests/Unit/` tests using `UnitTestCase` do not take that lock and can still run concurrently.
- When writing tests, prefer real code paths, or use Laravel facades to set up service mocks.
Expand Down
13 changes: 6 additions & 7 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Craft's Mutex classes have been deprecated. [Laravel's atomic locking](https://l

##### Events

- Deprecated `craft\services\Dashboard::EVENT_REGISTER_WIDGET_TYPES`. `CraftCms\Cms\Dashboard\Events\WidgetTypesResolving` should be used instead.
- Deprecated `craft\services\Dashboard::EVENT_REGISTER_WIDGET_TYPES`. `CraftCms\Cms\Dashboard\WidgetTypes::register()` should be used instead.
- Deprecated `craft\events\WidgetEvent` in favor of the following new events:
- `craft\services\Dashboard::EVENT_BEFORE_SAVE_WIDGET` => `CraftCms\Cms\Dashboard\Events\WidgetSaving`
- `craft\services\Dashboard::EVENT_AFTER_SAVE_WIDGET` => `CraftCms\Cms\Dashboard\Events\WidgetSaved`
Expand Down Expand Up @@ -601,7 +601,7 @@ Craft 6 introduces a new validation system that uses Laravel's Validator instead
- Deprecated `craft\events\CreateFieldLayoutFormEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutFormCreating` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutCustomFieldsEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutCustomFieldsResolving` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutElementsEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutUIElementsResolving` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutFieldsEvent`. `CraftCms\Cms\FieldLayout\Events\NativeFieldsResolving` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutFieldsEvent`. `CraftCms\Cms\FieldLayout\NativeFields` should be used instead.
- Deprecated `craft\events\DefineShowFieldLayoutComponentInFormEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutComponentShowInFormResolving` should be used instead.
- Deprecated `craft\events\DefineFieldActionsEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutActionMenuItemsResolving` should be used instead.

Expand Down Expand Up @@ -1048,14 +1048,13 @@ Moved the following controllers:
- Added `CraftCms\Cms\View\DeltaRegistry`.
- Added `CraftCms\Cms\Support\Facades\DeltaRegistry`.
- Added `CraftCms\Cms\View\TemplateMode` enum.
- Added `CraftCms\Cms\View\Events\CpTemplateRootsResolving`.
- Added `CraftCms\Cms\View\Events\SiteTemplateRootsResolving`.
- Added `CraftCms\Cms\View\TemplateRoots`.
- Added `CraftCms\Cms\View\TemplateCaches`.
- Added `CraftCms\Cms\View\CacheCollectors\DependencyCollector`.
- Added `CraftCms\Cms\View\CacheCollectors\ResourceCollector`.
- Added `CraftCms\Cms\View\Contracts\CacheCollectorInterface`.
- Added `CraftCms\Cms\View\Data\TemplateCacheContext`.
- Added `CraftCms\Cms\View\Events\TemplateCacheCollectorsResolving`.
- Added `CraftCms\Cms\View\TemplateCacheCollectors`.
- Deprecated `craft\services\TemplateCaches`. `CraftCms\Cms\View\TemplateCaches` should be used instead.
- Deprecated `craft\web\View::registerJs()`. `CraftCms\Cms\View\HtmlStack::js()` should be used instead.
- Deprecated `craft\web\View::registerJsWithVars()`. `CraftCms\Cms\View\HtmlStack::jsWithVars()` should be used instead.
Expand Down Expand Up @@ -1098,8 +1097,8 @@ Moved the following controllers:
- Deprecated `craft\web\View::getTemplatesPath()`. `CraftCms\Cms\View\TemplateMode::templatesPath()` should be used instead.
- Deprecated `craft\web\View::getCpTemplateRoots()`. `CraftCms\Cms\View\TemplateMode::templateRoots()` should be used instead.
- Deprecated `craft\web\View::getSiteTemplateRoots()`. `CraftCms\Cms\View\TemplateMode::templateRoots()` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_CP_TEMPLATE_ROOTS`. `CraftCms\Cms\View\Events\CpTemplateRootsResolving` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_SITE_TEMPLATE_ROOTS`. `CraftCms\Cms\View\Events\SiteTemplateRootsResolving` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_CP_TEMPLATE_ROOTS`. `CraftCms\Cms\View\TemplateRoots::register()` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_SITE_TEMPLATE_ROOTS`. `CraftCms\Cms\View\TemplateRoots::register()` should be used instead.
- Deprecated `craft\web\View::registerDeltaName()`. `CraftCms\Cms\View\DeltaRegistry::registerName()` should be used instead.
- Deprecated `craft\web\View::getDeltaNames()`. `CraftCms\Cms\View\DeltaRegistry::getNames()` should be used instead.
- Deprecated `craft\web\View::getModifiedDeltaNames()`. `CraftCms\Cms\View\DeltaRegistry::getModifiedNames()` should be used instead.
Expand Down
13 changes: 6 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ Craft’s Mutex classes have been deprecated. [Laravel’s atomic locking](https

##### Events

- Deprecated `craft\services\Dashboard::EVENT_REGISTER_WIDGET_TYPES`. `CraftCms\Cms\Dashboard\Events\WidgetTypesResolving` should be used instead.
- Deprecated `craft\services\Dashboard::EVENT_REGISTER_WIDGET_TYPES`. `CraftCms\Cms\Dashboard\WidgetTypes::register()` should be used instead.
- Deprecated `craft\events\WidgetEvent` in favor of the following new events:
- `craft\services\Dashboard::EVENT_BEFORE_SAVE_WIDGET` => `CraftCms\Cms\Dashboard\Events\WidgetSaving`
- `craft\services\Dashboard::EVENT_AFTER_SAVE_WIDGET` => `CraftCms\Cms\Dashboard\Events\WidgetSaved`
Expand Down Expand Up @@ -946,7 +946,7 @@ Craft 6 introduces a new validation system that uses Laravel’s Validator inste
- Deprecated `craft\events\CreateFieldLayoutFormEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutFormCreating` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutCustomFieldsEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutCustomFieldsResolving` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutElementsEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutUIElementsResolving` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutFieldsEvent`. `CraftCms\Cms\FieldLayout\Events\NativeFieldsResolving` should be used instead.
- Deprecated `craft\events\DefineFieldLayoutFieldsEvent`. `CraftCms\Cms\FieldLayout\NativeFields` should be used instead.
- Deprecated `craft\events\DefineShowFieldLayoutComponentInFormEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutComponentShowInFormResolving` should be used instead.
- Deprecated `craft\events\DefineFieldActionsEvent`. `CraftCms\Cms\FieldLayout\Events\FieldLayoutActionMenuItemsResolving` should be used instead.

Expand Down Expand Up @@ -1391,14 +1391,13 @@ Moved the following controllers:
- Added `CraftCms\Cms\View\DeltaRegistry`.
- Added `CraftCms\Cms\Support\Facades\DeltaRegistry`.
- Added `CraftCms\Cms\View\TemplateMode` enum.
- Added `CraftCms\Cms\View\Events\CpTemplateRootsResolving`.
- Added `CraftCms\Cms\View\Events\SiteTemplateRootsResolving`.
- Added `CraftCms\Cms\View\TemplateRoots`.
- Added `CraftCms\Cms\View\TemplateCaches`.
- Added `CraftCms\Cms\View\CacheCollectors\DependencyCollector`.
- Added `CraftCms\Cms\View\CacheCollectors\ResourceCollector`.
- Added `CraftCms\Cms\View\Contracts\CacheCollectorInterface`.
- Added `CraftCms\Cms\View\Data\TemplateCacheContext`.
- Added `CraftCms\Cms\View\Events\TemplateCacheCollectorsResolving`.
- Added `CraftCms\Cms\View\TemplateCacheCollectors`.
- Deprecated `craft\services\TemplateCaches`. `CraftCms\Cms\View\TemplateCaches` should be used instead.
- Deprecated `craft\web\View::registerJs()`. `CraftCms\Cms\View\HtmlStack::js()` should be used instead.
- Deprecated `craft\web\View::registerJsWithVars()`. `CraftCms\Cms\View\HtmlStack::jsWithVars()` should be used instead.
Expand Down Expand Up @@ -1441,8 +1440,8 @@ Moved the following controllers:
- Deprecated `craft\web\View::getTemplatesPath()`. `CraftCms\Cms\View\TemplateMode::templatesPath()` should be used instead.
- Deprecated `craft\web\View::getCpTemplateRoots()`. `CraftCms\Cms\View\TemplateMode::templateRoots()` should be used instead.
- Deprecated `craft\web\View::getSiteTemplateRoots()`. `CraftCms\Cms\View\TemplateMode::templateRoots()` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_CP_TEMPLATE_ROOTS`. `CraftCms\Cms\View\Events\CpTemplateRootsResolving` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_SITE_TEMPLATE_ROOTS`. `CraftCms\Cms\View\Events\SiteTemplateRootsResolving` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_CP_TEMPLATE_ROOTS`. `CraftCms\Cms\View\TemplateRoots::register()` should be used instead.
- Deprecated `craft\web\View::EVENT_REGISTER_SITE_TEMPLATE_ROOTS`. `CraftCms\Cms\View\TemplateRoots::register()` should be used instead.
- Deprecated `craft\web\View::registerDeltaName()`. `CraftCms\Cms\View\DeltaRegistry::registerName()` should be used instead.
- Deprecated `craft\web\View::getDeltaNames()`. `CraftCms\Cms\View\DeltaRegistry::getNames()` should be used instead.
- Deprecated `craft\web\View::getModifiedDeltaNames()`. `CraftCms\Cms\View\DeltaRegistry::getModifiedNames()` should be used instead.
Expand Down
91 changes: 91 additions & 0 deletions src/Asset/AssetFileKinds.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Asset;

use Closure;
use CraftCms\Cms\Asset\Enums\FileKind;
use CraftCms\Cms\Config\GeneralConfig;
use CraftCms\Cms\Support\Arr;
use Illuminate\Container\Attributes\Singleton;
use InvalidArgumentException;

/**
* Registers additional asset file kinds or replaces built-in definitions.
*
* ```php
* public function boot(AssetFileKinds $fileKinds): void
* {
* $fileKinds->register('drawing', [
* 'label' => 'Drawing',
* 'extensions' => ['dwg'],
* ]);
* }
* ```
*/
#[Singleton]
class AssetFileKinds
{
/** @var array<string, array|Closure> */
private array $fileKinds = [];

/** @var array<string, true> */
private array $removedFileKinds = [];

public function __construct(
private readonly GeneralConfig $generalConfig,
) {}

/**
* @param array{label?:string, extensions?:list<string>}|Closure():array{label?:string, extensions?:list<string>} $definition
*/
public function register(string $kind, array|Closure $definition): void
{
if ($kind === '') {
throw new InvalidArgumentException('File kind names cannot be empty.');
}

unset($this->removedFileKinds[$kind]);
$this->fileKinds[$kind] = $definition;
}

public function remove(string ...$kinds): void
{
foreach ($kinds as $kind) {
$this->removedFileKinds[$kind] = true;
unset($this->fileKinds[$kind]);
}
}

/** @return array<string, array{label:string, extensions:list<string>}> */
public function fileKinds(): array
{
$fileKinds = collect(FileKind::cases())
->filter(fn (FileKind $kind) => $kind !== FileKind::Unknown)
->mapWithKeys(fn (FileKind $kind) => [$kind->value => $kind->toArray()])
->all();

$fileKinds = Arr::merge($fileKinds, $this->generalConfig->extraFileKinds);

foreach ($this->fileKinds as $kind => $definition) {
$fileKinds = Arr::merge($fileKinds, [
$kind => $definition instanceof Closure ? app()->call($definition) : $definition,
]);
}

$fileKinds = array_diff_key($fileKinds, $this->removedFileKinds);

foreach ($fileKinds as $kind => $definition) {
if (! isset($definition['label'], $definition['extensions']) ||
! is_string($definition['label']) ||
! is_array($definition['extensions']) ||
array_any($definition['extensions'], fn (mixed $extension) => ! is_string($extension))
) {
throw new InvalidArgumentException("Invalid file kind definition [$kind].");
}
}

return Arr::sort($fileKinds, 'label');
}
}
58 changes: 5 additions & 53 deletions src/Asset/AssetsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use CraftCms\Cms\Asset\Data\VolumeFolder;
use CraftCms\Cms\Asset\Elements\Asset;
use CraftCms\Cms\Asset\Enums\FileKind;
use CraftCms\Cms\Asset\Events\AssetFileKindsResolving;
use CraftCms\Cms\Asset\Events\SetAssetFilename;
use CraftCms\Cms\Cms;
use CraftCms\Cms\Element\Contracts\ElementInterface;
Expand All @@ -18,7 +17,6 @@
use CraftCms\Cms\Filesystem\Exceptions\FilesystemException;
use CraftCms\Cms\Filesystem\Exceptions\InvalidSubpathException;
use CraftCms\Cms\Filesystem\Filesystems\Temp;
use CraftCms\Cms\Support\Arr;
use CraftCms\Cms\Support\Env;
use CraftCms\Cms\Support\Facades\Filesystems;
use CraftCms\Cms\Support\Facades\Folders;
Expand All @@ -43,20 +41,6 @@ class AssetsHelper
{
public const string INDEX_SKIP_ITEMS_PATTERN = '/.*(Thumbs\.db|__MACOSX|__MACOSX\/|__MACOSX\/.*|\.DS_STORE)$/i';

/**
* @var array|null Supported file kinds
*
* @see getFileKinds()
*/
private static ?array $_fileKinds;

/**
* @var array|null Allowed file kinds
*
* @see getAllowedFileKinds()
*/
private static ?array $_allowedFileKinds;

/**
* Get a temporary file path.
*
Expand Down Expand Up @@ -319,7 +303,7 @@ public static function fileTransferList(array $assets, array $folderIdChanges):
*/
public static function getFileKinds(): array
{
return self::fileKinds();
return app(AssetFileKinds::class)->fileKinds();
}

/**
Expand All @@ -329,32 +313,28 @@ public static function getFileKinds(): array
*/
public static function getAllowedFileKinds(): array
{
if (isset(self::$_allowedFileKinds)) {
return self::$_allowedFileKinds;
}

self::$_allowedFileKinds = [];
$allowedFileKinds = [];
$allowedExtensions = array_flip(Cms::config()->allowedFileExtensions);

foreach (static::getFileKinds() as $kind => $info) {
foreach ($info['extensions'] as $extension) {
if (isset($allowedExtensions[$extension])) {
self::$_allowedFileKinds[$kind] = $info;
$allowedFileKinds[$kind] = $info;

continue 2;
}
}
}

return self::$_allowedFileKinds;
return $allowedFileKinds;
}

/**
* Returns the label of a given file kind.
*/
public static function getFileKindLabel(string $kind): string
{
return self::fileKinds()[$kind]['label'] ?? FileKind::Unknown->value;
return self::getFileKinds()[$kind]['label'] ?? FileKind::Unknown->value;
}

/**
Expand Down Expand Up @@ -394,34 +374,6 @@ public static function parseFileLocation(string $location): array
return [(int) $folderId, $filename];
}

/**
* Builds the internal file kinds array, if it hasn't been built already.
*/
private static function fileKinds(): array
{
if (isset(self::$_fileKinds)) {
return self::$_fileKinds;
}

self::$_fileKinds = collect(FileKind::cases())
->filter(fn (FileKind $kind) => $kind !== FileKind::Unknown)
->mapWithKeys(fn (FileKind $kind) => [$kind->value => $kind->toArray()])
->all();

// Merge with the extraFileKinds setting
self::$_fileKinds = Arr::merge(self::$_fileKinds, Cms::config()->extraFileKinds);

event($event = new AssetFileKindsResolving(self::$_fileKinds));

return self::$_fileKinds = Arr::sort($event->fileKinds, 'label');
}

public static function clear(): void
{
self::$_fileKinds = null;
self::$_allowedFileKinds = null;
}

/**
* Returns the maximum allowed upload size in bytes per all config settings combined.
*/
Expand Down
16 changes: 0 additions & 16 deletions src/Asset/Events/AssetFileKindsResolving.php

This file was deleted.

36 changes: 36 additions & 0 deletions src/Auth/AuthMethodCatalog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Auth;

use CraftCms\Cms\Auth\Methods\AuthMethodInterface;
use CraftCms\Cms\Auth\Methods\RecoveryCodes;
use CraftCms\Cms\Auth\Methods\TOTP;
use CraftCms\Cms\Component\TypeRegistry;
use Illuminate\Container\Attributes\Singleton;

/**
* Internal type catalog backing {@see AuthMethods} registration.
*
* @internal
*
* @extends TypeRegistry<AuthMethodInterface>
*/
#[Singleton]
class AuthMethodCatalog extends TypeRegistry
{
protected const string CONTRACT = AuthMethodInterface::class;

protected const array DEFAULT_TYPES = [
TOTP::class,
RecoveryCodes::class,
];

/** @param class-string<AuthMethodInterface> $type */
#[\Override]
protected function identity(string $type): string
{
return $type::handle();
}
}
Loading
Loading