@@ -189,19 +189,19 @@
label: 'Show field handles in edit forms'|t('app'),
name: 'showFieldHandles',
id: 'showFieldHandles',
- checked: Auth.user.getPreference('showFieldHandles')
+ checked: currentUser.getPreference('showFieldHandles')
},
{
label: 'Profile Twig templates when Dev Mode is disabled'|t('app'),
name: 'profileTemplates',
id: 'profileTemplates',
- checked: Auth.user.getPreference('profileTemplates')
+ checked: currentUser.getPreference('profileTemplates')
},
{
label: 'Show full exception views when Dev Mode is disabled'|t('app'),
name: 'showExceptionView',
id: 'showExceptionView',
- checked: Auth.user.getPreference('showExceptionView')
+ checked: currentUser.getPreference('showExceptionView')
},
],
}) }}
diff --git a/src/Address/Elements/Address.php b/src/Address/Elements/Address.php
index e99f4e22050..aaa8181253d 100644
--- a/src/Address/Elements/Address.php
+++ b/src/Address/Elements/Address.php
@@ -359,7 +359,7 @@ protected function safeActionMenuItems(): array
if (
app(ElementRequest::class)->element === $this &&
- Auth::user()->isAdmin() &&
+ Auth::craftUser()->isAdmin() &&
Cms::config()->allowAdminChanges &&
! empty($this->fieldId)
) {
diff --git a/src/Address/Policies/AddressPolicy.php b/src/Address/Policies/AddressPolicy.php
index 027b4368bf1..f033d6d14ec 100644
--- a/src/Address/Policies/AddressPolicy.php
+++ b/src/Address/Policies/AddressPolicy.php
@@ -7,11 +7,11 @@
use CraftCms\Cms\Address\Elements\Address;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Policies\ElementPolicy;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
class AddressPolicy extends ElementPolicy
{
- public function view(User $user, Address $address): bool
+ public function view(CraftUser $user, Address $address): bool
{
if (! $owner = $this->getOwner($address)) {
return false;
@@ -20,7 +20,7 @@ public function view(User $user, Address $address): bool
return $user->can('view', $owner);
}
- public function save(User $user, Address $address): bool
+ public function save(CraftUser $user, Address $address): bool
{
if (! $owner = $this->getOwner($address)) {
return false;
@@ -29,7 +29,7 @@ public function save(User $user, Address $address): bool
return $user->can('save', $owner);
}
- public function delete(User $user, Address $address): bool
+ public function delete(CraftUser $user, Address $address): bool
{
if (! $owner = $this->getOwner($address)) {
return false;
@@ -38,7 +38,7 @@ public function delete(User $user, Address $address): bool
return $user->can('save', $owner);
}
- public function duplicate(User $user, Address $address): bool
+ public function duplicate(CraftUser $user, Address $address): bool
{
if (! $owner = $this->getOwner($address)) {
return false;
@@ -47,12 +47,12 @@ public function duplicate(User $user, Address $address): bool
return $user->can('save', $owner);
}
- public function copy(User $user, Address $address): bool
+ public function copy(CraftUser $user, Address $address): bool
{
return $user->can('duplicate', $address);
}
- public function createDrafts(User $user, Address $address): bool
+ public function createDrafts(CraftUser $user, Address $address): bool
{
return true;
}
diff --git a/src/Announcement/Announcements.php b/src/Announcement/Announcements.php
index 78618ac8e66..8a628deda2b 100644
--- a/src/Announcement/Announcements.php
+++ b/src/Announcement/Announcements.php
@@ -55,7 +55,7 @@ public function push(string $heading, string $body, ?string $pluginHandle = null
*/
public function get(): array
{
- $userId = Auth::user()?->getKey();
+ $userId = Auth::craftUser()?->getCraftUserId();
if (! $userId) {
return [];
@@ -111,7 +111,7 @@ public function markAsRead(array $ids): void
return;
}
- $userId = Auth::user()?->getKey();
+ $userId = Auth::craftUser()?->getCraftUserId();
if (! $userId) {
return;
diff --git a/src/Asset/Assets.php b/src/Asset/Assets.php
index deb444813a9..db2e4f06159 100644
--- a/src/Asset/Assets.php
+++ b/src/Asset/Assets.php
@@ -48,6 +48,7 @@
use Throwable;
use Tpetry\QueryExpressions\Language\Alias;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
#[Singleton]
@@ -94,7 +95,7 @@ public function replaceAssetFile(Asset $asset, string $pathOnServer, string $fil
$asset->tempFilePath = $pathOnServer;
$asset->newFilename = $filename;
$asset->setMimeType(File::getMimeType($pathOnServer, checkExtension: false) ?? $mimeType);
- $asset->uploaderId = Auth::user()?->id;
+ $asset->uploaderId = Auth::craftUser()?->getCraftUserId();
$asset->avoidFilenameConflicts = true;
$asset->ruleset->useScenario(AssetRules::SCENARIO_REPLACE);
$this->elements->saveElement($asset);
@@ -346,7 +347,7 @@ public function createTempAssetQuery(): AssetQuery
*/
public function getUserTemporaryUploadFolder(?User $user = null): VolumeFolder
{
- $user ??= Auth::user();
+ $user ??= currentUserElement();
$cacheKey = $user->id ?? '__GUEST__';
if (isset($this->userTempFolders[$cacheKey])) {
diff --git a/src/Asset/Data/Volume.php b/src/Asset/Data/Volume.php
index 1352bb71e74..ee51a182cc4 100644
--- a/src/Asset/Data/Volume.php
+++ b/src/Asset/Data/Volume.php
@@ -289,7 +289,7 @@ public function getUiLabel(): string
public function getCpEditUrl(): ?string
{
- if (! $this->id || ! Auth::user()?->isAdmin()) {
+ if (! $this->id || ! Auth::craftUser()?->isAdmin()) {
return null;
}
diff --git a/src/Asset/Elements/Asset.php b/src/Asset/Elements/Asset.php
index 229d4fb7c0d..cad4500b74a 100644
--- a/src/Asset/Elements/Asset.php
+++ b/src/Asset/Elements/Asset.php
@@ -108,6 +108,7 @@
use Throwable;
use Twig\Markup;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
/**
@@ -457,7 +458,7 @@ public static function gqlScopesByContext(mixed $context): array
protected static function defineSources(string $context): array
{
$sources = [];
- $user = Auth::user();
+ $user = currentUserElement();
$volumeIds = $context === ElementSources::CONTEXT_INDEX
? Volumes::getViewableVolumeIds()
: Volumes::getAllVolumeIds();
@@ -499,7 +500,7 @@ public static function findSource(string $sourceKey, ?string $context): ?array
if (preg_match('/^volume:[\w\-]+(?:\/.+)?\/folder:([\w\-]+)$/', $sourceKey, $match)) {
$folder = Folders::getFolderByUid($match[1]);
if ($folder) {
- $source = self::_assembleSourceInfoForFolder($folder, Auth::user());
+ $source = self::_assembleSourceInfoForFolder($folder, currentUserElement());
$source['keyPath'] = $sourceKey;
return $source;
@@ -770,7 +771,7 @@ protected static function defineCardAttributes(): array
],
'uploader' => [
'label' => t('Uploaded By'),
- 'placeholder' => fn () => ($uploader = Auth::user()) ? app(ElementHtml::class)->elementChipHtml($uploader) : '',
+ 'placeholder' => fn () => ($uploader = currentUserElement()) ? app(ElementHtml::class)->elementChipHtml($uploader) : '',
],
]);
@@ -1293,7 +1294,7 @@ protected function safeActionMenuItems(): array
$items = parent::safeActionMenuItems();
$volume = $this->getVolume();
- $user = Auth::user();
+ $user = currentUserElement();
$updatePreviewThumbJs = $this->_updatePreviewThumbJs();
$viewItems = [];
@@ -1347,7 +1348,7 @@ protected function safeActionMenuItems(): array
]);
// Show in Folder
- if ($this->volumeId && $this->canView($user)) {
+ if ($user && $this->volumeId && $this->canView($user)) {
$viewItems[] = [
'type' => MenuItemType::Link,
'icon' => 'magnifying-glass',
@@ -1365,6 +1366,7 @@ protected function safeActionMenuItems(): array
// Replace file
if (
+ $user &&
$user->can("replaceFiles:$volume->uid") &&
($user->id === $this->uploaderId || $user->can("replacePeerFiles:$volume->uid"))
) {
@@ -1539,7 +1541,7 @@ protected function safeActionMenuItems(): array
if (
app(ElementRequest::class)->element() === $this &&
- Auth::user()->isAdmin() &&
+ Auth::craftUser()->isAdmin() &&
Cms::config()->allowAdminChanges
) {
$items[] = ['type' => MenuItemType::HR];
diff --git a/src/Asset/Policies/AssetPolicy.php b/src/Asset/Policies/AssetPolicy.php
index 9b1859cddcc..8dbf239098f 100644
--- a/src/Asset/Policies/AssetPolicy.php
+++ b/src/Asset/Policies/AssetPolicy.php
@@ -7,19 +7,20 @@
use CraftCms\Cms\Asset\AssetsHelper;
use CraftCms\Cms\Asset\Elements\Asset;
use CraftCms\Cms\Element\Policies\ElementPolicy;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
class AssetPolicy extends ElementPolicy
{
- public function view(User $user, Asset $asset): bool
+ public function view(CraftUser $user, Asset $asset): bool
{
if ($asset->isFolder) {
return false;
}
$volume = $asset->getVolume();
+ $userId = $user->getCraftUserId();
- if ($asset->uploaderId !== $user->id) {
+ if ($asset->uploaderId !== $userId) {
return $user->can("viewPeerAssets:$volume->uid");
}
@@ -30,18 +31,19 @@ public function view(User $user, Asset $asset): bool
return $user->can("viewAssets:$volume->uid");
}
- public function save(User $user, Asset $asset): bool
+ public function save(CraftUser $user, Asset $asset): bool
{
$volume = $asset->getVolume();
+ $userId = $user->getCraftUserId();
- if ($asset->uploaderId !== $user->id) {
+ if ($asset->uploaderId !== $userId) {
return $user->can("savePeerAssets:$volume->uid");
}
return $user->can("saveAssets:$volume->uid");
}
- public function delete(User $user, Asset $asset): bool
+ public function delete(CraftUser $user, Asset $asset): bool
{
if ($asset->isFolder) {
return false;
@@ -53,14 +55,14 @@ public function delete(User $user, Asset $asset): bool
return true;
}
- if ($asset->uploaderId !== $user->id) {
+ if ($asset->uploaderId !== $user->getCraftUserId()) {
return $user->can("deletePeerAssets:$volume->uid");
}
return $user->can("deleteAssets:$volume->uid");
}
- public function copy(User $user, Asset $asset): bool
+ public function copy(CraftUser $user, Asset $asset): bool
{
return $this->view($user, $asset);
}
diff --git a/src/Auth/AuthMethods.php b/src/Auth/AuthMethods.php
index 98b9a1571ff..e4d31e1a296 100644
--- a/src/Auth/AuthMethods.php
+++ b/src/Auth/AuthMethods.php
@@ -20,8 +20,10 @@
use CraftCms\Cms\Support\DateTimeHelper;
use CraftCms\Cms\Support\Json;
use CraftCms\Cms\Twig\Attributes\AllowedInSandbox;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use CraftCms\Cms\User\Users;
+use Illuminate\Auth\SessionGuard;
use Illuminate\Container\Attributes\Scoped;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Support\Collection;
@@ -45,7 +47,7 @@ class AuthMethods
private Collection $methods;
/**
- * The user being logged in.
+ * The user element being logged in.
*/
private ?User $user = null;
@@ -63,9 +65,13 @@ public function __construct(
/**
* @return Collection
*/
- public function getAllMethods(?User $user = null): Collection
+ public function getAllMethods(?CraftUser $user = null): Collection
{
- $user ??= auth('craft')->user() ?? $this->getUser();
+ /** @var SessionGuard $guard */
+ $guard = auth('craft');
+ $user = $user?->asElement()
+ ?? $guard->craftUser()?->asElement()
+ ?? $this->getUser();
if (! $user?->id) {
return new Collection;
@@ -113,7 +119,7 @@ public function getAllMethods(?User $user = null): Collection
/**
* @return Collection
*/
- public function getAvailableMethods(?User $user = null): Collection
+ public function getAvailableMethods(?CraftUser $user = null): Collection
{
$methods = $this->getAllMethods($user);
@@ -134,7 +140,7 @@ public function getAvailableMethods(?User $user = null): Collection
/**
* Returns whether any authentication methods are active for the given user.
*/
- public function hasActiveMethod(?User $user = null): bool
+ public function hasActiveMethod(?CraftUser $user = null): bool
{
foreach ($this->getAvailableMethods($user) as $method) {
if ($method->isActive()) {
@@ -150,7 +156,7 @@ public function hasActiveMethod(?User $user = null): bool
*
* @return Collection
*/
- public function getActiveMethods(?User $user = null): Collection
+ public function getActiveMethods(?CraftUser $user = null): Collection
{
return $this->getAvailableMethods($user)
->filter(fn (AuthMethodInterface $method) => $method->isActive())
@@ -167,7 +173,7 @@ public function getActiveMethods(?User $user = null): Collection
*
* @throws InvalidArgumentException
*/
- public function getMethod(string $class, ?User $user = null): AuthMethodInterface
+ public function getMethod(string $class, ?CraftUser $user = null): AuthMethodInterface
{
foreach ($this->getAllMethods($user) as $method) {
if ($method::class === $class) {
@@ -193,20 +199,22 @@ public function getUser(): ?User
return $this->user;
}
- public function setUser(?User $user): void
+ public function setUser(?CraftUser $user): void
{
- $this->user = $user;
+ $this->user = $user?->asElement();
- if ($user) {
- Session::put('user.id', $user->id);
+ if ($this->user) {
+ Session::put('user.id', $this->user->id);
Session::put('user.pending_2fa_at', now()->timestamp);
} else {
Session::forget(['user.id', 'user.pending_2fa_at']);
}
}
- public function is2faRequired(User $user): bool
+ public function is2faRequired(CraftUser $user): bool
{
+ $user = $user->asElement();
+
if (Edition::get() === Edition::Solo) {
return false;
}
@@ -222,7 +230,7 @@ public function is2faRequired(User $user): bool
foreach ($require2fa as $group) {
if ($group === 'admins') {
- if ($user->admin) {
+ if ($user->isAdmin()) {
return true;
}
} elseif (isset($groups[$group])) {
@@ -234,10 +242,12 @@ public function is2faRequired(User $user): bool
return false;
}
- public function authenticate(User $user, #[SensitiveParameter] array $credentials): bool
+ public function authenticate(CraftUser $user, #[SensitiveParameter] array $credentials): bool
{
event($event = new UserAuthenticating($credentials));
+ $user = $user->asElement();
+
$this->authError = $event->authError;
if (isset($this->authError)) {
@@ -281,10 +291,12 @@ public function authenticate(User $user, #[SensitiveParameter] array $credential
return true;
}
- public function authenticateWithPasskey(User $user, string $requestOptions, string $response): bool
+ public function authenticateWithPasskey(CraftUser $user, string $requestOptions, string $response): bool
{
event($event = new UserAuthenticating);
+ $user = $user->asElement();
+
$this->authError = $event->authError;
if (isset($this->authError)) {
@@ -344,20 +356,30 @@ public function verifyMethod(string $methodClass, mixed ...$args): bool
if ($user) {
$this->setUser(null);
+ /** @var SessionGuard $guard */
+ $guard = auth('craft');
+
// if we're impersonating, pass the user we're impersonating to the complete the login
if ($this->impersonation->isImpersonating()) {
- /** @var User $user */
- $user = auth('craft')->user();
+ $authUser = $guard->craftUser();
+ }
+
+ $authUser ??= $guard->getProvider()->retrieveById($user->id);
+
+ if (! $authUser) {
+ return false;
}
- auth('craft')->login($user, true);
+ auth('craft')->login($authUser, true);
}
return true;
}
- public function getAuthError(User $user): ?AuthError
+ public function getAuthError(CraftUser $user): ?AuthError
{
+ $user = $user->asElement();
+
switch ($user->getStatus()) {
case User::STATUS_INACTIVE:
case User::STATUS_ARCHIVED:
@@ -435,8 +457,10 @@ public function getAuthMethodErrorMessage(?string $defaultMessage = null): strin
/**
* @return array{0:AuthError|null,1:string}
*/
- public function getLoginFailureInfo(?AuthError $authError, ?User $user): array
+ public function getLoginFailureInfo(?AuthError $authError, ?CraftUser $user): array
{
+ $user = $user?->asElement();
+
if ($this->generalConfig->preventUserEnumeration && in_array($authError, [AuthError::AccountLocked, AuthError::AccountCooldown])) {
$authError = AuthError::InvalidCredentials;
}
@@ -462,8 +486,10 @@ public function getLoginFailureInfo(?AuthError $authError, ?User $user): array
return [$authError, $message];
}
- public function handleInvalidLogin(User $user): void
+ public function handleInvalidLogin(CraftUser $user): void
{
+ $user = $user->asElement();
+
$this->users->handleInvalidLogin($user);
// Was that one bad password/2fa code/passkey too many?
@@ -486,8 +512,10 @@ public function getRememberedUsername(): ?string
return Cookie::get($this->rememberedUsernameCookie());
}
- public function setRememberedUsername(User $user): void
+ public function setRememberedUsername(CraftUser $user): void
{
+ $user = $user->asElement();
+
if ($this->generalConfig->rememberUsernameDuration === 0) {
Cookie::unqueue($this->rememberedUsernameCookie());
Cookie::forget($this->rememberedUsernameCookie());
diff --git a/src/Auth/AuthServiceProvider.php b/src/Auth/AuthServiceProvider.php
index 4b78e43e8ab..e44301bcab7 100644
--- a/src/Auth/AuthServiceProvider.php
+++ b/src/Auth/AuthServiceProvider.php
@@ -19,21 +19,18 @@
use CraftCms\Cms\Field\Policies\ContentBlockPolicy;
use CraftCms\Cms\Support\Facades\Sites;
use CraftCms\Cms\Support\Facades\Users as UsersFacade;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use CraftCms\Cms\User\Elements\User as UserElement;
+use CraftCms\Cms\User\Models\User;
use CraftCms\Cms\User\Policies\UserPolicy;
use CraftCms\Cms\User\UserPermissions;
-use CraftCms\Cms\User\Users;
use Illuminate\Auth\Events\Authenticated;
use Illuminate\Auth\Events\Failed;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Auth\Middleware\RedirectIfAuthenticated;
-use Illuminate\Contracts\Auth\Access\Authorizable;
-use Illuminate\Contracts\Hashing\Hasher;
-use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth as AuthFacade;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Gate;
@@ -73,12 +70,6 @@ private function bootRedirects(): void
private function registerGuard(): void
{
- AuthFacade::provider('craft', fn (Application $app) => new UserProvider(
- $app->make(Hasher::class),
- $app->make(Users::class),
- $app->make(AuthMethods::class),
- ));
-
if (! Config::has('auth.guards.craft')) {
Config::set('auth.guards.craft', [
'driver' => 'session',
@@ -89,7 +80,7 @@ private function registerGuard(): void
if (! Config::has('auth.providers.craft')) {
Config::set('auth.providers.craft', [
- 'driver' => 'craft',
+ 'driver' => 'eloquent',
'model' => User::class,
]);
}
@@ -110,11 +101,7 @@ private function registerPermissions(): void
* This hooks our permission system into
* Laravel's Gate authorization system
*/
- Gate::after(function (Authorizable $user, string $ability, ?bool $result) {
- if (! $user instanceof User) {
- return null;
- }
-
+ Gate::after(function (CraftUser $user, string $ability, ?bool $result) {
/**
* Only check our permissions when the
* result was not explicitly set.
@@ -124,17 +111,19 @@ private function registerPermissions(): void
}
if (
- $user->admin ||
+ $user->isAdmin() ||
Edition::get() === Edition::Solo
) {
return true;
}
- if (! isset($user->id)) {
+ $userId = $user->getCraftUserId();
+
+ if (! $userId) {
return null;
}
- if (! app(UserPermissions::class)->doesUserHavePermission($user->id, $ability)) {
+ if (! app(UserPermissions::class)->doesUserHavePermission($userId, $ability)) {
return null;
}
@@ -150,23 +139,27 @@ private function registerEvents(): void
});
Event::listen(Login::class, function (Login $event) {
- if (! $event->user instanceof User) {
+ $user = $event->user instanceof CraftUser ? $event->user->asElement() : null;
+
+ if (! $user) {
return;
}
- UsersFacade::handleValidLogin($event->user);
+ UsersFacade::handleValidLogin($user);
- app(AuthMethods::class)->setRememberedUsername($event->user);
+ app(AuthMethods::class)->setRememberedUsername($user);
Session::passwordConfirmed();
});
Event::listen(Failed::class, function (Failed $event) {
- if (! $event->user instanceof User) {
+ $user = $event->user instanceof CraftUser ? $event->user->asElement() : null;
+
+ if (! $user) {
return;
}
- UsersFacade::handleInvalidLogin($event->user);
+ UsersFacade::handleInvalidLogin($user);
});
Event::listen(Logout::class, function () {
@@ -181,6 +174,6 @@ private function registerElementPolicies(): void
Gate::policy(Asset::class, AssetPolicy::class);
Gate::policy(ContentBlock::class, ContentBlockPolicy::class);
Gate::policy(Entry::class, EntryPolicy::class);
- Gate::policy(User::class, UserPolicy::class);
+ Gate::policy(UserElement::class, UserPolicy::class);
}
}
diff --git a/src/Auth/Concerns/EnforcesPermissions.php b/src/Auth/Concerns/EnforcesPermissions.php
index c82912edb5d..173c4fb0d87 100644
--- a/src/Auth/Concerns/EnforcesPermissions.php
+++ b/src/Auth/Concerns/EnforcesPermissions.php
@@ -41,7 +41,7 @@ protected function requireSessionAuthorization(string $permission): void
protected function requirePermission(string $permission): void
{
- if (! $user = Auth::user()) {
+ if (! $user = Auth::craftUser()) {
abort(403, 'User is not authenticated.');
}
@@ -52,6 +52,6 @@ protected function requirePermission(string $permission): void
protected function requireAdmin(): void
{
- abort_unless(Auth::user()->isAdmin(), 403, 'User is not permitted to perform this action.');
+ abort_unless(Auth::craftUser()->isAdmin(), 403, 'User is not permitted to perform this action.');
}
}
diff --git a/src/Auth/Events/ElementAuthorizing.php b/src/Auth/Events/ElementAuthorizing.php
index a61fce17e1a..3978d80d5d9 100644
--- a/src/Auth/Events/ElementAuthorizing.php
+++ b/src/Auth/Events/ElementAuthorizing.php
@@ -5,7 +5,7 @@
namespace CraftCms\Cms\Auth\Events;
use CraftCms\Cms\Element\Contracts\ElementInterface;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
use Illuminate\Foundation\Events\Dispatchable;
/**
@@ -27,7 +27,7 @@ class ElementAuthorizing
public ?bool $authorized = null;
public function __construct(
- public readonly User $user,
+ public readonly CraftUser $user,
public readonly ElementInterface $element,
public readonly string $ability,
) {}
diff --git a/src/Auth/Events/LoginUserRetrieved.php b/src/Auth/Events/LoginUserRetrieved.php
index 1f22aa6454b..8c6484e7b65 100644
--- a/src/Auth/Events/LoginUserRetrieved.php
+++ b/src/Auth/Events/LoginUserRetrieved.php
@@ -4,7 +4,7 @@
namespace CraftCms\Cms\Auth\Events;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
/**
* @event LoginUserRetrieved The event that is triggered after attempting to find a user to sign in
@@ -13,6 +13,6 @@ class LoginUserRetrieved
{
public function __construct(
public string $loginName,
- public ?User $user = null,
+ public ?CraftUser $user = null,
) {}
}
diff --git a/src/Auth/Events/LoginUserRetrieving.php b/src/Auth/Events/LoginUserRetrieving.php
index f5592256b4d..3b179cba0b3 100644
--- a/src/Auth/Events/LoginUserRetrieving.php
+++ b/src/Auth/Events/LoginUserRetrieving.php
@@ -4,13 +4,13 @@
namespace CraftCms\Cms\Auth\Events;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
/**
* @event LoginUserRetrieving The event that is triggered before attempting to find a user to sign in
*
* ```php
- * use CraftCms\Cms\User\Elements\User;
+ * use CraftCms\Cms\User\Models\User;
* use CraftCms\Cms\User\Events\LoginUserRetrieving;
* use Illuminate\Support\Facades\Event;
*
@@ -30,6 +30,6 @@ class LoginUserRetrieving
{
public function __construct(
public string $loginName,
- public ?User $user = null,
+ public ?CraftUser $user = null,
) {}
}
diff --git a/src/Auth/Passkeys/CredentialRepository.php b/src/Auth/Passkeys/CredentialRepository.php
index 40e08289aa6..15273b9e229 100644
--- a/src/Auth/Passkeys/CredentialRepository.php
+++ b/src/Auth/Passkeys/CredentialRepository.php
@@ -7,6 +7,7 @@
use CraftCms\Cms\Auth\Models\WebAuthn;
use CraftCms\Cms\Support\Facades\Users;
use CraftCms\Cms\Support\Json;
+use Illuminate\Support\Facades\Auth;
use ParagonIE\ConstantTime\Base64UrlSafe;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialUserEntity;
@@ -81,7 +82,7 @@ public function savedNamedCredentialSource(PublicKeyCredentialSource $publicKeyC
if (! $model) {
$model = new WebAuthn;
- $model->userId = auth('craft')->user()?->id;
+ $model->userId = Auth::craftUser()?->getCraftUserId();
$model->credentialName = ! empty($credentialName) ? $credentialName : t('Secure credential');
$model->credentialId = Base64UrlSafe::encodeUnpadded($publicKeyCredentialId);
}
diff --git a/src/Auth/Passkeys/Passkeys.php b/src/Auth/Passkeys/Passkeys.php
index 4421f019f44..6c2cf25af09 100644
--- a/src/Auth/Passkeys/Passkeys.php
+++ b/src/Auth/Passkeys/Passkeys.php
@@ -7,7 +7,8 @@
use Carbon\CarbonInterface;
use CraftCms\Cms\Auth\Models\WebAuthn;
use CraftCms\Cms\Cms;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use CraftCms\Cms\User\Elements\User as UserElement;
use Illuminate\Container\Attributes\Scoped;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -48,8 +49,10 @@ public function __construct(
public readonly string $passkeyCredSourceParam = 'pkCredSource',
) {}
- public function hasPasskeys(User $user): bool
+ public function hasPasskeys(CraftUser $user): bool
{
+ $user = $this->userElement($user);
+
if (! $user->id) {
return false;
}
@@ -66,8 +69,10 @@ public function hasPasskeys(User $user): bool
* uid:string
* }>
*/
- public function getPasskeys(User $user): Collection
+ public function getPasskeys(CraftUser $user): Collection
{
+ $user = $this->userElement($user);
+
if (! $user->id) {
return new Collection;
}
@@ -86,7 +91,7 @@ public function getPasskeys(User $user): Collection
/**
* Generates new passkey credential creation options for the given user.
*/
- public function getPasskeyCreationOptions(User $user): PublicKeyCredentialOptions
+ public function getPasskeyCreationOptions(CraftUser $user): PublicKeyCredentialOptions
{
$userEntity = $this->passkeyUserEntity($user);
$credentialRepository = $this->webauthnServer()->getCredentialRepository();
@@ -180,7 +185,7 @@ public function getPasskeyRequestOptions(): PublicKeyCredentialRequestOptions
* @param string $response The authentication response data
*/
public function verifyPasskey(
- User $user,
+ CraftUser $user,
string $requestOptions,
string $response,
bool $checkOldUserHandle = false,
@@ -244,8 +249,10 @@ public function verifyPasskey(
return true;
}
- public function deletePasskey(User $user, string $uid): void
+ public function deletePasskey(CraftUser $user, string $uid): void
{
+ $user = $this->userElement($user);
+
WebAuthn::where('userId', $user->id)->where('uid', $uid)->delete();
}
@@ -264,8 +271,10 @@ public function webauthnServer(): WebauthnServer
/**
* Returns User Entity for given User element
*/
- public function passkeyUserEntity(User $user): PublicKeyCredentialUserEntity
+ public function passkeyUserEntity(CraftUser $user): PublicKeyCredentialUserEntity
{
+ $user = $this->userElement($user);
+
return PublicKeyCredentialUserEntity::create(
name: $user->email,
id: Base64UrlSafe::encodeUnpadded($user->uid),
@@ -283,4 +292,9 @@ private function passkeyRpEntity(): PublicKeyCredentialRpEntity
id: request()->host(),
);
}
+
+ private function userElement(CraftUser $user): UserElement
+ {
+ return $user->asElement();
+ }
}
diff --git a/src/Auth/UserProvider.php b/src/Auth/UserProvider.php
deleted file mode 100644
index 9c818edc114..00000000000
--- a/src/Auth/UserProvider.php
+++ /dev/null
@@ -1,107 +0,0 @@
-addSelect('password')->id($identifier)->one();
- }
-
- public function retrieveByToken($identifier, #[SensitiveParameter] $token): ?User
- {
- $user = $this->retrieveById($identifier);
-
- if (! $user) {
- return null;
- }
-
- if ($user->getRememberToken() && hash_equals($user->getRememberToken(), $token)) {
- return $user;
- }
-
- return null;
- }
-
- public function updateRememberToken(Authenticatable $user, #[SensitiveParameter] $token): void
- {
- DB::table(Table::USERS)
- ->where('id', $user->getAuthIdentifier())
- ->update(['rememberToken' => $token]);
- }
-
- public function retrieveByCredentials(#[SensitiveParameter] array $credentials): ?User
- {
- $credentials = array_filter(
- $credentials,
- fn ($key) => ! str_contains((string) $key, 'password'),
- ARRAY_FILTER_USE_KEY
- );
-
- if (empty($credentials)) {
- return null;
- }
-
- $loginName = $credentials['loginName'] ?? $credentials['email'] ?? $credentials['username'];
-
- event($event = new LoginUserRetrieving($loginName));
-
- $user = $event->user ?? $this->users->getUserByUsernameOrEmail($loginName);
-
- event($event = new LoginUserRetrieved($loginName, $user));
-
- return $event->user;
- }
-
- /**
- * @param User $user
- */
- public function validateCredentials(Authenticatable $user, #[SensitiveParameter] array $credentials): bool
- {
- return $this->auth->authenticate($user, $credentials);
- }
-
- public function rehashPasswordIfRequired(
- Authenticatable $user,
- #[SensitiveParameter] array $credentials,
- bool $force = false,
- ): void {
- if (! $this->hasher->needsRehash($user->getAuthPassword()) && ! $force) {
- return;
- }
-
- DB::table(Table::USERS)
- ->where('id', $user->getAuthIdentifier())
- ->update([$user->getAuthPasswordName() => $this->hasher->make($credentials['password'])]);
- }
-
- public function getAuthError(): ?AuthError
- {
- return $this->auth->authError;
- }
-}
diff --git a/src/Cms.php b/src/Cms.php
index b579992ef9b..32cdca3fd2a 100644
--- a/src/Cms.php
+++ b/src/Cms.php
@@ -111,7 +111,7 @@ public static function targetLanguage(?Request $request = null): string
return Sites::getCurrentSite()->getLanguage();
}
- $user = Auth::guard('craft')->user();
+ $user = Auth::craftUser();
if (
($id = $user?->getAuthIdentifier()) &&
diff --git a/src/Component/Concerns/MissingComponentTrait.php b/src/Component/Concerns/MissingComponentTrait.php
index b0917326256..74c7cdc224a 100644
--- a/src/Component/Concerns/MissingComponentTrait.php
+++ b/src/Component/Concerns/MissingComponentTrait.php
@@ -64,7 +64,7 @@ public function getPlaceholderHtml(): string
$iconSvg = null;
if (
- Auth::user()?->isAdmin() &&
+ Auth::craftUser()?->isAdmin() &&
Cms::config()->allowAdminChanges
) {
$pluginsService = app(Plugins::class);
diff --git a/src/Cp/Alerts.php b/src/Cp/Alerts.php
index b55df0bc487..0da733f4331 100644
--- a/src/Cp/Alerts.php
+++ b/src/Cp/Alerts.php
@@ -42,7 +42,7 @@ public function __construct(
public function get(?string $path = null, bool $fetch = false): array
{
$alerts = [];
- $user = Auth::user();
+ $user = Auth::craftUser();
$consoleUrl = rtrim(Api::craftIdEndpoint(), '/');
if (! $user) {
@@ -114,7 +114,7 @@ public function get(?string $path = null, bool $fetch = false): array
}
if (
- $user->admin &&
+ $user->isAdmin() &&
$this->generalConfig->allowAdminChanges &&
$this->projectConfig->getHadFileWriteIssues()
) {
diff --git a/src/Cp/FormFields.php b/src/Cp/FormFields.php
index da6cb811865..6e44a36b232 100644
--- a/src/Cp/FormFields.php
+++ b/src/Cp/FormFields.php
@@ -112,8 +112,8 @@ public static function fieldHtml(string|Stringable|callable $input, array $confi
$showAttribute = (
($config['showAttribute'] ?? false) &&
- Auth::user()->isAdmin() &&
- Auth::user()->getPreference('showFieldHandles')
+ Auth::craftUser()->isAdmin() &&
+ Auth::craftUser()->getPreference('showFieldHandles')
);
$showActionMenu = (
! empty($config['actionMenuItems']) &&
diff --git a/src/Cp/Html/ElementHtml.php b/src/Cp/Html/ElementHtml.php
index 7dd0bcf4603..31f292d1a3a 100644
--- a/src/Cp/Html/ElementHtml.php
+++ b/src/Cp/Html/ElementHtml.php
@@ -570,7 +570,7 @@ public function elementCardHtml(ElementInterface $element, array $config = []):
private function baseElementAttributes(ElementInterface $element, array $config): array
{
- $user = Auth::user();
+ $user = Auth::craftUser();
$editable = $user && $user->can('view', $element);
return Arr::merge(
diff --git a/src/Cp/Navigation.php b/src/Cp/Navigation.php
index 6b095a9d3cf..e389cb15602 100644
--- a/src/Cp/Navigation.php
+++ b/src/Cp/Navigation.php
@@ -35,7 +35,7 @@ public function __construct(
public function getItems(): array
{
- $user = Auth::user();
+ $user = Auth::craftUser();
$isAdmin = $user?->isAdmin();
$navItems = [
diff --git a/src/Dashboard/Dashboard.php b/src/Dashboard/Dashboard.php
index 51db0911050..924e3a5638a 100644
--- a/src/Dashboard/Dashboard.php
+++ b/src/Dashboard/Dashboard.php
@@ -123,7 +123,7 @@ public function getWidgetById(int $id): WidgetInterface
{
$result = Models\Widget::query()
->where('id', $id)
- ->where('userId', Auth::user()->getAuthIdentifier())
+ ->where('userId', Auth::craftUser()->getAuthIdentifier())
->firstOrFail();
return Widget::fromConfig($result);
@@ -174,7 +174,7 @@ public function saveWidget(WidgetInterface $widget, bool $runValidation = true):
if ($isNewWidget) {
// Set the sortOrder
$maxSortOrder = Models\Widget::query()
- ->where('userId', Auth::user()->getAuthIdentifier())
+ ->where('userId', Auth::craftUser()->getAuthIdentifier())
->max('sortOrder');
$widgetModel->sortOrder = $maxSortOrder + 1;
@@ -289,7 +289,7 @@ public function changeWidgetColspan(int $widgetId, int $colspan): bool
*/
private function addDefaultUserWidgets(): void
{
- $user = Auth::user();
+ $user = Auth::craftUser();
// Recent Entries widget
$this->saveWidget($this->createWidget(RecentEntriesWidget::class));
@@ -313,16 +313,14 @@ private function addDefaultUserWidgets(): void
],
]));
- User::where('id', $user->id)->update([
+ User::where('id', $user->getCraftUserId())->update([
'hasDashboard' => true,
]);
-
- $user->hasDashboard = true;
}
private function getUserWidgetModelById(?int $widgetId = null): Models\Widget
{
- $userId = Auth::user()->getAuthIdentifier();
+ $userId = Auth::craftUser()->getAuthIdentifier();
if ($widgetId !== null) {
return Models\Widget::query()
@@ -346,19 +344,18 @@ private function getUserWidgetModelById(?int $widgetId = null): Models\Widget
*/
private function getUserWidgets(): Collection|false
{
- /** @var User $user */
- $user = Auth::user();
+ $user = Auth::craftUser();
if (! $user) {
throw new Exception('No logged-in user');
}
- if (! $user->hasDashboard) {
+ if (! User::where('id', $user->getCraftUserId())->value('hasDashboard')) {
return false;
}
return once(fn () => Models\Widget::query()
- ->where('userId', $user->id)
+ ->where('userId', $user->getCraftUserId())
->orderBy('sortOrder')
->get()
->map(fn (Models\Widget $widget) => Widget::fromConfig($widget)));
diff --git a/src/Dashboard/Widgets/CraftSupport.php b/src/Dashboard/Widgets/CraftSupport.php
index acdc304fe3c..5a411fa0635 100644
--- a/src/Dashboard/Widgets/CraftSupport.php
+++ b/src/Dashboard/Widgets/CraftSupport.php
@@ -43,7 +43,7 @@ public static function displayName(): string
public static function isSelectable(): bool
{
// Only admins get the Craft Support widget.
- return parent::isSelectable() && Auth::user()?->isAdmin();
+ return parent::isSelectable() && Auth::craftUser()?->isAdmin();
}
#[Override]
@@ -68,7 +68,7 @@ public function getTitle(): ?string
public function getBodyHtml(): ?string
{
// Only admins get the Craft Support widget.
- if (! Auth::user()?->isAdmin()) {
+ if (! Auth::craftUser()?->isAdmin()) {
return null;
}
diff --git a/src/Dashboard/Widgets/MyDrafts.php b/src/Dashboard/Widgets/MyDrafts.php
index 298608d75f7..6131db60e4d 100644
--- a/src/Dashboard/Widgets/MyDrafts.php
+++ b/src/Dashboard/Widgets/MyDrafts.php
@@ -68,7 +68,7 @@ public function getBodyHtml(): string
$drafts = Entry::find()
->drafts()
->status(null)
- ->draftCreator(Auth::user()->id)
+ ->draftCreator(Auth::craftUser()?->getCraftUserId())
->section('*')
->site('*')
->unique()
diff --git a/src/Dashboard/Widgets/QuickPost.php b/src/Dashboard/Widgets/QuickPost.php
index 24a4abd7c58..1cf55a02381 100644
--- a/src/Dashboard/Widgets/QuickPost.php
+++ b/src/Dashboard/Widgets/QuickPost.php
@@ -105,7 +105,7 @@ public function getSettingsHtml(): string
if ($section->type === SectionType::Single) {
continue;
}
- if (! Auth::user()->can('createEntries:'.$section->uid)) {
+ if (! Auth::craftUser()->can('createEntries:'.$section->uid)) {
continue;
}
$sections[] = $section;
diff --git a/src/Dashboard/Widgets/Updates.php b/src/Dashboard/Widgets/Updates.php
index 3da5bc97f58..a6687d33f5a 100644
--- a/src/Dashboard/Widgets/Updates.php
+++ b/src/Dashboard/Widgets/Updates.php
@@ -33,7 +33,7 @@ public static function displayName(): string
public static function isSelectable(): bool
{
// Gotta have update permission to get this widget
- return parent::isSelectable() && Auth::user()->can('performUpdates');
+ return parent::isSelectable() && Auth::craftUser()->can('performUpdates');
}
#[Override]
@@ -52,7 +52,7 @@ public static function icon(): string
public function getBodyHtml(): ?string
{
// Make sure the user actually has permission to perform updates
- if (! Auth::user()->can('performUpdates')) {
+ if (! Auth::craftUser()->can('performUpdates')) {
return null;
}
diff --git a/src/Edition.php b/src/Edition.php
index f23e89dd53e..8292e80ddee 100644
--- a/src/Edition.php
+++ b/src/Edition.php
@@ -123,7 +123,7 @@ public static function canTest(): bool
public static function canUpgrade(): bool
{
- if (! Auth::user()?->isAdmin()) {
+ if (! Auth::craftUser()?->isAdmin()) {
return false;
}
diff --git a/src/Element/Conditions/HintableConditionRuleTrait.php b/src/Element/Conditions/HintableConditionRuleTrait.php
index dd10b58ad4b..a5337b18c20 100644
--- a/src/Element/Conditions/HintableConditionRuleTrait.php
+++ b/src/Element/Conditions/HintableConditionRuleTrait.php
@@ -10,6 +10,6 @@ trait HintableConditionRuleTrait
{
public function showLabelHint(): bool
{
- return Auth::user()?->getPreference('showFieldHandles') ?? false;
+ return Auth::craftUser()?->getPreference('showFieldHandles') ?? false;
}
}
diff --git a/src/Element/Drafts.php b/src/Element/Drafts.php
index 86f2610952d..35ed2918005 100644
--- a/src/Element/Drafts.php
+++ b/src/Element/Drafts.php
@@ -29,6 +29,7 @@
use Throwable;
use Tpetry\QueryExpressions\Language\Alias;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
#[Singleton]
@@ -47,7 +48,7 @@ public function __construct(
*/
public function getEditableDrafts(ElementInterface $element, ?string $permission = null): Collection
{
- $user = Auth::user();
+ $user = Auth::craftUser();
if (! $user) {
return collect();
@@ -61,7 +62,7 @@ public function getEditableDrafts(ElementInterface $element, ?string $permission
->orderByDesc('dateUpdated');
if (! $permission || ! $user->can($permission)) {
- $query->draftCreator($user->id);
+ $query->draftCreator($user->getCraftUserId());
}
return collect($query->all());
@@ -493,7 +494,7 @@ private function provisionalDrafts(array $elements, ?User $user = null): array
$user = User::find()->id($userId)->status(null)->one();
}
- $user ??= Auth::user();
+ $user ??= currentUserElement();
if (! $user) {
return [];
diff --git a/src/Element/ElementActivity.php b/src/Element/ElementActivity.php
index 0d5e20730ac..574f638afe8 100644
--- a/src/Element/ElementActivity.php
+++ b/src/Element/ElementActivity.php
@@ -13,11 +13,12 @@
use Illuminate\Container\Attributes\Singleton;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use InvalidArgumentException;
+use function CraftCms\Cms\currentUserElement;
+
#[Singleton]
readonly class ElementActivity
{
@@ -119,7 +120,7 @@ public function getRecentActivity(ElementInterface $element, ?int $excludeUserId
public function trackActivity(ElementInterface $element, ElementActivityType $type, ?User $user = null): void
{
- $user ??= Auth::user();
+ $user ??= currentUserElement();
if (! $user) {
throw new InvalidArgumentException('$user must be set if no user is signed in.');
diff --git a/src/Element/ElementHelper.php b/src/Element/ElementHelper.php
index 95765d3f087..6065402c8a1 100644
--- a/src/Element/ElementHelper.php
+++ b/src/Element/ElementHelper.php
@@ -19,13 +19,13 @@
use CraftCms\Cms\Support\Str;
use CraftCms\Cms\Support\Url;
use Illuminate\Database\Query\Builder;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\HtmlString;
use RuntimeException;
use Tpetry\QueryExpressions\Function\String\Lower;
use Tpetry\QueryExpressions\Language\Alias;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\renderObjectTemplate;
class ElementHelper
@@ -275,7 +275,7 @@ public static function shouldTrackChanges(ElementInterface $element): bool
*/
public static function isElementEditable(ElementInterface $element): bool
{
- $user = Auth::user();
+ $user = currentUserElement();
if (! $user || ! $element->canView($user)) {
return false;
@@ -295,7 +295,7 @@ public static function isElementEditable(ElementInterface $element): bool
*/
public static function editableSiteIdsForElement(ElementInterface $element): array
{
- $user = Auth::user();
+ $user = currentUserElement();
if (! $user || ! $element->canView($user)) {
return [];
diff --git a/src/Element/ElementSources.php b/src/Element/ElementSources.php
index 47fd7569416..c1c16bf6233 100644
--- a/src/Element/ElementSources.php
+++ b/src/Element/ElementSources.php
@@ -25,9 +25,9 @@
use CraftCms\Cms\Support\Str;
use Illuminate\Container\Attributes\Scoped;
use Illuminate\Support\Collection;
-use Illuminate\Support\Facades\Auth;
use Tpetry\QueryExpressions\Function\Conditional\Coalesce;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
#[Scoped]
@@ -410,7 +410,7 @@ private function showCustomSource(array $source): bool
return true;
}
- $user = Auth::user();
+ $user = currentUserElement();
if (! $user) {
return false;
@@ -691,7 +691,7 @@ public function getSourceTableAttributes(string $elementType, string $sourceKey)
*/
public function getTableAttributesForFieldLayouts(array|Collection $fieldLayouts): Collection
{
- $user = Auth::user();
+ $user = currentUserElement();
$attributes = [];
/** @var CustomField[][] $groupedFieldElements */
diff --git a/src/Element/NestedElementManager.php b/src/Element/NestedElementManager.php
index 6780c7d2d6c..76b688b6051 100644
--- a/src/Element/NestedElementManager.php
+++ b/src/Element/NestedElementManager.php
@@ -485,7 +485,7 @@ private function createView(?ElementInterface $owner, array $config, string $mod
}
$authorizedOwnerId = $owner->id;
- if ($owner->isProvisionalDraft && $owner->draftCreatorId === Auth::user()?->id) {
+ if ($owner->isProvisionalDraft && $owner->draftCreatorId === Auth::craftUser()?->getCraftUserId()) {
/** @var ElementInterface $owner */
$authorizedOwnerId = $owner->getCanonicalId();
}
diff --git a/src/Element/Operations/ElementDuplicates.php b/src/Element/Operations/ElementDuplicates.php
index 948c5c794a2..bda84d42bad 100644
--- a/src/Element/Operations/ElementDuplicates.php
+++ b/src/Element/Operations/ElementDuplicates.php
@@ -112,7 +112,7 @@ public function duplicateElement(
$mainClone->setCanonicalId($element->getCanonicalId());
$mainClone->draftId = $this->drafts->insertDraftRow(
name: $mainClone->draftName,
- creatorId: Auth::user()->id,
+ creatorId: Auth::craftUser()?->getCraftUserId(),
canonicalId: $element->getCanonicalId(),
trackChanges: $mainClone->trackDraftChanges,
);
@@ -124,7 +124,7 @@ public function duplicateElement(
$mainClone->setCanonicalId(null);
$mainClone->draftId = $this->drafts->insertDraftRow(
name: $mainClone->draftName,
- creatorId: Auth::user()->id,
+ creatorId: Auth::craftUser()?->getCraftUserId(),
trackChanges: $mainClone->trackDraftChanges,
);
}
@@ -327,7 +327,7 @@ private function copyModifiedFields(ElementInterface $from, ElementInterface $to
$modifiedAttributes = array_unique($modifiedAttributes);
$modifiedFields = array_unique($modifiedFields);
- $userId = Auth::user()?->id;
+ $userId = Auth::craftUser()?->getCraftUserId();
if (! empty($modifiedAttributes)) {
$data = [];
diff --git a/src/Element/Operations/ElementWrites.php b/src/Element/Operations/ElementWrites.php
index 640883763fb..68bf571fafa 100644
--- a/src/Element/Operations/ElementWrites.php
+++ b/src/Element/Operations/ElementWrites.php
@@ -667,7 +667,7 @@ protected function saveInternal(
}
if ($trackChanges) {
- $userId = Auth::user()?->id;
+ $userId = Auth::craftUser()?->getCraftUserId();
$timestamp = now();
foreach ($dirtyAttributes as $attributeName) {
@@ -886,7 +886,7 @@ private function crossSiteValidationErrors(
$propagateToSite = $this->sites->getSiteById($siteElement->siteId);
/** @var ?User $user */
- $user = Auth::user();
+ $user = Auth::craftUser();
$message = t('Validation errors for site: “{siteName}“', [
'siteName' => $propagateToSite?->getName(),
]);
diff --git a/src/Element/Policies/ElementPolicy.php b/src/Element/Policies/ElementPolicy.php
index 792cdd2cf1c..74add51d685 100644
--- a/src/Element/Policies/ElementPolicy.php
+++ b/src/Element/Policies/ElementPolicy.php
@@ -10,8 +10,8 @@
use CraftCms\Cms\Element\Contracts\NestedElementInterface;
use CraftCms\Cms\Element\Element;
use CraftCms\Cms\Field\Contracts\ElementContainerFieldInterface;
-use CraftCms\Cms\Site\Models\Site;
use CraftCms\Cms\Support\Facades\Sites;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use Illuminate\Support\Facades\Gate;
use ReflectionMethod;
@@ -45,7 +45,7 @@ class ElementPolicy
* Runs before all ability checks.
* Returns true/false to short-circuit, or null to continue.
*/
- public function before(User $user, string $ability, mixed $element): ?bool
+ public function before(CraftUser $user, string $ability, mixed $element): ?bool
{
if (! $element instanceof ElementInterface) {
return null;
@@ -69,7 +69,7 @@ public function before(User $user, string $ability, mixed $element): ?bool
return $event->authorized;
}
- public function saveCanonical(User $user, ElementInterface $element): bool
+ public function saveCanonical(CraftUser $user, ElementInterface $element): bool
{
if ($element->getIsUnpublishedDraft()) {
$fakeCanonical = clone $element;
@@ -91,12 +91,8 @@ public function __call(string $method, array $arguments): bool
if (in_array($method, self::ABILITIES, true)) {
[$user, $element] = $arguments + [null, null];
- if (
- $user instanceof User &&
- $element instanceof ElementInterface &&
- $this->hasCustomElementAuthorizationMethod($element, $method)
- ) {
- return $element->{self::ELEMENT_AUTHORIZATION_METHODS[$method]}($user);
+ if ($user instanceof CraftUser && $element instanceof ElementInterface && $this->hasCustomElementAuthorizationMethod($element, $method)) {
+ return $element->{self::ELEMENT_AUTHORIZATION_METHODS[$method]}($user->asElement());
}
return false;
@@ -118,7 +114,7 @@ private function hasCustomElementAuthorizationMethod(ElementInterface $element,
->getName() !== Element::class;
}
- private function checkSiteAuthorization(User $user, ElementInterface $element): ?bool
+ private function checkSiteAuthorization(CraftUser $user, ElementInterface $element): ?bool
{
if (! $siteId = $element->siteId) {
return null;
@@ -132,7 +128,7 @@ private function checkSiteAuthorization(User $user, ElementInterface $element):
}
private function checkNestedElementAuthorization(
- User $user,
+ CraftUser $user,
string $ability,
ElementInterface $element,
): ?bool {
@@ -145,12 +141,14 @@ private function checkNestedElementAuthorization(
return null;
}
+ $userElement = $user->asElement();
+
return match ($ability) {
- 'view' => $field->canViewElement($element, $user),
- 'save' => $this->checkNestedSaveAuthorization($element, $user, $field),
- 'delete' => $field->canDeleteElement($element, $user),
- 'duplicate', 'duplicateAsDraft' => $field->canDuplicateElement($element, $user),
- 'deleteForSite' => $field->canDeleteElementForSite($element, $user),
+ 'view' => $field->canViewElement($element, $userElement),
+ 'save' => $this->checkNestedSaveAuthorization($element, $userElement, $field),
+ 'delete' => $field->canDeleteElement($element, $userElement),
+ 'duplicate', 'duplicateAsDraft' => $field->canDuplicateElement($element, $userElement),
+ 'deleteForSite' => $field->canDeleteElementForSite($element, $userElement),
default => null,
};
}
diff --git a/src/Element/Queries/AssetQuery.php b/src/Element/Queries/AssetQuery.php
index 43574d1dfb1..0b4a052b4c2 100644
--- a/src/Element/Queries/AssetQuery.php
+++ b/src/Element/Queries/AssetQuery.php
@@ -119,7 +119,7 @@ private function applyAuthParam(?bool $value, string $permissionPrefix, string $
return;
}
- $user = Auth::user();
+ $user = Auth::craftUser();
if (! $user) {
throw new QueryAbortedException;
@@ -144,7 +144,9 @@ private function applyAuthParam(?bool $value, string $permissionPrefix, string $
throw new QueryAbortedException;
}
- $this->where(function (Builder $query) use ($user, $fullyAuthorizedVolumeIds, $partiallyAuthorizedVolumeIds) {
+ $userId = $user->getCraftUserId();
+
+ $this->where(function (Builder $query) use ($userId, $fullyAuthorizedVolumeIds, $partiallyAuthorizedVolumeIds) {
if ($fullyAuthorizedVolumeIds) {
$query->orWhereIn('assets.volumeId', $fullyAuthorizedVolumeIds);
}
@@ -152,7 +154,7 @@ private function applyAuthParam(?bool $value, string $permissionPrefix, string $
if ($partiallyAuthorizedVolumeIds) {
$query->orWhere(fn (Builder $query) => $query
->whereIn('assets.volumeId', $partiallyAuthorizedVolumeIds)
- ->where('assets.uploaderId', $user->id),
+ ->where('assets.uploaderId', $userId),
);
}
});
@@ -164,16 +166,18 @@ private function applyAuthParam(?bool $value, string $permissionPrefix, string $
throw new QueryAbortedException;
}
- $this->where(function (Builder $query) use ($user, $unauthorizedVolumeIds, $partiallyAuthorizedVolumeIds) {
+ $userId = $user->getCraftUserId();
+
+ $this->where(function (Builder $query) use ($userId, $unauthorizedVolumeIds, $partiallyAuthorizedVolumeIds) {
if ($unauthorizedVolumeIds) {
$query->orWhereIn('assets.volumeId', $unauthorizedVolumeIds);
}
if ($partiallyAuthorizedVolumeIds) {
- $query->orWhere(function (Builder $query) use ($user, $partiallyAuthorizedVolumeIds) {
+ $query->orWhere(function (Builder $query) use ($userId, $partiallyAuthorizedVolumeIds) {
$query->whereIn('assets.volumeId', $partiallyAuthorizedVolumeIds)
- ->where(function (Builder $query) use ($user) {
- $query->where('assets.uploaderId', '!=', $user->id)
+ ->where(function (Builder $query) use ($userId) {
+ $query->where('assets.uploaderId', '!=', $userId)
->orWhereNull('assets.uploaderId');
});
});
diff --git a/src/Element/Queries/Concerns/QueriesDraftsAndRevisions.php b/src/Element/Queries/Concerns/QueriesDraftsAndRevisions.php
index b3b88470363..f59574e4b60 100644
--- a/src/Element/Queries/Concerns/QueriesDraftsAndRevisions.php
+++ b/src/Element/Queries/Concerns/QueriesDraftsAndRevisions.php
@@ -367,7 +367,7 @@ public function draftOf(mixed $value): static
* ```php
* // Fetch drafts by the current user
* ${elements-var} = {php-method}
- * ->draftCreator(Auth::user())
+ * ->draftCreator(Auth::craftUser())
* ->all();
* ```
*/
@@ -403,7 +403,7 @@ public function draftCreator(mixed $value): static
* // Fetch provisional drafts created by the current user
* ${elements-var} = {php-method}
* ->provisionalDrafts()
- * ->draftCreator(Auth::user())
+ * ->draftCreator(Auth::craftUser())
* ->all();
* ```
*/
@@ -588,7 +588,7 @@ public function revisionOf(mixed $value): static
* ```php
* // Fetch revisions by the current user
* ${elements-var} = {php-method}
- * ->revisionCreator(Auth::user())
+ * ->revisionCreator(Auth::craftUser())
* ->all();
* ```
*/
diff --git a/src/Element/Queries/EntryQuery.php b/src/Element/Queries/EntryQuery.php
index 51a3946eebe..bed6c901e54 100644
--- a/src/Element/Queries/EntryQuery.php
+++ b/src/Element/Queries/EntryQuery.php
@@ -227,7 +227,7 @@ private function applyAuthParam(
return;
}
- $user = Auth::user();
+ $user = Auth::craftUser();
if (! $user) {
throw new QueryAbortedException;
@@ -253,21 +253,23 @@ private function applyAuthParam(
if ($excludePeerEntries || $excludePeerDrafts) {
$partialAccessSections[] = $section->id;
- $query->orWhere(function (Builder $query) use ($excludePeerDrafts, $user, $excludePeerEntries, $section) {
+ $userId = $user->getCraftUserId();
+
+ $query->orWhere(function (Builder $query) use ($excludePeerDrafts, $userId, $excludePeerEntries, $section) {
$query->where('entries.sectionId', $section->id);
if ($excludePeerEntries) {
$query->whereExists(
DB::table(Table::ENTRIES_AUTHORS, 'entries_authors')
->whereColumn('entries_authors.entryId', 'entries.id')
- ->where('entries_authors.authorId', $user->id)
+ ->where('entries_authors.authorId', $userId)
);
}
if ($excludePeerDrafts) {
- $query->where(function (Builder $query) use ($user) {
+ $query->where(function (Builder $query) use ($userId) {
$query->whereNull('elements.draftId')
- ->orWhere('drafts.creatorId', $user->id);
+ ->orWhere('drafts.creatorId', $userId);
});
}
});
diff --git a/src/Element/Revisions.php b/src/Element/Revisions.php
index e266dc33d63..3d53375718d 100644
--- a/src/Element/Revisions.php
+++ b/src/Element/Revisions.php
@@ -97,7 +97,7 @@ public function createRevision(
if ($creatorId === null) {
// Default to the logged-in user ID if there is one
- $creatorId = Auth::user()?->id;
+ $creatorId = Auth::craftUser()?->getCraftUserId();
}
event($event = new RevisionCreating(
diff --git a/src/Entry/Data/EntryType.php b/src/Entry/Data/EntryType.php
index db694876b09..8213a47e2e6 100644
--- a/src/Entry/Data/EntryType.php
+++ b/src/Entry/Data/EntryType.php
@@ -166,7 +166,7 @@ public function getActionMenuItems(): array
return [];
}
- if (! Auth::user()?->isAdmin()) {
+ if (! Auth::craftUser()?->isAdmin()) {
return [];
}
@@ -220,7 +220,7 @@ public function getEagerLoadingPrefix(): string
public function getCpEditUrl(): ?string
{
- if (! $this->id || ! Auth::user()?->isAdmin()) {
+ if (! $this->id || ! Auth::craftUser()?->isAdmin()) {
return null;
}
diff --git a/src/Entry/Elements/Entry.php b/src/Entry/Elements/Entry.php
index 53192bd3558..9b234b244cd 100644
--- a/src/Entry/Elements/Entry.php
+++ b/src/Entry/Elements/Entry.php
@@ -82,6 +82,7 @@
use CraftCms\Cms\Support\Str;
use CraftCms\Cms\Support\Url;
use CraftCms\Cms\Twig\Attributes\AllowedInSandbox;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use CraftCms\RulesetValidation\Attributes\Ruleset;
use DateInterval;
@@ -95,6 +96,7 @@
use RuntimeException;
use Tpetry\QueryExpressions\Language\Alias;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\renderObjectTemplate;
use function CraftCms\Cms\t;
@@ -406,7 +408,7 @@ protected static function defineSources(string $context): array
SectionType::Structure->value => t('Structures'),
];
- $user = Auth::user();
+ $user = Auth::craftUser();
foreach ($sectionTypes as $type => $heading) {
if (! empty($sectionsByType[$type])) {
@@ -534,7 +536,7 @@ protected static function defineActions(string $source): array
$actions = [];
if ($section) {
- $user = Auth::user();
+ $user = Auth::craftUser();
if (
$section->type === SectionType::Structure &&
@@ -764,7 +766,7 @@ protected static function defineDefaultTableAttributes(string $source): array
#[Override]
protected static function defineCardAttributes(): array
{
- $currentUser = Auth::user();
+ $currentUser = currentUserElement();
$attributes = array_merge(parent::defineCardAttributes(), [
'section' => [
@@ -1220,7 +1222,7 @@ protected function crumbs(): array
}
if ($section->type === SectionType::Structure) {
- $user = Auth::user();
+ $user = Auth::craftUser();
$ancestors = $this->getAncestors();
if ($ancestors instanceof ElementQueryInterface) {
@@ -1810,7 +1812,7 @@ protected function safeActionMenuItems(): array
if (
app(ElementRequest::class)->element === $this &&
- Auth::user()?->isAdmin() &&
+ Auth::craftUser()?->isAdmin() &&
Cms::config()->allowAdminChanges
) {
// Entry type settings
@@ -1997,9 +1999,9 @@ protected function htmlAttributes(string $context): array
/**
* Returns whether the given user is authorized to move this entry to a different section.
*/
- public function canMove(?User $user = null): bool
+ public function canMove(?CraftUser $user = null): bool
{
- $user ??= Auth::user();
+ $user ??= currentUserElement();
if (! $user) {
return false;
@@ -2009,6 +2011,8 @@ public function canMove(?User $user = null): bool
return false;
}
+ $userId = $user->getCraftUserId();
+
// disallow moving singles and trashed entries
if ($section->type === SectionType::Single || $this->trashed) {
return false;
@@ -2020,14 +2024,14 @@ public function canMove(?User $user = null): bool
}
if ($this->getIsDraft()) {
- return $this->draftCreatorId === $user->id || $user->can("savePeerEntryDrafts:$section->uid");
+ return $this->draftCreatorId === $userId || $user->can("savePeerEntryDrafts:$section->uid");
}
if (! $user->can("saveEntries:$section->uid")) {
return false;
}
- return in_array($user->id, $this->getAuthorIds(), true) || $user->can("savePeerEntries:$section->uid");
+ return ($userId !== null && in_array($userId, $this->getAuthorIds(), true)) || $user->can("savePeerEntries:$section->uid");
}
/**
@@ -2053,7 +2057,7 @@ public function metaFieldsHtml(bool $static): string
{
$fields = [];
$section = $this->getSection();
- $user = Auth::user();
+ $user = currentUserElement();
$this->_applyActionBtnEntryTypeCompatibility();
@@ -2230,18 +2234,19 @@ private function _applyActionBtnEntryTypeCompatibility(): void
/**
* Returns whether the current user has permission to change this entry’s author.
*/
- private function canChangeAuthor(?User $user = null): bool
+ private function canChangeAuthor(?CraftUser $user = null): bool
{
- if (! $user && ! $user = Auth::user()) {
+ if (! $user && ! $user = currentUserElement()) {
return false;
}
$section = $this->getSection();
$authorIds = $this->getAuthorIds();
+ $userId = $user->getCraftUserId();
return
empty($authorIds) ||
- in_array($user->id, $authorIds) ||
+ ($userId !== null && in_array($userId, $authorIds, true)) ||
$user->can("changeAuthorForPeerEntries:$section->uid");
}
@@ -2428,7 +2433,7 @@ private function maybeSetDefaultAttributes(): void
if ($section?->type !== SectionType::Single
&& $section?->minAuthors === 1
&& empty($this->getAuthors())
- && $user = Auth::user()
+ && $user = currentUserElement()
) {
$this->setAuthor($user);
}
diff --git a/src/Entry/Policies/EntryPolicy.php b/src/Entry/Policies/EntryPolicy.php
index 229cb4cd287..f98b7d34188 100644
--- a/src/Entry/Policies/EntryPolicy.php
+++ b/src/Entry/Policies/EntryPolicy.php
@@ -8,43 +8,47 @@
use CraftCms\Cms\Element\Policies\ElementPolicy;
use CraftCms\Cms\Entry\Elements\Entry;
use CraftCms\Cms\Section\Enums\SectionType;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
class EntryPolicy extends ElementPolicy
{
- public function view(User $user, Entry $entry): bool
+ public function view(CraftUser $user, Entry $entry): bool
{
if (! $section = $entry->getSection()) {
return false;
}
+ $userId = $user->getCraftUserId();
+
if (! $user->can("viewEntries:$section->uid")) {
return false;
}
if ($entry->getIsDraft()) {
- return $entry->draftCreatorId === $user->id
+ return $entry->draftCreatorId === $userId
|| $user->can("viewPeerEntryDrafts:$section->uid");
}
return $section->type === SectionType::Single
- || in_array($user->id, $entry->getAuthorIds(), true)
+ || in_array($userId, $entry->getAuthorIds(), true)
|| $user->can("viewPeerEntries:$section->uid");
}
- public function save(User $user, Entry $entry): bool
+ public function save(CraftUser $user, Entry $entry): bool
{
if (! $section = $entry->getSection()) {
return false;
}
+ $userId = $user->getCraftUserId();
+
if (! $entry->id) {
return $section->type !== SectionType::Single
&& $user->can("createEntries:$section->uid");
}
if ($entry->getIsDraft()) {
- return $entry->draftCreatorId === $user->id
+ return $entry->draftCreatorId === $userId
|| $user->can("savePeerEntryDrafts:$section->uid");
}
@@ -53,22 +57,24 @@ public function save(User $user, Entry $entry): bool
}
return $section->type === SectionType::Single
- || in_array($user->id, $entry->getAuthorIds(), true)
+ || in_array($userId, $entry->getAuthorIds(), true)
|| $user->can("savePeerEntries:$section->uid");
}
- public function delete(User $user, Entry $entry): bool
+ public function delete(CraftUser $user, Entry $entry): bool
{
if (! $section = $entry->getSection()) {
return false;
}
+ $userId = $user->getCraftUserId();
+
if ($section->type === SectionType::Single && ! $entry->getIsDraft()) {
return false;
}
if ($entry->getIsDraft()) {
- return $entry->draftCreatorId === $user->id
+ return $entry->draftCreatorId === $userId
|| $user->can("deletePeerEntryDrafts:$section->uid");
}
@@ -76,11 +82,11 @@ public function delete(User $user, Entry $entry): bool
return false;
}
- return in_array($user->id, $entry->getAuthorIds(), true)
+ return in_array($userId, $entry->getAuthorIds(), true)
|| $user->can("deletePeerEntries:$section->uid");
}
- public function duplicate(User $user, Entry $entry): bool
+ public function duplicate(CraftUser $user, Entry $entry): bool
{
if (! $section = $entry->getSection()) {
return false;
@@ -91,7 +97,7 @@ public function duplicate(User $user, Entry $entry): bool
&& $user->can("saveEntries:$section->uid");
}
- public function duplicateAsDraft(User $user, Entry $entry): bool
+ public function duplicateAsDraft(CraftUser $user, Entry $entry): bool
{
if (! $section = $entry->getSection()) {
return false;
@@ -101,28 +107,30 @@ public function duplicateAsDraft(User $user, Entry $entry): bool
&& $user->can("createEntries:$section->uid");
}
- public function copy(User $user, Entry $entry): bool
+ public function copy(CraftUser $user, Entry $entry): bool
{
return $this->view($user, $entry);
}
- public function createDrafts(User $user, Entry $entry): bool
+ public function createDrafts(CraftUser $user, Entry $entry): bool
{
return true;
}
- public function deleteForSite(User $user, Entry $entry): bool
+ public function deleteForSite(CraftUser $user, Entry $entry): bool
{
if (! $section = $entry->getSection()) {
return false;
}
+ $userId = $user->getCraftUserId();
+
if ($section->propagationMethod !== PropagationMethod::Custom) {
return false;
}
if ($entry->getIsDraft()) {
- return $entry->draftCreatorId === $user->id
+ return $entry->draftCreatorId === $userId
|| $user->can("deletePeerEntryDrafts:$section->uid");
}
@@ -130,7 +138,7 @@ public function deleteForSite(User $user, Entry $entry): bool
return false;
}
- return in_array($user->id, $entry->getAuthorIds(), true)
+ return in_array($userId, $entry->getAuthorIds(), true)
|| $user->can("deletePeerEntriesForSite:$section->uid");
}
}
diff --git a/src/Field/Addresses.php b/src/Field/Addresses.php
index 2155dcaa78c..de4a42706ec 100644
--- a/src/Field/Addresses.php
+++ b/src/Field/Addresses.php
@@ -421,7 +421,7 @@ private function createAddressesFromSerializedData(array $value, ElementInterfac
// Is this a derivative element, and does the entry primarily belong to the canonical?
if ($element->getIsDerivative() && $address->getPrimaryOwnerId() === $element->getCanonicalId()) {
// Duplicate it as a draft. (We'll drop its draft status from NestedElementManager::saveNestedElements().)
- $address = app(Drafts::class)->createDraft($address, Auth::user()->id, null, null, [
+ $address = app(Drafts::class)->createDraft($address, Auth::craftUser()?->getCraftUserId(), null, null, [
'canonicalId' => $address->id,
'primaryOwnerId' => $element->id,
'owner' => $element,
diff --git a/src/Field/Conditions/FieldConditionRuleTrait.php b/src/Field/Conditions/FieldConditionRuleTrait.php
index 89121f38f53..8f3e9ec4fde 100644
--- a/src/Field/Conditions/FieldConditionRuleTrait.php
+++ b/src/Field/Conditions/FieldConditionRuleTrait.php
@@ -178,7 +178,7 @@ public function getLabelHint(): ?string
public function showLabelHint(): bool
{
- return Auth::user()?->getPreference('showFieldHandles') ?? false;
+ return Auth::craftUser()?->getPreference('showFieldHandles') ?? false;
}
public function getExclusiveQueryParams(): array
diff --git a/src/Field/Email.php b/src/Field/Email.php
index 926c0e2ad2c..049b7df937a 100644
--- a/src/Field/Email.php
+++ b/src/Field/Email.php
@@ -150,7 +150,7 @@ public function getPreviewHtml(mixed $value, ElementInterface $element): string
public function previewPlaceholderHtml(mixed $value, ?ElementInterface $element): string
{
if (! $value) {
- $value = Auth::user()->email;
+ $value = Auth::craftUser()?->asElement()->email;
}
return $this->getPreviewHtml($value, $element ?? new Entry);
diff --git a/src/Field/Field.php b/src/Field/Field.php
index b29c5b41fc2..f45de9dd34b 100644
--- a/src/Field/Field.php
+++ b/src/Field/Field.php
@@ -450,7 +450,7 @@ public function getIcon(): ?string
public function getCpEditUrl(): ?string
{
- if (! $this->id || ! Auth::user()?->isAdmin()) {
+ if (! $this->id || ! Auth::craftUser()?->isAdmin()) {
return null;
}
@@ -474,7 +474,7 @@ protected function actionMenuItems(): array
return $items;
}
- if (! Auth::user()?->isAdmin()) {
+ if (! Auth::craftUser()?->isAdmin()) {
return $items;
}
diff --git a/src/Field/Policies/ContentBlockPolicy.php b/src/Field/Policies/ContentBlockPolicy.php
index 1d40f6a4ae1..1b65f54c781 100644
--- a/src/Field/Policies/ContentBlockPolicy.php
+++ b/src/Field/Policies/ContentBlockPolicy.php
@@ -7,11 +7,11 @@
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Policies\ElementPolicy;
use CraftCms\Cms\Field\Elements\ContentBlock;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
class ContentBlockPolicy extends ElementPolicy
{
- public function view(User $user, ContentBlock $contentBlock): bool
+ public function view(CraftUser $user, ContentBlock $contentBlock): bool
{
if (! $owner = $this->getOwner($contentBlock)) {
return false;
@@ -20,7 +20,7 @@ public function view(User $user, ContentBlock $contentBlock): bool
return $user->can('view', $owner);
}
- public function save(User $user, ContentBlock $contentBlock): bool
+ public function save(CraftUser $user, ContentBlock $contentBlock): bool
{
if (! $owner = $this->getOwner($contentBlock)) {
return false;
@@ -29,7 +29,7 @@ public function save(User $user, ContentBlock $contentBlock): bool
return $user->can('save', $owner);
}
- public function delete(User $user, ContentBlock $contentBlock): bool
+ public function delete(CraftUser $user, ContentBlock $contentBlock): bool
{
if (! $owner = $this->getOwner($contentBlock)) {
return false;
@@ -38,7 +38,7 @@ public function delete(User $user, ContentBlock $contentBlock): bool
return $user->can('save', $owner);
}
- public function duplicate(User $user, ContentBlock $contentBlock): bool
+ public function duplicate(CraftUser $user, ContentBlock $contentBlock): bool
{
if (! $owner = $this->getOwner($contentBlock)) {
return false;
@@ -47,12 +47,12 @@ public function duplicate(User $user, ContentBlock $contentBlock): bool
return $user->can('save', $owner);
}
- public function copy(User $user, ContentBlock $contentBlock): bool
+ public function copy(CraftUser $user, ContentBlock $contentBlock): bool
{
return $user->can('duplicate', $contentBlock);
}
- public function createDrafts(User $user, ContentBlock $contentBlock): bool
+ public function createDrafts(CraftUser $user, ContentBlock $contentBlock): bool
{
return true;
}
diff --git a/src/FieldLayout/FieldLayoutComponent.php b/src/FieldLayout/FieldLayoutComponent.php
index 8183e0394cd..a41f4f6c4e4 100644
--- a/src/FieldLayout/FieldLayoutComponent.php
+++ b/src/FieldLayout/FieldLayoutComponent.php
@@ -14,9 +14,9 @@
use CraftCms\Cms\Support\Html;
use CraftCms\Cms\User\Conditions\UserCondition;
use CraftCms\Cms\User\Elements\User;
-use Illuminate\Support\Facades\Auth;
use Override;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
/**
@@ -291,7 +291,7 @@ public function showInForm(?ElementInterface $element = null): bool
$elementCondition = $this->getElementCondition();
if ($userCondition) {
- $currentUser = Auth::user();
+ $currentUser = currentUserElement();
if ($currentUser && ! $userCondition->matchElement($currentUser)) {
return false;
}
diff --git a/src/FieldLayout/LayoutElements/Addresses/CountryCodeField.php b/src/FieldLayout/LayoutElements/Addresses/CountryCodeField.php
index 919d41c455b..fac2e957a9b 100644
--- a/src/FieldLayout/LayoutElements/Addresses/CountryCodeField.php
+++ b/src/FieldLayout/LayoutElements/Addresses/CountryCodeField.php
@@ -110,7 +110,7 @@ protected function actionMenuItems(?ElementInterface $element = null, bool $stat
{
$items = [];
- if (Auth::user()?->isAdmin()) {
+ if (Auth::craftUser()?->isAdmin()) {
$items[] = $this->copyAttributeAction();
}
diff --git a/src/FieldLayout/LayoutElements/Addresses/LatLongField.php b/src/FieldLayout/LayoutElements/Addresses/LatLongField.php
index 76ed8a19461..dcda1f0046b 100644
--- a/src/FieldLayout/LayoutElements/Addresses/LatLongField.php
+++ b/src/FieldLayout/LayoutElements/Addresses/LatLongField.php
@@ -88,7 +88,7 @@ protected function inputHtml(?ElementInterface $element = null, bool $static = f
throw new InvalidArgumentException(sprintf('%s can only be used in address field layouts.', self::class));
}
- $isAdmin = Auth::user()?->isAdmin();
+ $isAdmin = Auth::craftUser()?->isAdmin();
return
Html::beginTag('div', ['class' => 'flex-fields']).
diff --git a/src/FieldLayout/LayoutElements/CustomField.php b/src/FieldLayout/LayoutElements/CustomField.php
index 40a3fded224..7758569be6a 100644
--- a/src/FieldLayout/LayoutElements/CustomField.php
+++ b/src/FieldLayout/LayoutElements/CustomField.php
@@ -30,6 +30,7 @@
use RuntimeException;
use Throwable;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
use function CraftCms\Cms\template;
@@ -721,7 +722,7 @@ public function editable(?ElementInterface $element): bool
$editCondition = $this->getEditCondition();
if ($editCondition) {
- $currentUser = Auth::user();
+ $currentUser = currentUserElement();
if (! $currentUser || ! $editCondition->matchElement($currentUser)) {
return false;
}
@@ -876,7 +877,7 @@ protected function actionMenuItems(?ElementInterface $element = null, bool $stat
$items = [];
}
- $user = Auth::user();
+ $user = Auth::craftUser();
if ($user?->isAdmin() && ! $user->getPreference('showFieldHandles')) {
$items[] = $this->copyAttributeAction([
'label' => t('Copy field handle'),
diff --git a/src/FieldLayout/LayoutElements/FullNameField.php b/src/FieldLayout/LayoutElements/FullNameField.php
index cad221ae4c1..be7ed45cd39 100644
--- a/src/FieldLayout/LayoutElements/FullNameField.php
+++ b/src/FieldLayout/LayoutElements/FullNameField.php
@@ -62,7 +62,7 @@ private function firstAndLastNameFields(?ElementInterface $element, bool $static
$statusClass = $this->statusClass($element);
$status = $statusClass ? [$statusClass, $this->statusLabel($element, $static) ?? ucfirst($statusClass)] : null;
$required = ! $static && $this->required;
- $isAdmin = Auth::user()?->isAdmin();
+ $isAdmin = Auth::craftUser()?->isAdmin();
return HtmlHelper::beginTag('div', ['class' => ['flex', 'flex-nowrap', 'fullwidth']]).
FormFields::textFieldHtml([
diff --git a/src/FieldLayout/LayoutElements/TextField.php b/src/FieldLayout/LayoutElements/TextField.php
index e21b83ff581..fed0237b9fe 100644
--- a/src/FieldLayout/LayoutElements/TextField.php
+++ b/src/FieldLayout/LayoutElements/TextField.php
@@ -161,7 +161,7 @@ protected function actionMenuItems(?ElementInterface $element = null, bool $stat
{
$items = [];
- if (Auth::user()?->isAdmin()) {
+ if (Auth::craftUser()?->isAdmin()) {
$items[] = $this->copyAttributeAction();
}
diff --git a/src/FieldLayout/LayoutElements/TextareaField.php b/src/FieldLayout/LayoutElements/TextareaField.php
index ce1faba054d..0db451da786 100644
--- a/src/FieldLayout/LayoutElements/TextareaField.php
+++ b/src/FieldLayout/LayoutElements/TextareaField.php
@@ -124,7 +124,7 @@ protected function actionMenuItems(?ElementInterface $element = null, bool $stat
{
$items = [];
- if (Auth::user()?->isAdmin()) {
+ if (Auth::craftUser()?->isAdmin()) {
$items[] = $this->copyAttributeAction();
}
diff --git a/src/FieldLayout/LayoutElements/TitleField.php b/src/FieldLayout/LayoutElements/TitleField.php
index a457bb1931a..8836ec87392 100644
--- a/src/FieldLayout/LayoutElements/TitleField.php
+++ b/src/FieldLayout/LayoutElements/TitleField.php
@@ -109,7 +109,7 @@ protected function actionMenuItems(?ElementInterface $element = null, bool $stat
{
$items = [];
- if (Auth::user()?->isAdmin()) {
+ if (Auth::craftUser()?->isAdmin()) {
$items[] = $this->copyAttributeAction();
}
diff --git a/src/FieldLayout/LayoutElements/Users/AffiliatedSiteField.php b/src/FieldLayout/LayoutElements/Users/AffiliatedSiteField.php
index 73aafc2ff1e..ed6a8ee6554 100644
--- a/src/FieldLayout/LayoutElements/Users/AffiliatedSiteField.php
+++ b/src/FieldLayout/LayoutElements/Users/AffiliatedSiteField.php
@@ -86,7 +86,7 @@ protected function actionMenuItems(?ElementInterface $element = null, bool $stat
{
$items = [];
- if (Auth::user()?->isAdmin()) {
+ if (Auth::craftUser()?->isAdmin()) {
$items[] = $this->copyAttributeAction([
'attribute' => 'affiliatedSite',
]);
diff --git a/src/FieldLayout/LayoutElements/Users/PhotoField.php b/src/FieldLayout/LayoutElements/Users/PhotoField.php
index 8eaf327a18a..806aac0e424 100644
--- a/src/FieldLayout/LayoutElements/Users/PhotoField.php
+++ b/src/FieldLayout/LayoutElements/Users/PhotoField.php
@@ -99,7 +99,7 @@ protected function actionMenuItems(?ElementInterface $element = null, bool $stat
{
$items = [];
- if (Auth::user()?->isAdmin()) {
+ if (Auth::craftUser()?->isAdmin()) {
$items[] = $this->copyAttributeAction();
}
diff --git a/src/Http/Controllers/App/CpAlertsController.php b/src/Http/Controllers/App/CpAlertsController.php
index 37b79b15f8e..1c1f170ccb8 100644
--- a/src/Http/Controllers/App/CpAlertsController.php
+++ b/src/Http/Controllers/App/CpAlertsController.php
@@ -38,7 +38,7 @@ public function destroy(Request $request, Users $users): Response
'message' => ['required', 'string'],
])['message'];
- $users->shunMessageForUser($request->user()->id, $message, now()->addDay()->toDateTime());
+ $users->shunMessageForUser($request->craftUser()?->getCraftUserId(), $message, now()->addDay()->toDateTime());
return $this->asSuccess();
}
diff --git a/src/Http/Controllers/Assets/ImageEditorController.php b/src/Http/Controllers/Assets/ImageEditorController.php
index d82ec409af0..ab2fac92d8d 100644
--- a/src/Http/Controllers/Assets/ImageEditorController.php
+++ b/src/Http/Controllers/Assets/ImageEditorController.php
@@ -120,7 +120,7 @@ public function save(Request $request, Elements $elements): Response
$folder = $asset->getFolder();
// Do what you want with your own photo.
- if ($asset->id !== $request->user()->photoId) {
+ if ($asset->id !== $request->craftUser()?->asElement()->photoId) {
$this->requireVolumePermissionByAsset('editImages', $asset);
$this->requirePeerVolumePermissionByAsset('editPeerImages', $asset);
}
diff --git a/src/Http/Controllers/Assets/PreviewController.php b/src/Http/Controllers/Assets/PreviewController.php
index 8b3eb9add4c..c5cf651880d 100644
--- a/src/Http/Controllers/Assets/PreviewController.php
+++ b/src/Http/Controllers/Assets/PreviewController.php
@@ -72,7 +72,7 @@ public function previewFile(Request $request): Response
$previewHandler = $this->assets->getAssetPreviewHandler($asset);
$variables = [];
- if (($previewHandler instanceof ImagePreview) && $asset->id !== $request->user()->photoId) {
+ if (($previewHandler instanceof ImagePreview) && $asset->id !== $request->craftUser()?->asElement()->photoId) {
$variables['editFocal'] = true;
try {
diff --git a/src/Http/Controllers/Assets/UploadController.php b/src/Http/Controllers/Assets/UploadController.php
index 8c5df9eb4ed..8b5c2e01381 100644
--- a/src/Http/Controllers/Assets/UploadController.php
+++ b/src/Http/Controllers/Assets/UploadController.php
@@ -108,7 +108,7 @@ public function upload(Request $request): Response
$asset->setMimeType(File::getMimeType($tempPath, checkExtension: false) ?? $uploadedFile->getClientMimeType());
$asset->newFolderId = $folder->id;
$asset->setVolumeId($folder->volumeId);
- $asset->uploaderId = $request->user()->id;
+ $asset->uploaderId = $request->craftUser()?->getCraftUserId();
$asset->avoidFilenameConflicts = true;
if (isset($originalFilename)) {
diff --git a/src/Http/Controllers/Auth/AuthenticationController.php b/src/Http/Controllers/Auth/AuthenticationController.php
index 945f198bf3e..c6570b48abc 100644
--- a/src/Http/Controllers/Auth/AuthenticationController.php
+++ b/src/Http/Controllers/Auth/AuthenticationController.php
@@ -12,6 +12,7 @@
use CraftCms\Cms\Config\GeneralConfig;
use CraftCms\Cms\Http\RespondsWithFlash;
use CraftCms\Cms\Support\Str;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use CraftCms\Cms\User\Events\EmailVerified;
use CraftCms\Cms\User\Events\UserEmailVerifying;
@@ -36,29 +37,30 @@ public function __construct(
protected AuthMethods $auth,
) {}
- protected function completeLogin(Request $request, User $user, bool $remember): Response
+ protected function completeLogin(Request $request, CraftUser $user, bool $remember): Response
{
- auth('craft')->login($user, $remember);
+ auth('craft')->loginUsingId($user->getAuthIdentifier(), $remember);
return $this->handleSuccessfulLogin($request, $user);
}
- protected function handleSuccessfulLogin(Request $request, User $user): Response
+ protected function handleSuccessfulLogin(Request $request, CraftUser $user): Response
{
$returnUrl = URL::returnUrl();
+ $userElement = $user->asElement();
if ($request->wantsJson()) {
- return $this->asModelSuccess($user, modelName: 'user', data: [
+ return $this->asModelSuccess($userElement, modelName: 'user', data: [
'returnUrl' => $returnUrl,
]);
}
- return $this->redirectToPostedUrl($user, $returnUrl);
+ return $this->redirectToPostedUrl($userElement, $returnUrl);
}
protected function finalizeLogin(
Request $request,
- User $user,
+ CraftUser $user,
bool $remember,
bool $skipTwoFactor = false,
): Response {
@@ -83,7 +85,7 @@ protected function finalizeLogin(
return $this->completeLogin($request, $user, $remember);
}
- protected function handleLoginFailure(Request $request, ?AuthError $authError = null, ?User $user = null): Response
+ protected function handleLoginFailure(Request $request, ?AuthError $authError = null, ?CraftUser $user = null): Response
{
[$authError, $message] = $this->auth->getLoginFailureInfo($authError, $user);
@@ -130,7 +132,7 @@ protected function processTokenRequest(Request $request): Response|array
}
// If someone is logged in and it’s not this person, log them out
- if ($request->user() && $request->user()->id !== $user->id) {
+ if ($request->craftUser() && $request->craftUser()->getCraftUserId() !== $user->id) {
auth('craft')->logout();
}
@@ -187,9 +189,7 @@ protected function maybeLoginUserAfterAccountActivation(User $user): bool
return false;
}
- auth('craft')->login($user);
-
- return true;
+ return auth('craft')->loginUsingId($user->id);
}
protected function redirectUserToCp(User $user): ?Response
diff --git a/src/Http/Controllers/Auth/LoginController.php b/src/Http/Controllers/Auth/LoginController.php
index c1de15811d0..99edcd8d6fc 100644
--- a/src/Http/Controllers/Auth/LoginController.php
+++ b/src/Http/Controllers/Auth/LoginController.php
@@ -7,12 +7,18 @@
use CraftCms\Cms\Auth\AuthMethods;
use CraftCms\Cms\Auth\Enums\AuthError;
use CraftCms\Cms\Auth\Enums\CpAuthPath;
+use CraftCms\Cms\Auth\Events\LoginUserRetrieved;
+use CraftCms\Cms\Auth\Events\LoginUserRetrieving;
use CraftCms\Cms\Auth\Impersonation;
-use CraftCms\Cms\Auth\UserProvider;
use CraftCms\Cms\Config\GeneralConfig;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use CraftCms\Cms\User\Models\User;
use CraftCms\Cms\View\HtmlStack;
use CraftCms\Cms\View\TemplateMode;
+use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\View\View;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
@@ -20,6 +26,7 @@
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
use Symfony\Component\HttpFoundation\Response;
+use Tpetry\QueryExpressions\Function\String\Lower;
use function CraftCms\Cms\cp_url;
use function CraftCms\Cms\template;
@@ -29,7 +36,7 @@
public function showLogin(Request $request, GeneralConfig $generalConfig, AuthMethods $authMethods): Response|View|\Inertia\Response
{
// see if they're already logged in
- if ($user = $request->user()) {
+ if ($user = $request->craftUser()) {
return $this->handleSuccessfulLogin($request, $user);
}
@@ -98,37 +105,55 @@ public function attemptLogin(Request $request, Impersonation $impersonation): Re
]);
/**
- * @var UserProvider $provider
+ * @var EloquentUserProvider $provider
*
* @phpstan-ignore method.notFound
*/
$provider = auth('craft')->getProvider();
- $user = $provider->retrieveByCredentials($request->only('loginName', 'password'));
+ $user = $this->retrieveLoginUser($request->input('loginName'));
return new Timebox()->call(function () use ($request, $provider, $user, $impersonation) {
- if (! $user || $user->password === null) {
+ if (! $user || $user->getAuthPassword() === null) {
return $this->handleLoginFailure($request, AuthError::InvalidCredentials);
}
- if (! $provider->validateCredentials($user, ['password' => $request->input('password')])) {
- return $this->handleLoginFailure($request, $provider->getAuthError(), $user);
+ if (! $this->auth->authenticate($user, ['password' => $request->input('password')])) {
+ return $this->handleLoginFailure($request, $this->auth->authError, $user);
}
// Valid credentials
- if (config('hashing.rehash_on_login', true)) {
+ if (config('hashing.rehash_on_login', true) && $user instanceof Model) {
$provider->rehashPasswordIfRequired($user, ['password' => $request->input('password')]);
}
// if we're impersonating, pass the user we're impersonating to the complete method
if ($impersonation->isImpersonating()) {
- $user = $request->user() ?? $user;
+ $user = $request->craftUser() ?? $user;
}
return $this->finalizeLogin($request, $user, $request->boolean('rememberMe'));
}, 30_000);
}
+ private function retrieveLoginUser(string $loginName): ?CraftUser
+ {
+ event($retrieving = new LoginUserRetrieving($loginName));
+
+ $user = $retrieving->user ?? User::query()
+ ->where(function (Builder $query) use ($loginName) {
+ $loginName = mb_strtolower($loginName);
+
+ $query->where(new Lower('username'), $loginName)
+ ->orWhere(new Lower('email'), $loginName);
+ })
+ ->first();
+
+ event($retrieved = new LoginUserRetrieved($loginName, $user));
+
+ return $retrieved->user;
+ }
+
public function logout(Request $request): Response
{
auth('craft')->logout();
diff --git a/src/Http/Controllers/Auth/PasskeyController.php b/src/Http/Controllers/Auth/PasskeyController.php
index 33c366fa3b0..49b37b49499 100644
--- a/src/Http/Controllers/Auth/PasskeyController.php
+++ b/src/Http/Controllers/Auth/PasskeyController.php
@@ -9,7 +9,8 @@
use CraftCms\Cms\Auth\Models\WebAuthn;
use CraftCms\Cms\Auth\Passkeys\Passkeys;
use CraftCms\Cms\Support\Json;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use Illuminate\Auth\SessionGuard;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
@@ -51,9 +52,11 @@ public function login(Request $request, Passkeys $passkeys, AuthMethods $auth, I
return $this->asFailure(t('Passkey authentication failed.'));
}
- $user = User::findOne(['id' => $credential->userId]);
+ /** @var SessionGuard $guard */
+ $guard = auth('craft');
+ $user = $guard->getProvider()->retrieveById($credential->userId);
- if ($user === null) {
+ if (! $user instanceof CraftUser) {
return $this->handleLoginFailure($request);
}
@@ -63,7 +66,7 @@ public function login(Request $request, Passkeys $passkeys, AuthMethods $auth, I
// if we're impersonating, pass the user we're impersonating to the complete method
if ($impersonation->isImpersonating()) {
- $user = $request->user();
+ $user = $request->craftUser() ?? $user;
}
return $this->completeLogin($request, $user, true);
diff --git a/src/Http/Controllers/Auth/SessionInfoController.php b/src/Http/Controllers/Auth/SessionInfoController.php
index 2f9a27fad91..fb9a2eac7c3 100644
--- a/src/Http/Controllers/Auth/SessionInfoController.php
+++ b/src/Http/Controllers/Auth/SessionInfoController.php
@@ -15,16 +15,17 @@
public function show(Request $request): JsonResponse
{
$data = [
- 'isGuest' => $request->user() === null,
+ 'isGuest' => $request->craftUser() === null,
'csrfTokenName' => '_token',
'csrfTokenValue' => csrf_token(),
];
- if ($user = $request->user()) {
- $data['id'] = $user->id;
- $data['uid'] = $user->uid;
- $data['username'] = $user->username;
- $data['email'] = $user->email;
+ if ($user = $request->craftUser()) {
+ $userElement = $user->asElement();
+ $data['id'] = $user->getCraftUserId();
+ $data['uid'] = $userElement->uid;
+ $data['username'] = $userElement->username;
+ $data['email'] = $userElement->email;
}
return new JsonResponse($data);
diff --git a/src/Http/Controllers/Auth/SetPasswordController.php b/src/Http/Controllers/Auth/SetPasswordController.php
index a1621dfa158..541840bf978 100644
--- a/src/Http/Controllers/Auth/SetPasswordController.php
+++ b/src/Http/Controllers/Auth/SetPasswordController.php
@@ -11,6 +11,7 @@
use CraftCms\Cms\Element\Elements;
use CraftCms\Cms\Element\Exceptions\InvalidElementException;
use CraftCms\Cms\Support\Url;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use CraftCms\Cms\User\Users;
use CraftCms\Cms\User\Validation\UserRules;
@@ -68,10 +69,11 @@ public function store(Request $request, Users $users, Elements $elements): Respo
$status = PasswordFacade::broker('craft')->reset(
[
'token' => $request->input('code'),
- 'loginName' => $user->email,
+ 'email' => $user->email,
'password' => $request->input('newPassword'),
],
- function (User $user, string $password) use ($elements) {
+ function (CraftUser $authUser, string $password) use ($elements) {
+ $user = $authUser->asElement();
$user->newPassword = $password;
$user->ruleset->useScenario(UserRules::SCENARIO_PASSWORD);
diff --git a/src/Http/Controllers/Auth/VerifyEmailController.php b/src/Http/Controllers/Auth/VerifyEmailController.php
index b8feb85598c..355fe66352b 100644
--- a/src/Http/Controllers/Auth/VerifyEmailController.php
+++ b/src/Http/Controllers/Auth/VerifyEmailController.php
@@ -72,7 +72,7 @@ public function store(Request $request, Users $users): Response|View
$users->activateUser($user);
}
- if ($request->user()) {
+ if ($request->craftUser()) {
Flash::success(t('Email verified'));
}
diff --git a/src/Http/Controllers/Dashboard/Widgets/CraftSupportController.php b/src/Http/Controllers/Dashboard/Widgets/CraftSupportController.php
index 9e267df3910..69690f3be4e 100644
--- a/src/Http/Controllers/Dashboard/Widgets/CraftSupportController.php
+++ b/src/Http/Controllers/Dashboard/Widgets/CraftSupportController.php
@@ -75,7 +75,7 @@ public function __invoke(Request $request): string
],
[
'name' => 'name',
- 'contents' => $request->user()->fullName,
+ 'contents' => $request->craftUser()?->asElement()->fullName,
],
[
'name' => 'message',
diff --git a/src/Http/Controllers/Dashboard/WidgetsController.php b/src/Http/Controllers/Dashboard/WidgetsController.php
index 56182372954..e835c2d5dca 100644
--- a/src/Http/Controllers/Dashboard/WidgetsController.php
+++ b/src/Http/Controllers/Dashboard/WidgetsController.php
@@ -59,7 +59,7 @@ public function update(Request $request): JsonResponse
'widgetId' => [
'required',
'integer',
- Rule::exists('widgets', 'id')->where('userId', $request->user()->id),
+ Rule::exists('widgets', 'id')->where('userId', $request->craftUser()?->getCraftUserId()),
],
]);
@@ -86,7 +86,7 @@ public function updateColspan(Request $request): JsonResponse
'id' => [
'required',
'integer',
- Rule::exists('widgets', 'id')->where('userId', $request->user()->id),
+ Rule::exists('widgets', 'id')->where('userId', $request->craftUser()?->getCraftUserId()),
],
'colspan' => ['required', 'integer', 'min:1', 'max:4'],
]);
@@ -105,7 +105,7 @@ public function reorder(Request $request): JsonResponse
'ids.*' => [
'required',
'integer',
- Rule::exists('widgets', 'id')->where('userId', $request->user()->id),
+ Rule::exists('widgets', 'id')->where('userId', $request->craftUser()?->getCraftUserId()),
],
]);
@@ -120,7 +120,7 @@ public function delete(Request $request): JsonResponse
'id' => [
'required',
'integer',
- Rule::exists('widgets', 'id')->where('userId', $request->user()->id),
+ Rule::exists('widgets', 'id')->where('userId', $request->craftUser()?->getCraftUserId()),
],
]);
diff --git a/src/Http/Controllers/Elements/Concerns/SavesElement.php b/src/Http/Controllers/Elements/Concerns/SavesElement.php
index 7c0bd760ade..f3909596bc1 100644
--- a/src/Http/Controllers/Elements/Concerns/SavesElement.php
+++ b/src/Http/Controllers/Elements/Concerns/SavesElement.php
@@ -9,6 +9,7 @@
use CraftCms\Cms\Http\Requests\ElementRequest;
use CraftCms\Cms\Support\Arr;
use CraftCms\Cms\Support\Facades\Sites;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use Illuminate\Support\Facades\Gate;
@@ -16,7 +17,7 @@ trait SavesElement
{
protected readonly ElementRequest $request;
- protected function canSave(ElementInterface $element, User $user): bool
+ protected function canSave(ElementInterface $element, CraftUser $user): bool
{
if ($element->getIsRevision()) {
return false;
diff --git a/src/Http/Controllers/Elements/CreateElementController.php b/src/Http/Controllers/Elements/CreateElementController.php
index 0825be11189..d3d50a4e467 100644
--- a/src/Http/Controllers/Elements/CreateElementController.php
+++ b/src/Http/Controllers/Elements/CreateElementController.php
@@ -28,7 +28,7 @@ public function __invoke(Request $request, Drafts $drafts): Response
$element = $this->createElement();
$element->ruleset->useScenario(ElementRules::SCENARIO_ESSENTIALS);
- if (! $drafts->saveElementAsDraft($element, $request->user()->id, markAsSaved: false)) {
+ if (! $drafts->saveElementAsDraft($element, $request->craftUser()?->getCraftUserId(), markAsSaved: false)) {
return new ElementResponse()->failure($element, mb_ucfirst(t('Couldn’t create {type}.', [
'type' => $element::lowerDisplayName(),
])));
diff --git a/src/Http/Controllers/Elements/EditElementController.php b/src/Http/Controllers/Elements/EditElementController.php
index e56ba34d40b..7ed73709c09 100644
--- a/src/Http/Controllers/Elements/EditElementController.php
+++ b/src/Http/Controllers/Elements/EditElementController.php
@@ -117,7 +117,7 @@ public function __invoke(): Response|CpScreenResponse
$canEditMultipleSites = count($propEditableSiteIds) > 1 || $addlEditableSites;
// Permissions
- $canSave = $this->canSave($element, $this->request->user());
+ $canSave = $this->canSave($element, $this->request->craftUser());
$canSaveCanonical = Gate::check('saveCanonical', $element);
$canCreateDrafts = Gate::check('createDrafts', $canonical);
$canDuplicate = ! $isRevision && Gate::check('duplicateAsDraft', $element);
@@ -361,7 +361,7 @@ private function contextMenuItems(
->orderByDesc('dateUpdated')
->with(['draftCreator'])
->get()
- ->filter(fn (ElementInterface $draft) => $this->request->user()->can('view', $draft))
+ ->filter(fn (ElementInterface $draft) => $this->request->craftUser()->can('view', $draft))
->all();
} else {
$drafts = [];
diff --git a/src/Http/Controllers/Elements/ElementActivityController.php b/src/Http/Controllers/Elements/ElementActivityController.php
index 2d7b4156ecb..fa754bfc285 100644
--- a/src/Http/Controllers/Elements/ElementActivityController.php
+++ b/src/Http/Controllers/Elements/ElementActivityController.php
@@ -30,9 +30,14 @@ public function __invoke(): Response
abort(400, 'No element was identified by the request.');
}
- $activity = $this->elementActivity->getRecentActivity($element, $this->request->user()->id);
+ $user = $this->request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
+
+ $activity = $this->elementActivity->getRecentActivity($element, $user->getCraftUserId());
- $this->elementActivity->trackActivity($element, ElementActivityType::View, $this->request->user());
+ $this->elementActivity->trackActivity($element, ElementActivityType::View, $user->asElement());
return new JsonResponse([
'activity' => $activity->map(fn (ElementActivityData $record) => $record->toActivityRow($element))->all(),
diff --git a/src/Http/Controllers/Elements/ElementDraftsController.php b/src/Http/Controllers/Elements/ElementDraftsController.php
index 964ce50e8d7..47267bb2874 100644
--- a/src/Http/Controllers/Elements/ElementDraftsController.php
+++ b/src/Http/Controllers/Elements/ElementDraftsController.php
@@ -68,7 +68,7 @@ public function store(): Response
if (! $element->getIsDraft() && ! $provisional) {
Gate::authorize('createDrafts', $element);
- } elseif (! $this->canSave($element, $this->request->user())) {
+ } elseif (! $this->canSave($element, $this->request->craftUser())) {
abort(403, 'User not authorized to save this element.');
}
@@ -77,13 +77,13 @@ public function store(): Response
$existingProvisionalDraft = $element::find()
->provisionalDrafts()
->draftOf($element->id)
- ->draftCreator($this->request->user()->id)
+ ->draftCreator($this->request->craftUser()?->getCraftUserId())
->site('*')
->status(null)
->one();
if ($existingProvisionalDraft) {
- Log::warning("Overwriting an existing provisional draft for element/user $element->id/{$this->request->user()->id}", [__METHOD__]);
+ Log::warning("Overwriting an existing provisional draft for element/user $element->id/{$this->request->craftUser()?->getCraftUserId()}", [__METHOD__]);
$this->elements->deleteElement($existingProvisionalDraft, true);
}
@@ -106,7 +106,7 @@ public function store(): Response
/** @var Element $element */
$draft = $this->drafts->createDraft(
canonical: $element,
- creatorId: $this->request->user()->id,
+ creatorId: $this->request->craftUser()?->getCraftUserId(),
provisional: $provisional,
);
@@ -121,7 +121,7 @@ public function store(): Response
$this->applyParamsToElement($element);
// Make sure nothing just changed that would prevent the user from saving
- if (! $this->canSave($element, $this->request->user())) {
+ if (! $this->canSave($element, $this->request->craftUser())) {
abort(403, 'User not authorized to save this element.');
}
@@ -209,7 +209,7 @@ public function ensure(): Response
$provisionalId = $element::find()
->provisionalDrafts()
->draftOf($element->id)
- ->draftCreator($this->request->user()->id)
+ ->draftCreator($this->request->craftUser()?->getCraftUserId())
->site('*')
->status(null)
->ids()[0] ?? null;
@@ -222,7 +222,7 @@ public function ensure(): Response
$draft = $this->drafts->createDraft(
canonical: $element,
- creatorId: $this->request->user()->id,
+ creatorId: $this->request->craftUser()?->getCraftUserId(),
provisional: true,
);
diff --git a/src/Http/Controllers/Elements/ElementIndex/ElementIndexController.php b/src/Http/Controllers/Elements/ElementIndex/ElementIndexController.php
index ec9bd91eb9a..69c769d5407 100644
--- a/src/Http/Controllers/Elements/ElementIndex/ElementIndexController.php
+++ b/src/Http/Controllers/Elements/ElementIndex/ElementIndexController.php
@@ -123,7 +123,7 @@ public function elementTableHtml(): JsonResponse
/** @var ElementInterface|null $element */
$element = (clone $elementQuery)
->draftOf($this->request->integer('id'))
- ->draftCreator($this->request->user())
+ ->draftCreator($this->request->craftUser())
->provisionalDrafts()
->status(null)
->one();
diff --git a/src/Http/Controllers/Elements/ElementRevisionsController.php b/src/Http/Controllers/Elements/ElementRevisionsController.php
index 4ebd78cf136..07502b445e3 100644
--- a/src/Http/Controllers/Elements/ElementRevisionsController.php
+++ b/src/Http/Controllers/Elements/ElementRevisionsController.php
@@ -73,7 +73,7 @@ public function revert(Revisions $revisions, ElementActivity $elementActivity):
Gate::authorize('save', $element->getCanonical(true));
- $canonical = $revisions->revertToRevision($element, $this->request->user()->id);
+ $canonical = $revisions->revertToRevision($element, $this->request->craftUser()?->getCraftUserId());
$elementActivity->trackActivity($canonical, ElementActivityType::Save);
diff --git a/src/Http/Controllers/Elements/SaveElementController.php b/src/Http/Controllers/Elements/SaveElementController.php
index 9760dfc7692..8f2740b0492 100644
--- a/src/Http/Controllers/Elements/SaveElementController.php
+++ b/src/Http/Controllers/Elements/SaveElementController.php
@@ -105,7 +105,7 @@ public function store(): Response
$provisional = $element::find()
->provisionalDrafts()
->draftOf($element->id)
- ->draftCreator($this->request->user())
+ ->draftCreator($this->request->craftUser())
->siteId($element->siteId)
->status(null)
->one();
diff --git a/src/Http/Controllers/Entries/CreateEntryController.php b/src/Http/Controllers/Entries/CreateEntryController.php
index ffc7a3d1e73..3a761c08595 100644
--- a/src/Http/Controllers/Entries/CreateEntryController.php
+++ b/src/Http/Controllers/Entries/CreateEntryController.php
@@ -48,7 +48,10 @@ public function __invoke(Drafts $drafts, Users $users): Response
return $site;
}
- $user = $this->request->user();
+ $user = $this->request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
// Create & populate the draft
$entry = new Entry;
@@ -59,7 +62,7 @@ public function __invoke(Drafts $drafts, Users $users): Response
$entry->setAuthorIds(
$this->request->input('authorIds') ??
$this->request->input('authorId') ??
- $user->id
+ $user->getCraftUserId()
);
}
@@ -76,7 +79,7 @@ public function __invoke(Drafts $drafts, Users $users): Response
}
// Make sure the user is allowed to create this entry
- $craftUser = $users->getUserById($user->id);
+ $craftUser = $users->getUserById($user->getCraftUserId());
Gate::forUser($craftUser)->authorize('save', $entry);
@@ -92,7 +95,7 @@ public function __invoke(Drafts $drafts, Users $users): Response
// Save it
$entry->ruleset->useScenario(ElementRules::SCENARIO_ESSENTIALS);
- $success = $drafts->saveElementAsDraft($entry, $user->id, markAsSaved: false);
+ $success = $drafts->saveElementAsDraft($entry, $user->getCraftUserId(), markAsSaved: false);
if (! $success) {
return $this->asModelFailure($entry, mb_ucfirst(t('Couldn’t create {type}.', [
diff --git a/src/Http/Controllers/Entries/MoveEntryToSectionController.php b/src/Http/Controllers/Entries/MoveEntryToSectionController.php
index 3821c0aa007..7c6228593f2 100644
--- a/src/Http/Controllers/Entries/MoveEntryToSectionController.php
+++ b/src/Http/Controllers/Entries/MoveEntryToSectionController.php
@@ -58,7 +58,7 @@ public function showModal(): JsonResponse
->pluck('et.id')
->all();
- $user = $this->request->user();
+ $user = $this->request->craftUser();
// filter all sections to those that have all the entry types we just got
$compatibleSections = $this->sections->getEditableSections()
diff --git a/src/Http/Controllers/Entries/StoreEntryController.php b/src/Http/Controllers/Entries/StoreEntryController.php
index f442a616de2..72a7296902d 100644
--- a/src/Http/Controllers/Entries/StoreEntryController.php
+++ b/src/Http/Controllers/Entries/StoreEntryController.php
@@ -15,6 +15,7 @@
use CraftCms\Cms\Http\RespondsWithFlash;
use CraftCms\Cms\Site\Sites;
use CraftCms\Cms\Support\DateTimeHelper;
+use CraftCms\Cms\User\Contracts\CraftUser;
use Exception;
use Illuminate\Contracts\Cache\LockTimeoutException;
use Illuminate\Http\Request;
@@ -38,7 +39,12 @@ public function __construct(
public function __invoke(): Response
{
- $entry = $this->getEntry();
+ $currentUser = $this->request->craftUser();
+ if (! $currentUser) {
+ abort(401);
+ }
+
+ $entry = $this->getEntry($currentUser);
$entryVariable = $this->request->getSigned('entryVariable') ?? 'entry';
$this->enforeSitePermission($entry->getSite());
@@ -55,7 +61,7 @@ public function __invoke(): Response
}
}
- $this->populateEntry($entry);
+ $this->populateEntry($entry, $currentUser);
$this->enforceEditEntryPermissions($entry, $duplicate);
@@ -101,7 +107,7 @@ public function __invoke(): Response
$provisional = Entry::find()
->provisionalDrafts()
->draftOf($entry->id)
- ->draftCreator($this->request->user()->id)
+ ->draftCreator($currentUser->getCraftUserId())
->siteId($entry->siteId)
->status(null)
->one();
@@ -141,7 +147,7 @@ public function __invoke(): Response
);
}
- private function getEntry(): Entry
+ private function getEntry(CraftUser $currentUser): Entry
{
$entryId = $this->request->input('canonicalId')
?? $this->request->input('sourceId')
@@ -166,7 +172,7 @@ private function getEntry(): Entry
$entry = Entry::find()
->provisionalDrafts()
->draftOf($entryId)
- ->draftCreator($this->request->user()->id)
+ ->draftCreator($currentUser->getCraftUserId())
->siteId($siteId)
->status(null)
->one();
@@ -216,11 +222,11 @@ private function swapEntryWithDuplicate(Entry $entry, bool &$forceDisabled): ?Re
return null;
}
- private function populateEntry(Entry $entry): void
+ private function populateEntry(Entry $entry, CraftUser $currentUser): void
{
// Set the entry attributes, defaulting to the existing values for whatever is missing from the post data
$attributes = [
- 'authorIds' => $this->request->input('authors') ?? $this->request->input('author') ?? $entry->getAuthorId() ?? $this->request->user()->id,
+ 'authorIds' => $this->request->input('authors') ?? $this->request->input('author') ?? $entry->getAuthorId() ?? $currentUser->getCraftUserId(),
];
foreach (['expiryDate', 'postDate', 'slug', 'title', 'typeId'] as $attribute) {
@@ -248,7 +254,7 @@ private function populateEntry(Entry $entry): void
// Authors
if (empty($entry->getAuthorIds()) && ! $entry->id) {
- $entry->setAuthor($this->request->user());
+ $entry->setAuthor($currentUser->asElement());
}
// Parent
diff --git a/src/Http/Controllers/Gql/ApiController.php b/src/Http/Controllers/Gql/ApiController.php
index 648e97b80f3..ec586d01877 100644
--- a/src/Http/Controllers/Gql/ApiController.php
+++ b/src/Http/Controllers/Gql/ApiController.php
@@ -325,7 +325,7 @@ private function shouldShowExceptionDetails(): bool
return true;
}
- $user = Auth::user();
+ $user = Auth::craftUser();
return $user && $user->isAdmin() && $user->getPreference('showExceptionView');
}
diff --git a/src/Http/Controllers/MatrixController.php b/src/Http/Controllers/MatrixController.php
index f042d5e78e7..909005da62c 100644
--- a/src/Http/Controllers/MatrixController.php
+++ b/src/Http/Controllers/MatrixController.php
@@ -140,7 +140,7 @@ public function createEntry(Request $request): Response
$entry->ruleset->useScenario(ElementRules::SCENARIO_ESSENTIALS);
- if (! $this->drafts->saveElementAsDraft($entry, $request->user()->id, markAsSaved: false)) {
+ if (! $this->drafts->saveElementAsDraft($entry, $request->craftUser()?->getCraftUserId(), markAsSaved: false)) {
return $this->asFailure(mb_ucfirst(t('Couldn’t create {type}.', [
'type' => Entry::lowerDisplayName(),
])));
diff --git a/src/Http/Controllers/PluginStore/PluginStoreController.php b/src/Http/Controllers/PluginStore/PluginStoreController.php
index 08f9f646030..eeb1a414f36 100644
--- a/src/Http/Controllers/PluginStore/PluginStoreController.php
+++ b/src/Http/Controllers/PluginStore/PluginStoreController.php
@@ -62,7 +62,7 @@ public function craftData(Request $request): Response
$data = [];
// Current user
- $data['currentUser'] = $request->user()->email;
+ $data['currentUser'] = $request->craftUser()?->asElement()->email;
// Craft license/edition info
$data['licensedEdition'] = Edition::getLicensed()?->value;
diff --git a/src/Http/Controllers/PreviewController.php b/src/Http/Controllers/PreviewController.php
index d7fb8afa129..0558a5acd68 100644
--- a/src/Http/Controllers/PreviewController.php
+++ b/src/Http/Controllers/PreviewController.php
@@ -48,7 +48,7 @@ public function createToken(Request $request, RouteTokens $tokens): JsonResponse
'siteId' => $tokenData->siteId,
'draftId' => $tokenData->draftId ?? null,
'revisionId' => $tokenData->revisionId ?? null,
- 'userId' => $request->user()->id,
+ 'userId' => $request->craftUser()?->getCraftUserId(),
],
], token: $tokenData->previewToken);
diff --git a/src/Http/Controllers/Settings/EmailSettingsController.php b/src/Http/Controllers/Settings/EmailSettingsController.php
index 1ba9b01ecf7..76ff08241dc 100644
--- a/src/Http/Controllers/Settings/EmailSettingsController.php
+++ b/src/Http/Controllers/Settings/EmailSettingsController.php
@@ -15,6 +15,7 @@
use CraftCms\Cms\Support\Url;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Throwable;
@@ -54,7 +55,7 @@ public function index(GeneralConfig $generalConfig): CpScreenResponse
'envSuggestions' => SelectOptions::getEnvSuggestions(),
'templateSuggestions' => SelectOptions::getTemplateSuggestions(),
'sites' => $sites,
- 'defaultToEmail' => auth()->user()->email,
+ 'defaultToEmail' => Auth::craftUser()?->asElement()->email,
]);
}
diff --git a/src/Http/Controllers/Updates/UpdaterController.php b/src/Http/Controllers/Updates/UpdaterController.php
index 63c575da023..fe3a34df006 100644
--- a/src/Http/Controllers/Updates/UpdaterController.php
+++ b/src/Http/Controllers/Updates/UpdaterController.php
@@ -54,7 +54,7 @@ public function __construct(
parent::__construct($request, $generalConfig, $composer, $plugins, $updates);
if ($request->has('install') && $this->request->fullUrlIs(action([self::class, 'index']))) {
- abort_unless($request->user()->can('performUpdates'), 403, 'You do not have permission to perform updates.');
+ abort_unless($request->craftUser()->can('performUpdates'), 403, 'You do not have permission to perform updates.');
}
}
diff --git a/src/Http/Controllers/Updates/UpdatesController.php b/src/Http/Controllers/Updates/UpdatesController.php
index 25bef66a92d..14cb6306065 100644
--- a/src/Http/Controllers/Updates/UpdatesController.php
+++ b/src/Http/Controllers/Updates/UpdatesController.php
@@ -58,7 +58,7 @@ private function updatesResponse(UpdatesData $updates, bool $includeDetails): Js
$allowUpdates = (
$this->generalConfig->allowUpdates &&
$this->generalConfig->allowAdminChanges &&
- Auth::user()->can('performUpdates')
+ Auth::craftUser()->can('performUpdates')
);
$res = [
@@ -132,7 +132,7 @@ private function transformUpdate(bool $allowUpdates, Update $update, string $han
$arr['statusText'] = Html::tag('strong', t('This plugin is no longer maintained.'));
if ($update->replacementName) {
- if (Auth::user()?->isAdmin() && $this->generalConfig->allowAdminChanges) {
+ if (Auth::craftUser()?->isAdmin() && $this->generalConfig->allowAdminChanges) {
$replacementUrl = Url::url("plugin-store/$update->replacementHandle");
} else {
$replacementUrl = $update->replacementUrl;
diff --git a/src/Http/Controllers/Users/ActivateController.php b/src/Http/Controllers/Users/ActivateController.php
index 56ce7c8d78d..8e2ae450432 100644
--- a/src/Http/Controllers/Users/ActivateController.php
+++ b/src/Http/Controllers/Users/ActivateController.php
@@ -68,11 +68,11 @@ public function deactivate(Request $request): Response
abort_if(! $user, 400, 'User not found');
- if ($user->id !== $request->user()->id) {
+ if ($user->id !== $request->craftUser()?->getCraftUserId()) {
$this->authorize('administrateUsers');
// Even if you have administrateUsers permissions, only and admin should be able to deactivate another admin.
- abort_if($user->admin && ! $request->user()->isAdmin(), 403, 'User is not authorized to perform this action.');
+ abort_if($user->admin && ! $request->craftUser()->isAdmin(), 403, 'User is not authorized to perform this action.');
}
// Deactivate the user
diff --git a/src/Http/Controllers/Users/AddressesController.php b/src/Http/Controllers/Users/AddressesController.php
index 1136be9b1be..e816448dd0d 100644
--- a/src/Http/Controllers/Users/AddressesController.php
+++ b/src/Http/Controllers/Users/AddressesController.php
@@ -50,9 +50,12 @@ public function index(?int $userId = null): CpScreenResponse
public function store(Request $request, Elements $elements): Response
{
- $user = $request->user();
+ $user = $request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
- $userId = (int) ($request->input('userId') ?? $user->id);
+ $userId = (int) ($request->input('userId') ?? $user->getCraftUserId());
$addressId = $request->input('addressId');
if ($addressId) {
diff --git a/src/Http/Controllers/Users/EditUserTrait.php b/src/Http/Controllers/Users/EditUserTrait.php
index 5514c14d2ae..08816840f01 100644
--- a/src/Http/Controllers/Users/EditUserTrait.php
+++ b/src/Http/Controllers/Users/EditUserTrait.php
@@ -9,13 +9,16 @@
use CraftCms\Cms\Cp\Html\ElementHtml;
use CraftCms\Cms\Edition;
use CraftCms\Cms\Http\Responses\CpScreenResponse;
+use CraftCms\Cms\Support\Facades\UserGroups;
use CraftCms\Cms\Support\Url;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use CraftCms\Cms\User\Events\EditUserScreensResolving;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
trait EditUserTrait
@@ -42,7 +45,7 @@ trait EditUserTrait
protected function editedUser(?int $userId): User
{
if ($userId === null) {
- return $this->editedUser(Auth::user()->id);
+ return $this->editedUser(Auth::craftUser()?->getCraftUserId());
}
/** @var User|null $user */
@@ -79,7 +82,7 @@ protected function asEditUserScreen(User $user, string $screen, ?CpScreenRespons
$screens[self::SCREEN_ADDRESSES] = ['label' => t('Addresses')];
- $currentUser = Auth::user();
+ $currentUser = currentUserElement();
event($event = new EditUserScreensResolving($currentUser, $user, $screens));
@@ -163,7 +166,7 @@ function (CpScreenResponse $response) use ($user, $pageName) {
protected function existingPasswordVerified(Request $request): bool
{
- if (! $request->user()) {
+ if (! $request->craftUser()) {
return false;
}
@@ -173,24 +176,47 @@ protected function existingPasswordVerified(Request $request): bool
return false;
}
- $currentHashedPassword = $request->user()->password;
+ $currentHashedPassword = $request->craftUser()->asElement()->password;
return Hash::check($currentPassword, $currentHashedPassword);
}
private function showPermissionsScreen(): bool
{
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
+
+ if (! $currentUser) {
+ return false;
+ }
return
Edition::get()->value >= Edition::Team->value &&
(
- (Edition::get() === Edition::Team && $currentUser->admin) ||
+ (Edition::get() === Edition::Team && $currentUser->isAdmin()) ||
(Edition::get()->value >= Edition::Pro->value && $currentUser->can('assignUserPermissions')) ||
- $currentUser->canAssignUserGroups()
+ $this->canAssignUserGroups($currentUser)
);
}
+ private function canAssignUserGroups(CraftUser $user): bool
+ {
+ if (! Edition::isAtLeast(Edition::Pro)) {
+ return false;
+ }
+
+ if ($user->isAdmin()) {
+ return true;
+ }
+
+ foreach (UserGroups::getAllGroups() as $group) {
+ if ($user->can("assignUserGroup:$group->uid")) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private function editUserScreenUrl(User $user, string $screen): string
{
$basePath = $user->getIsCurrent() ? 'myaccount' : "users/$user->id";
diff --git a/src/Http/Controllers/Users/ImpersonationController.php b/src/Http/Controllers/Users/ImpersonationController.php
index 920f88dfec1..576a335d6ec 100644
--- a/src/Http/Controllers/Users/ImpersonationController.php
+++ b/src/Http/Controllers/Users/ImpersonationController.php
@@ -17,6 +17,7 @@
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use Illuminate\Validation\Rule;
+use RuntimeException;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
@@ -48,14 +49,16 @@ public function impersonate(): Response
$this->enforceImpersonatePermission($user);
- $this->impersonation->setImpersonatorId($this->request->user()->id);
+ $this->impersonation->setImpersonatorId($this->request->craftUser()?->getCraftUserId());
try {
- Auth::guard('craft')->login($user);
+ if (! Auth::guard('craft')->loginUsingId($user->id)) {
+ throw new RuntimeException('Unable to retrieve the user being impersonated.');
+ }
} catch (Throwable) {
Flash::error(t('There was a problem impersonating this user.'));
- Log::error($this->request->user()->username.' tried to impersonate userId: '.$userId.' but something went wrong.');
+ Log::error($this->request->craftUser()?->asElement()->username.' tried to impersonate userId: '.$userId.' but something went wrong.');
return back();
}
@@ -102,7 +105,9 @@ public function withToken(): Response
$this->impersonation->setImpersonatorId($prevUserId);
try {
- Auth::guard('craft')->login($user);
+ if (! Auth::guard('craft')->loginUsingId($user->id)) {
+ throw new RuntimeException('Unable to retrieve the user being impersonated.');
+ }
} catch (Throwable) {
Flash::error(t('There was a problem impersonating this user.'));
@@ -127,12 +132,12 @@ private function handleSuccessfulLogin(User $user): Response
return $this->asModelSuccess($user, modelName: 'user', data: $return);
}
- return $this->redirectToPostedUrl($this->request->user(), $returnUrl);
+ return $this->redirectToPostedUrl($this->request->craftUser(), $returnUrl);
}
private function enforceImpersonatePermission(User $user): void
{
- $currentUser = $this->request->user();
+ $currentUser = $this->request->craftUser();
abort_unless(
$this->users->canImpersonate($currentUser, $user),
diff --git a/src/Http/Controllers/Users/PasskeysController.php b/src/Http/Controllers/Users/PasskeysController.php
index 8740ce42bd4..3aa9f953957 100644
--- a/src/Http/Controllers/Users/PasskeysController.php
+++ b/src/Http/Controllers/Users/PasskeysController.php
@@ -8,7 +8,7 @@
use CraftCms\Cms\Auth\Passkeys\Passkeys;
use CraftCms\Cms\Http\RespondsWithFlash;
use CraftCms\Cms\Http\Responses\CpScreenResponse;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\View\HtmlStack;
use CraftCms\Cms\View\LegacyAssets\InternalAssetRegistry;
use CraftCms\Cms\View\LegacyAssets\PasskeySetupAsset;
@@ -27,12 +27,17 @@
use RespondsWithFlash;
public function __construct(
- private Passkeys $passkeys
+ private Passkeys $passkeys,
) {}
public function index(Request $request, HtmlStack $HtmlStack): CpScreenResponse
{
- $user = $request->user();
+ $currentUser = $request->craftUser();
+ if (! $currentUser) {
+ abort(401);
+ }
+
+ $user = $currentUser->asElement();
$response = $this->asEditUserScreen($user, self::SCREEN_PASSKEYS);
@@ -53,9 +58,13 @@ public function creationOptions(Request $request): JsonResponse
{
$this->requireConfirmedPassword();
$serializer = $this->passkeys->webauthnServer()->getSerializer();
+ $user = $request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
return new JsonResponse([
- 'options' => $serializer->serialize($this->passkeys->getPasskeyCreationOptions($request->user()), 'json'),
+ 'options' => $serializer->serialize($this->passkeys->getPasskeyCreationOptions($user), 'json'),
]);
}
@@ -77,8 +86,13 @@ public function verifyCreation(Request $request): Response
return $this->asFailure(t('Passkey creation failed.'));
}
+ $user = $request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
+
return $this->asSuccess(t('Passkey created.'), [
- 'tableHtml' => $this->passkeyTableHtml($request->user()),
+ 'tableHtml' => $this->passkeyTableHtml($user),
]);
}
@@ -88,14 +102,19 @@ public function delete(Request $request): Response
'uid' => ['required', 'string'],
])['uid'];
- $this->passkeys->deletePasskey($request->user(), $uid);
+ $user = $request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
+
+ $this->passkeys->deletePasskey($user, $uid);
return $this->asSuccess(t('Passkey deleted.'), [
- 'tableHtml' => $this->passkeyTableHtml($request->user()),
+ 'tableHtml' => $this->passkeyTableHtml($user),
]);
}
- private function passkeyTableHtml(User $user): string
+ private function passkeyTableHtml(CraftUser $user): string
{
return template('users/_passkeys-table', [
'passkeys' => $this->passkeys->getPasskeys($user)->all(),
diff --git a/src/Http/Controllers/Users/PasswordController.php b/src/Http/Controllers/Users/PasswordController.php
index 9919c29b357..917875c183c 100644
--- a/src/Http/Controllers/Users/PasswordController.php
+++ b/src/Http/Controllers/Users/PasswordController.php
@@ -36,7 +36,12 @@
public function index(Request $request): CpScreenResponse
{
- $user = $request->user();
+ $currentUser = $request->craftUser();
+ if (! $currentUser) {
+ abort(401);
+ }
+
+ $user = $currentUser->asElement();
$response = $this->asEditUserScreen($user, self::SCREEN_PASSWORD);
@@ -52,8 +57,12 @@ public function store(Request $request, Elements $elements): Response
{
$this->requireConfirmedPassword('An elevated session is required to change your password.');
- /** @var User $user */
- $user = $request->user();
+ $currentUser = $request->craftUser();
+ if (! $currentUser) {
+ abort(401);
+ }
+
+ $user = $currentUser->asElement();
abort_if(! $user->getHasPassword(), 400, 'Only users with current passwords can set new ones.');
diff --git a/src/Http/Controllers/Users/PermissionsController.php b/src/Http/Controllers/Users/PermissionsController.php
index 763498f350f..27f3d9bc27d 100644
--- a/src/Http/Controllers/Users/PermissionsController.php
+++ b/src/Http/Controllers/Users/PermissionsController.php
@@ -16,6 +16,7 @@
use CraftCms\Cms\Support\Facades\Users;
use CraftCms\Cms\Support\Flash;
use CraftCms\Cms\Support\Html;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User as UserElement;
use CraftCms\Cms\User\Events\GroupsAndPermissionsAssigned;
use CraftCms\Cms\User\Events\UserGroupsAndPermissionsAssigning;
@@ -35,15 +36,22 @@
public function index(Request $request, ?int $userId = null): CpScreenResponse
{
$user = $this->editedUser($userId);
+ $currentUser = $request->craftUser();
+ if (! $currentUser) {
+ abort(401);
+ }
$response = $this->asEditUserScreen($user, self::SCREEN_PERMISSIONS);
$response->action('users/save-permissions');
$response->contentTemplate('users/_permissions', [
'user' => $user,
'currentGroupIds' => Arr::pluck($user->getGroups(), 'id'),
+ 'currentUserIsAdmin' => $currentUser->isAdmin(),
+ 'showPermissions' => $currentUser->can('assignUserPermissions'),
+ 'showUserGroups' => $this->canAssignUserGroups($currentUser),
]);
- if (! $user->getIsCredentialed() && $user->username && $request->user()->can('moderateUsers')) {
+ if (! $user->getIsCredentialed() && $user->username && $currentUser->can('moderateUsers')) {
$response->additionalButtonsHtml(
Html::button(t('Save and send activation email'), [
'class' => ['btn', 'secondary', 'formsubmit'],
@@ -70,11 +78,15 @@ public function store(Request $request, Elements $elements): Response
abort(403, 'User not authorized to perform this action.');
}
- $currentUser = $request->user();
+ $currentUser = $request->craftUser();
+ if (! $currentUser) {
+ abort(401);
+ }
+
$user = $this->editedUser($request->integer('userId'));
// Is their admin status changing?
- if ($currentUser->admin) {
+ if ($currentUser->isAdmin()) {
$adminParam = $request->boolean('admin', $user->admin);
if ($adminParam !== $user->admin) {
@@ -114,9 +126,9 @@ public function store(Request $request, Elements $elements): Response
return $this->asSuccess(t('Permissions saved.'));
}
- private function saveUserGroups(Request $request, UserElement $user, UserElement $currentUser): void
+ private function saveUserGroups(Request $request, UserElement $user, CraftUser $currentUser): void
{
- if (! $currentUser->canAssignUserGroups()) {
+ if (! $this->canAssignUserGroups($currentUser)) {
return;
}
@@ -161,7 +173,7 @@ private function saveUserGroups(Request $request, UserElement $user, UserElement
$user->setGroups($newGroups);
}
- private function saveUserPermissions(Request $request, UserElement $user, UserElement $currentUser): void
+ private function saveUserPermissions(Request $request, UserElement $user, CraftUser $currentUser): void
{
if (! $currentUser->can('assignUserPermissions')) {
return;
diff --git a/src/Http/Controllers/Users/PhotoController.php b/src/Http/Controllers/Users/PhotoController.php
index f1ebcd8ac60..44492fa2333 100644
--- a/src/Http/Controllers/Users/PhotoController.php
+++ b/src/Http/Controllers/Users/PhotoController.php
@@ -51,7 +51,7 @@ public function upload(Request $request): Response
'userId' => ['required', 'integer'],
]);
- if ($request->integer('userId') !== $request->user()->id) {
+ if ($request->integer('userId') !== $request->craftUser()?->getCraftUserId()) {
$this->authorize('editUsers');
}
diff --git a/src/Http/Controllers/Users/PreferencesController.php b/src/Http/Controllers/Users/PreferencesController.php
index aad190ecf49..4c6ff978223 100644
--- a/src/Http/Controllers/Users/PreferencesController.php
+++ b/src/Http/Controllers/Users/PreferencesController.php
@@ -25,7 +25,12 @@
public function index(Request $request, I18N $i18N, GeneralConfig $generalConfig, ProjectConfig $projectConfig): CpScreenResponse
{
- $user = $request->user();
+ $currentUser = $request->craftUser();
+ if (! $currentUser) {
+ abort(401);
+ }
+
+ $user = $currentUser->asElement();
$response = $this->asEditUserScreen($user, self::SCREEN_PREFERENCES);
@@ -66,7 +71,10 @@ public function index(Request $request, I18N $i18N, GeneralConfig $generalConfig
public function store(Request $request, Users $users): Response
{
- $user = $request->user();
+ $user = $request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
$preferredLocale = $request->input('preferredLocale', $user->getPreference('locale')) ?: null;
@@ -93,7 +101,7 @@ public function store(Request $request, Users $users): Response
'slideoutPosition' => $request->input('slideoutPosition', $user->getPreference('slideoutPosition')),
];
- if ($user->admin) {
+ if ($user->isAdmin()) {
$preferences = array_merge($preferences, [
'showFieldHandles' => (bool) $request->input('showFieldHandles', $user->getPreference('showFieldHandles')),
'showExceptionView' => (bool) $request->input('showExceptionView', $user->getPreference('showExceptionView')),
diff --git a/src/Http/Controllers/Users/RecoveryCodesController.php b/src/Http/Controllers/Users/RecoveryCodesController.php
index 14d8eb4d4e3..96ad0468fa6 100644
--- a/src/Http/Controllers/Users/RecoveryCodesController.php
+++ b/src/Http/Controllers/Users/RecoveryCodesController.php
@@ -53,10 +53,15 @@ public function download(Request $request, Sites $sites, I18N $i18N): Response
$systemNameUnderline = str_repeat('=', mb_strlen($systemName));
$primarySite = $sites->getPrimarySite();
$website = $primarySite->getBaseUrl() ?? $primarySite->getName();
- $user = $request->user();
+ $user = $request->craftUser();
+ if (! $user) {
+ abort(401);
+ }
+
+ $userElement = $user->asElement();
$generalConfig = Cms::config();
- $username = ! $generalConfig->useEmailAsUsername && $user->username ? $user->username : null;
- $account = $username ? sprintf('%s (%s)', $username, $user->email) : $user->email;
+ $username = ! $generalConfig->useEmailAsUsername && $userElement->username ? $userElement->username : null;
+ $account = $username ? sprintf('%s (%s)', $username, $userElement->email) : $userElement->email;
$generated = $i18N->getFormatter()->asDate($dateCreated, Locale::LENGTH_SHORT);
$codeContent = implode('', array_map(
fn (string $code) => $code ? "- $code\n" : "- ~~~~~~~~~~~~~\n",
@@ -79,7 +84,7 @@ public function download(Request $request, Sites $sites, I18N $i18N): Response
$codeContent
EOD;
- $name = sprintf('%s recovery codes - %s.txt', $systemName, $username ?? $user->email);
+ $name = sprintf('%s recovery codes - %s.txt', $systemName, $username ?? $userElement->email);
return response()->make($content, 200, [
'Content-Type' => 'text/plain',
diff --git a/src/Http/Controllers/Users/SaveUserController.php b/src/Http/Controllers/Users/SaveUserController.php
index c1edf0594f0..264a71275de 100644
--- a/src/Http/Controllers/Users/SaveUserController.php
+++ b/src/Http/Controllers/Users/SaveUserController.php
@@ -20,6 +20,7 @@
use CraftCms\Cms\Support\Query;
use CraftCms\Cms\Support\Url;
use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Models\User as UserModel;
use CraftCms\Cms\User\Users;
use CraftCms\Cms\User\Validation\UserRules;
use Illuminate\Http\Request;
@@ -100,7 +101,7 @@ public function __invoke(Request $request): Response
Edition::require(Edition::Team);
// Is someone logged in?
- if ($request->user()) {
+ if ($request->craftUser()) {
// Make sure they have permission to register users
$this->requirePermission('registerUsers');
} else {
@@ -211,7 +212,7 @@ public function __invoke(Request $request): Response
// Is the site set to use email addresses as usernames?
if ($this->generalConfig->useEmailAsUsername) {
$user->username = $user->email;
- } elseif ($isNewUser || $request->user()->admin || $isCurrentUser) {
+ } elseif ($isNewUser || $request->craftUser()?->isAdmin() || $isCurrentUser) {
$user->username = $request->input('username', ($user->username ?: $user->email));
}
@@ -439,7 +440,7 @@ private function maybeLoginUserAfterAccountActivation(User $user): bool
return false;
}
- auth('craft')->login($user);
+ auth('craft')->login(UserModel::findOrFail($user->id));
return true;
}
diff --git a/src/Http/Controllers/Users/SuspendController.php b/src/Http/Controllers/Users/SuspendController.php
index 1c3f11dbd2c..2d0c4011fe7 100644
--- a/src/Http/Controllers/Users/SuspendController.php
+++ b/src/Http/Controllers/Users/SuspendController.php
@@ -34,7 +34,7 @@ public function suspend(Request $request): Response
abort_if(! $user, 400, 'User not found');
- if (! $this->users->canSuspend($request->user(), $user)) {
+ if (! $this->users->canSuspend($request->craftUser(), $user)) {
return $this->asFailure(t('Couldn’t suspend user.'));
}
@@ -60,7 +60,7 @@ public function unsuspend(Request $request): Response
abort_if(! $user, 400, 'User not found');
// Even if you have moderateUsers permissions, only and admin should be able to unsuspend another admin.
- if (! $this->users->canSuspend($request->user(), $user)) {
+ if (! $this->users->canSuspend($request->craftUser(), $user)) {
return $this->asFailure(t('Couldn’t unsuspend user.'));
}
diff --git a/src/Http/Controllers/Users/UnlockController.php b/src/Http/Controllers/Users/UnlockController.php
index 40003c46f64..0a1050b6604 100644
--- a/src/Http/Controllers/Users/UnlockController.php
+++ b/src/Http/Controllers/Users/UnlockController.php
@@ -30,7 +30,7 @@ public function __invoke(Request $request, Users $users, Impersonation $imperson
abort_if(! $user, 400, 'User not found');
if ($user->admin) {
- abort_if(! $request->user()->isAdmin(), 403, 'Only admins can unlock other admins.');
+ abort_if(! $request->craftUser()->isAdmin(), 403, 'Only admins can unlock other admins.');
abort_if($user->id === $impersonation->getImpersonatorId(), 403, 'You can’t unlock yourself via impersonation.');
}
diff --git a/src/Http/Controllers/Users/UsersController.php b/src/Http/Controllers/Users/UsersController.php
index cccde6f3ce9..1bea1ffce6f 100644
--- a/src/Http/Controllers/Users/UsersController.php
+++ b/src/Http/Controllers/Users/UsersController.php
@@ -16,6 +16,7 @@
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Gate;
use Symfony\Component\HttpFoundation\Response;
use function CraftCms\Cms\t;
@@ -38,6 +39,7 @@ public function index(Request $request, ?string $slug = null): View
'buttonLabel' => mb_ucfirst(t('New {type}', [
'type' => User::lowerDisplayName(),
])),
+ 'canRegisterUsers' => Gate::allows('save', new User),
'source' => $slug ?? $request->input('source'),
]);
}
@@ -49,7 +51,7 @@ public function create(Request $request, Drafts $drafts): Response
$this->authorize('save', $user);
$user->ruleset->useScenario(ElementRules::SCENARIO_ESSENTIALS);
- if (! $drafts->saveElementAsDraft($user, $request->user()->id, markAsSaved: false)) {
+ if (! $drafts->saveElementAsDraft($user, $request->craftUser()?->getCraftUserId(), markAsSaved: false)) {
return $this->asModelFailure($user, mb_ucfirst(t('Couldn’t create {type}.', [
'type' => User::lowerDisplayName(),
])), 'user');
diff --git a/src/Http/Middleware/AddLogContext.php b/src/Http/Middleware/AddLogContext.php
index 94ca068fdeb..3ff432b9372 100644
--- a/src/Http/Middleware/AddLogContext.php
+++ b/src/Http/Middleware/AddLogContext.php
@@ -21,7 +21,7 @@ public function handle(Request $request, Closure $next): mixed
{
Log::shareContext(array_filter([
'environment' => app()->environment(),
- 'userId' => $request->user()?->id,
+ 'userId' => $request->craftUser()?->getCraftUserId(),
'sessionId' => $request->hasSession() ? $request->session()->getId() : null,
'ips' => $this->generalConfig->storeUserIps ? $request->ips() : null,
]));
diff --git a/src/Http/Middleware/EnforceLicenses.php b/src/Http/Middleware/EnforceLicenses.php
index 0a6e043e15d..150b411c13b 100644
--- a/src/Http/Middleware/EnforceLicenses.php
+++ b/src/Http/Middleware/EnforceLicenses.php
@@ -22,7 +22,7 @@ public function __construct(
public function handle(Request $request, Closure $next): mixed
{
- if (! $request->user()) {
+ if (! $request->craftUser()) {
return $next($request);
}
diff --git a/src/Http/Middleware/HandleInertiaRequests.php b/src/Http/Middleware/HandleInertiaRequests.php
index 756c529eeba..90be46e2635 100644
--- a/src/Http/Middleware/HandleInertiaRequests.php
+++ b/src/Http/Middleware/HandleInertiaRequests.php
@@ -31,6 +31,7 @@
use function CraftCms\Cms\action_url;
use function CraftCms\Cms\cp_url;
+use function CraftCms\Cms\currentUserElement;
class HandleInertiaRequests extends Middleware
{
@@ -115,7 +116,7 @@ public function share(Request $request): array
$generalConfig = app(GeneralConfig::class);
if (! $updates->isCraftUpdatePending()) {
- $currentUser = $request->user();
+ $currentUser = currentUserElement();
}
$systemIcon = ($generalConfig->cpIconUrl && Edition::isAtLeast(Edition::Pro))
diff --git a/src/Http/Middleware/RequireAdmin.php b/src/Http/Middleware/RequireAdmin.php
index 9c5f6832ef1..7b8ee2a8d26 100644
--- a/src/Http/Middleware/RequireAdmin.php
+++ b/src/Http/Middleware/RequireAdmin.php
@@ -13,7 +13,7 @@
{
public function handle(Request $request, Closure $next): mixed
{
- if (! $user = $request->user()) {
+ if (! $user = $request->craftUser()) {
throw new AuthenticationException('Unauthenticated.');
}
diff --git a/src/Http/Mixins/RequestMixin.php b/src/Http/Mixins/RequestMixin.php
index 18f1d61de64..fe5a5644bde 100644
--- a/src/Http/Mixins/RequestMixin.php
+++ b/src/Http/Mixins/RequestMixin.php
@@ -9,6 +9,8 @@
use CraftCms\Cms\Http\Middleware\HandleTokenRequest;
use CraftCms\Cms\Http\Routing\ActionRoute;
use CraftCms\Cms\Http\Routing\ActionRouteResolver;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Context;
@@ -16,6 +18,28 @@
class RequestMixin
{
+ public function craftUser(): Closure
+ {
+ return function (): ?CraftUser {
+ /**
+ * @var Request $request
+ *
+ * @phpstan-ignore-next-line
+ */
+ $request = $this;
+ $user = $request->user();
+
+ if ($user === null || $user instanceof CraftUser) {
+ return $user;
+ }
+
+ throw new AuthenticationException(sprintf(
+ 'The request user must implement %s to be used by Craft.',
+ CraftUser::class,
+ ));
+ };
+ }
+
public function isMobileBrowser(): Closure
{
return function (bool $detectTablets = false): bool {
diff --git a/src/Http/Requests/ElementRequest.php b/src/Http/Requests/ElementRequest.php
index 9418f0a00f6..e0aa4bcaf60 100644
--- a/src/Http/Requests/ElementRequest.php
+++ b/src/Http/Requests/ElementRequest.php
@@ -13,7 +13,7 @@
use CraftCms\Cms\Support\Arr;
use CraftCms\Cms\Support\Facades\Elements;
use CraftCms\Cms\Support\Facades\Sites;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
use Illuminate\Container\Attributes\Scoped;
use Illuminate\Foundation\Http\FormRequest;
use Symfony\Component\HttpFoundation\Response;
@@ -117,7 +117,7 @@ public function element(array $overrides = [], bool $checkForProvisionalDraft =
return null;
}
- abort_unless($this->user()->can('view', $element), 403, 'User not authorized to view this element.');
+ abort_unless($this->craftUser()->can('view', $element), 403, 'User not authorized to view this element.');
if (
! $this->strictSite &&
@@ -211,7 +211,7 @@ public function site(): array
abort_if(is_null($site), 400, "Invalid site ID: $siteId");
- if (Sites::isMultiSite() && ! $this->user()->can("editSite:$site->uid")) {
+ if (Sites::isMultiSite() && ! $this->craftUser()->can("editSite:$site->uid")) {
abort(403, 'User not authorized to edit content for this site.');
}
} else {
@@ -259,7 +259,7 @@ private function elementByDraftOrRevision(mixed $draftId, mixed $revisionId): El
// check for the canonical element as a fallback
$element = $this->elementById() ?? $this->elementByUid();
- if ($element && $this->user()->can('view', $element)) {
+ if ($element && $this->craftUser()->can('view', $element)) {
if (! $this->wantsJson()) {
return redirect($element->getCpEditUrl());
}
@@ -290,14 +290,14 @@ private function elementById(): ?ElementInterface
$element = $this->elementQuery()
->provisionalDrafts()
->draftOf($elementId)
- ->draftCreator($this->user())
+ ->draftCreator($this->craftUser()?->getCraftUserId())
->siteId($siteId)
->preferSites($preferSites)
->unique()
->status(null)
->one();
- if ($element && $this->canSave($element, $this->user())) {
+ if ($element && $this->canSave($element, $this->craftUser())) {
return $element;
}
}
@@ -364,7 +364,7 @@ private function elementByUid(): ?ElementInterface
->one();
}
- private function canSave(ElementInterface $element, User $user): bool
+ private function canSave(ElementInterface $element, CraftUser $user): bool
{
if ($element->getIsRevision()) {
return false;
diff --git a/src/Http/Responses/ElementResponse.php b/src/Http/Responses/ElementResponse.php
index a9d3359438c..d16a21fcc3f 100644
--- a/src/Http/Responses/ElementResponse.php
+++ b/src/Http/Responses/ElementResponse.php
@@ -39,7 +39,7 @@ public function success(ElementInterface $element, string $message, array $data
]);
if ($supportsAddAnother && request()->boolean('addAnother')) {
- $user = request()->user();
+ $user = request()->craftUser();
$newElement = $element->createAnother();
if (! $newElement || ! Gate::check('save', $newElement)) {
@@ -52,7 +52,7 @@ public function success(ElementInterface $element, string $message, array $data
$newElement->ruleset->useScenario(ElementRules::SCENARIO_ESSENTIALS);
- if (! Drafts::saveElementAsDraft($newElement, $user->id, null, null, false)) {
+ if (! Drafts::saveElementAsDraft($newElement, $user->getCraftUserId(), null, null, false)) {
abort(500, sprintf('Unable to create a new element: %s', implode(', ', $element->errors()->all())));
}
diff --git a/src/Providers/AppServiceProvider.php b/src/Providers/AppServiceProvider.php
index 1387bdc0399..1b5663591af 100644
--- a/src/Providers/AppServiceProvider.php
+++ b/src/Providers/AppServiceProvider.php
@@ -20,8 +20,10 @@
use CraftCms\Cms\Update\Data\Update as UpdateData;
use CraftCms\Cms\Update\Data\UpdateRelease;
use CraftCms\Cms\Update\Data\Updates as UpdatesData;
+use CraftCms\Cms\User\Contracts\CraftUser;
use GuzzleHttp\Utils;
use Illuminate\Auth\AuthenticationException;
+use Illuminate\Auth\SessionGuard;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Foundation\Application;
@@ -156,6 +158,19 @@ private function registerMacros(): void
Request::mixin(new RequestMixin);
SessionStore::mixin(new SessionMixin);
+ SessionGuard::macro('craftUser', function (): ?CraftUser {
+ $user = $this->user();
+
+ if ($user === null || $user instanceof CraftUser) {
+ return $user;
+ }
+
+ throw new AuthenticationException(sprintf(
+ 'The authenticated user must implement %s to be used by Craft.',
+ CraftUser::class,
+ ));
+ });
+
Response::macro('setNoCacheHeaders', function (bool $replace = true) {
$this->header('Expires', '0', $replace);
$this->header('Pragma', 'no-cache', $replace);
diff --git a/src/Section/Data/Section.php b/src/Section/Data/Section.php
index 34b97f70ab1..6ca4878f118 100644
--- a/src/Section/Data/Section.php
+++ b/src/Section/Data/Section.php
@@ -210,7 +210,7 @@ public function getHasMultiSiteEntries(): bool
public function getCpEditUrl(): ?string
{
- if (! $this->id || ! Auth::user()?->isAdmin()) {
+ if (! $this->id || ! Auth::craftUser()?->isAdmin()) {
return null;
}
diff --git a/src/Section/Sections.php b/src/Section/Sections.php
index 0e086be6d33..6a130470e2b 100644
--- a/src/Section/Sections.php
+++ b/src/Section/Sections.php
@@ -257,7 +257,7 @@ public function getEditableSections(): Collection
return $this->getAllSections();
}
- $user = Auth::user();
+ $user = Auth::craftUser();
if (! $user) {
return collect();
diff --git a/src/Site/Sites.php b/src/Site/Sites.php
index 637ae52ba22..cb439731405 100644
--- a/src/Site/Sites.php
+++ b/src/Site/Sites.php
@@ -279,7 +279,7 @@ public function getEditableSiteIds(): Collection
return $this->editableSiteIds;
}
- $user = Auth::user();
+ $user = Auth::craftUser();
return $this->editableSiteIds = $this->getAllSites(true)->filter(fn (Site $site) => $user?->can("editSite:$site->uid"))->pluck('id')->values();
}
diff --git a/src/Support/DateTimeHelper.php b/src/Support/DateTimeHelper.php
index e1006083781..489be2ddf67 100644
--- a/src/Support/DateTimeHelper.php
+++ b/src/Support/DateTimeHelper.php
@@ -857,7 +857,7 @@ public static function relativeTimeToSeconds(int $number, string $unit): int
*/
public static function firstWeekDay(): int
{
- $user = Auth::user();
+ $user = Auth::craftUser();
return (int) ($user?->getPreference('weekStartDay') ?? Cms::config()->defaultWeekStartDay);
}
diff --git a/src/Support/Facades/Users.php b/src/Support/Facades/Users.php
index 2eaca969077..6943fc2f19d 100644
--- a/src/Support/Facades/Users.php
+++ b/src/Support/Facades/Users.php
@@ -13,7 +13,7 @@
* @method static \CraftCms\Cms\User\Elements\User|null getUserByUsernameOrEmail(string $usernameOrEmail)
* @method static \CraftCms\Cms\User\Elements\User|null getUserByUid(string $uid)
* @method static array getUserPreferences(int $userId)
- * @method static void saveUserPreferences(\CraftCms\Cms\User\Elements\User $user, array $preferences)
+ * @method static void saveUserPreferences(\CraftCms\Cms\User\Contracts\CraftUser $user, array $preferences)
* @method static mixed getUserPreference(int $userId, string $key, mixed $default = null)
* @method static bool sendPasswordResetEmail(\CraftCms\Cms\User\Elements\User $user)
* @method static bool sendActivationEmail(\CraftCms\Cms\User\Elements\User $user)
@@ -44,8 +44,8 @@
* @method static bool assignUserToDefaultGroup(\CraftCms\Cms\User\Elements\User $user)
* @method static void handleChangedUserFieldLayout(\CraftCms\Cms\ProjectConfig\Events\ConfigEvent $event)
* @method static bool saveLayout(\CraftCms\Cms\FieldLayout\FieldLayout $layout, bool $runValidation = true)
- * @method static bool canImpersonate(\CraftCms\Cms\User\Elements\User $impersonator, \CraftCms\Cms\User\Elements\User $impersonatee)
- * @method static bool canSuspend(\CraftCms\Cms\User\Elements\User $suspender, \CraftCms\Cms\User\Elements\User $suspendee)
+ * @method static bool canImpersonate(\CraftCms\Cms\User\Contracts\CraftUser $impersonator, \CraftCms\Cms\User\Elements\User $impersonatee)
+ * @method static bool canSuspend(\CraftCms\Cms\User\Contracts\CraftUser $suspender, \CraftCms\Cms\User\Elements\User $suspendee)
* @method static int|null getMaxUsers(\CraftCms\Cms\Edition $edition)
* @method static bool canCreateUsers()
*
diff --git a/src/Translation/I18N.php b/src/Translation/I18N.php
index a1254431446..f19dbf01d21 100644
--- a/src/Translation/I18N.php
+++ b/src/Translation/I18N.php
@@ -7,7 +7,6 @@
use CraftCms\Cms\Cms;
use CraftCms\Cms\Site\Data\Site;
use CraftCms\Cms\Support\Facades\Sites;
-use CraftCms\Cms\Support\Facades\Users;
use CraftCms\Cms\Support\Json;
use Illuminate\Container\Attributes\Singleton;
use Illuminate\Support\Collection;
@@ -93,14 +92,14 @@ public function getFormattingLocale(): Locale
return $this->getLocale();
}
- if (Cms::isInstalled() && $user = Auth::user()) {
+ if (Cms::isInstalled() && $user = Auth::craftUser()) {
// If they have a preferred locale, use it
- if (($locale = Users::getUserPreference($user->id, 'locale')) !== null) {
+ if (($locale = $user->getPreference('locale')) !== null) {
return $this->getLocaleById($locale);
}
if (
- ($language = Users::getUserPreference($user->id, 'language')) !== null &&
+ ($language = $user->getPreference('language')) !== null &&
$this->validateAppLocaleId($language)
) {
return $this->getLocaleById($language);
diff --git a/src/Twig/Extensions/CoreTwigExtension.php b/src/Twig/Extensions/CoreTwigExtension.php
index c9baf1b705a..5a066a1f6e9 100644
--- a/src/Twig/Extensions/CoreTwigExtension.php
+++ b/src/Twig/Extensions/CoreTwigExtension.php
@@ -292,12 +292,12 @@ public function getFunctions(): array
new TwigFunction('entries', fn (array $config = []) => new EntryQuery($config)),
new TwigFunction('users', fn (array $config = []) => new UserQuery($config)),
- new TwigFunction('canCreateDrafts', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::user())?->can('createDrafts', $element)),
- new TwigFunction('canDelete', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::user())?->can('delete', $element)),
- new TwigFunction('canDeleteForSite', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::user())?->can('deleteForSite', $element)),
- new TwigFunction('canDuplicate', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::user())?->can('duplicate', $element)),
- new TwigFunction('canSave', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::user())?->can('save', $element)),
- new TwigFunction('canView', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::user())?->can('view', $element)),
+ new TwigFunction('canCreateDrafts', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::craftUser())?->can('createDrafts', $element)),
+ new TwigFunction('canDelete', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::craftUser())?->can('delete', $element)),
+ new TwigFunction('canDeleteForSite', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::craftUser())?->can('deleteForSite', $element)),
+ new TwigFunction('canDuplicate', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::craftUser())?->can('duplicate', $element)),
+ new TwigFunction('canSave', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::craftUser())?->can('save', $element)),
+ new TwigFunction('canView', fn (ElementInterface $element, ?User $user = null) => ($user ?? Auth::craftUser())?->can('view', $element)),
new TwigFunction('head', $this->pageLifecycle->head(...)),
new TwigFunction('beginBody', $this->pageLifecycle->beginBody(...)),
diff --git a/src/User/Actions/GetImpersonationUrlAction.php b/src/User/Actions/GetImpersonationUrlAction.php
index 0cd5dff5c73..c1a62402754 100644
--- a/src/User/Actions/GetImpersonationUrlAction.php
+++ b/src/User/Actions/GetImpersonationUrlAction.php
@@ -22,7 +22,7 @@ public function __invoke(User $user): string|false
$token = $this->tokens->createToken([
action_url('/users/impersonate-with-token'), [
'userId' => $user->id,
- 'prevUserId' => Auth::user()->id ?? $user->id,
+ 'prevUserId' => Auth::craftUser()?->getCraftUserId() ?? $user->id,
],
], 1, now()->addHour());
diff --git a/src/User/Actions/SuspendUsers.php b/src/User/Actions/SuspendUsers.php
index 3317c106e78..1b959c1aff0 100644
--- a/src/User/Actions/SuspendUsers.php
+++ b/src/User/Actions/SuspendUsers.php
@@ -9,6 +9,7 @@
use CraftCms\Cms\Element\Queries\UserQuery;
use CraftCms\Cms\Support\Facades\HtmlStack;
use CraftCms\Cms\Support\Facades\Users;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use Illuminate\Support\Facades\Auth;
use Override;
@@ -49,7 +50,7 @@ public function getTriggerHtml(): ?string
})();
JS, [
static::class,
- Auth::user()->id,
+ Auth::craftUser()?->getCraftUserId(),
]);
return null;
@@ -63,7 +64,11 @@ public function performAction(ElementQueryInterface $query): bool
/** @var User[] $users */
$users = $query->all();
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
+
+ if (! $currentUser instanceof CraftUser) {
+ return false;
+ }
$successCount = count(array_filter($users, function (User $user) use ($currentUser) {
try {
diff --git a/src/User/Actions/UnsuspendUsers.php b/src/User/Actions/UnsuspendUsers.php
index 117dbda34ac..d7587db6bdc 100644
--- a/src/User/Actions/UnsuspendUsers.php
+++ b/src/User/Actions/UnsuspendUsers.php
@@ -8,6 +8,7 @@
use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface;
use CraftCms\Cms\Support\Facades\HtmlStack;
use CraftCms\Cms\Support\Facades\Users;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use Illuminate\Support\Facades\Auth;
use Throwable;
@@ -58,7 +59,11 @@ public function performAction(ElementQueryInterface $query): bool
$query->status(User::STATUS_SUSPENDED);
/** @var User[] $users */
$users = $query->all();
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
+
+ if (! $currentUser instanceof CraftUser) {
+ return false;
+ }
$successCount = count(array_filter($users, function (User $user) use ($currentUser) {
if (! Users::canSuspend($currentUser, $user)) {
diff --git a/src/User/Concerns/CraftUserTrait.php b/src/User/Concerns/CraftUserTrait.php
new file mode 100644
index 00000000000..fd8c25195d5
--- /dev/null
+++ b/src/User/Concerns/CraftUserTrait.php
@@ -0,0 +1,94 @@
+id ?? null;
+ }
+
+ public function isAdmin(): bool
+ {
+ return (bool) $this->admin;
+ }
+
+ protected function defaultName(): string
+ {
+ return $this->fullName ?? (string) $this->username;
+ }
+
+ protected function defaultFriendlyName(): ?string
+ {
+ return $this->firstName ?? $this->username;
+ }
+
+ public function sendPasswordResetNotification($token): void
+ {
+ $this->notify(new ResetPasswordNotification($token));
+ }
+
+ public function sendActivationNotification(string $token): void
+ {
+ $this->notify(new ActivationNotification($token));
+ }
+
+ public function hasVerifiedEmail(): bool
+ {
+ return is_null($this->unverifiedEmail);
+ }
+
+ public function markEmailAsVerified(): bool
+ {
+ try {
+ Users::verifyEmailForUser($this->asElement());
+
+ return true;
+ } catch (Throwable) {
+ return false;
+ }
+ }
+
+ public function markEmailAsUnverified(): bool
+ {
+ try {
+ Users::unverifyEmailForUser($this->asElement());
+
+ return true;
+ } catch (Throwable) {
+ return false;
+ }
+ }
+
+ public function sendEmailVerificationNotification(): void
+ {
+ $this->notify(new VerifyEmailNotification(Users::setVerificationCodeOnUser($this->asElement())));
+ }
+
+ public function getEmailForVerification(): string
+ {
+ return $this->unverifiedEmail ?? $this->email;
+ }
+
+ public function getPreference(string $key, mixed $default = null): mixed
+ {
+ return $this->id ? Users::getUserPreference($this->id, $key, $default) : $default;
+ }
+}
diff --git a/src/User/Contracts/CraftUser.php b/src/User/Contracts/CraftUser.php
new file mode 100644
index 00000000000..89a78eb7cff
--- /dev/null
+++ b/src/User/Contracts/CraftUser.php
@@ -0,0 +1,22 @@
+id || ! Auth::user()?->isAdmin()) {
+ if (! $this->id || ! Auth::craftUser()?->isAdmin()) {
return null;
}
@@ -80,7 +80,7 @@ public function getActionMenuItems(): array
if (
$this->id &&
- Auth::user()?->isAdmin() &&
+ Auth::craftUser()?->isAdmin() &&
Cms::config()->allowAdminChanges
) {
$editId = sprintf('action-edit-%s', mt_rand());
diff --git a/src/User/Elements/User.php b/src/User/Elements/User.php
index 4cfb775d972..5441c874183 100644
--- a/src/User/Elements/User.php
+++ b/src/User/Elements/User.php
@@ -50,15 +50,14 @@
use CraftCms\Cms\Twig\Attributes\AllowedInSandbox;
use CraftCms\Cms\User\Actions\SuspendUsers;
use CraftCms\Cms\User\Actions\UnsuspendUsers;
+use CraftCms\Cms\User\Concerns\CraftUserTrait;
use CraftCms\Cms\User\Concerns\LegacyConstants;
use CraftCms\Cms\User\Conditions\UserCondition;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Data\UserGroup;
use CraftCms\Cms\User\Events\UserFriendlyNameResolving;
use CraftCms\Cms\User\Events\UserNameResolving;
use CraftCms\Cms\User\Models\User as UserModel;
-use CraftCms\Cms\User\Notifications\ActivationNotification;
-use CraftCms\Cms\User\Notifications\ResetPasswordNotification;
-use CraftCms\Cms\User\Notifications\VerifyEmailNotification;
use CraftCms\Cms\User\Validation\UserRules;
use CraftCms\RulesetValidation\Attributes\Ruleset;
use DateInterval;
@@ -80,7 +79,6 @@
use Illuminate\Support\Traits\Macroable;
use Override;
use Stringable;
-use Throwable;
use function CraftCms\Cms\t;
@@ -101,11 +99,13 @@
* @property-read string|null $preferredLocale the user’s preferred formatting locale
*/
#[Ruleset(UserRules::class)]
-class User extends Element implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, MustVerifyEmailContract
+class User extends Element implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, CraftUser, MustVerifyEmailContract
{
use Authenticatable;
use Authorizable;
- use CanResetPassword;
+ use CanResetPassword, CraftUserTrait {
+ CraftUserTrait::sendPasswordResetNotification insteadof CanResetPassword;
+ }
use ConfirmsPasswords;
use HasNames;
use LegacyConstants;
@@ -371,6 +371,11 @@ public function getAuthIdentifierName(): string
return 'id';
}
+ public function asElement(): self
+ {
+ return $this;
+ }
+
public function getKey(): ?int
{
return $this->id;
@@ -750,53 +755,6 @@ public static function eagerLoadingMap(array $sourceElements, string $handle): a
return parent::eagerLoadingMap($sourceElements, $handle);
}
- public function sendPasswordResetNotification($token): void
- {
- $this->notify(new ResetPasswordNotification($token));
- }
-
- public function sendActivationNotification(string $token): void
- {
- $this->notify(new ActivationNotification($token));
- }
-
- public function hasVerifiedEmail(): bool
- {
- return is_null($this->unverifiedEmail);
- }
-
- public function markEmailAsVerified(): bool
- {
- try {
- Users::verifyEmailForUser($this);
-
- return true;
- } catch (Throwable) {
- return false;
- }
- }
-
- public function markEmailAsUnverified(): bool
- {
- try {
- Users::unverifyEmailForUser($this);
-
- return true;
- } catch (Throwable) {
- return false;
- }
- }
-
- public function sendEmailVerificationNotification(): void
- {
- $this->notify(new VerifyEmailNotification(Users::setVerificationCodeOnUser($this)));
- }
-
- public function getEmailForVerification(): string
- {
- return $this->unverifiedEmail ?? $this->email;
- }
-
/**
* Use the full name or username as the string representation.
*/
@@ -1136,7 +1094,7 @@ private function _defineName(): string
{
event($event = new UserNameResolving($this));
- return $event->name ?? $this->fullName ?? (string) $this->username;
+ return $event->name ?? $this->defaultName();
}
/**
@@ -1164,7 +1122,7 @@ private function _defineFriendlyName(): ?string
{
event($event = new UserFriendlyNameResolving($this));
- return $event->name ?? $this->firstName ?? $this->username;
+ return $event->name ?? $this->defaultFriendlyName();
}
/**
@@ -1276,42 +1234,7 @@ public function getIsCurrent(): bool
return false;
}
- return Auth::user()?->id === $this->id;
- }
-
- public function isAdmin(): bool
- {
- return $this->admin;
- }
-
- /**
- * Returns whether the user can register additional users.
- */
- public function canRegisterUsers(): bool
- {
- return $this->can('registerUsers') && Users::canCreateUsers();
- }
-
- /**
- * Returns whether the user is authorized to assign any user groups to users.
- */
- public function canAssignUserGroups(): bool
- {
- if (! Edition::isAtLeast(Edition::Pro)) {
- return false;
- }
-
- if ($this->admin) {
- return true;
- }
-
- foreach (UserGroups::getAllGroups() as $group) {
- if ($this->can("assignUserGroup:$group->uid")) {
- return true;
- }
- }
-
- return false;
+ return Auth::craftUser()?->getCraftUserId() === $this->id;
}
/**
@@ -1387,7 +1310,12 @@ protected function safeActionMenuItems(): array
return parent::safeActionMenuItems();
}
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
+
+ if (! $currentUser instanceof CraftUser) {
+ return parent::safeActionMenuItems();
+ }
+
$canAdministrateUsers = $currentUser->can('administrateUsers');
$canModerateUsers = $currentUser->can('moderateUsers');
@@ -1459,7 +1387,7 @@ protected function safeActionMenuItems(): array
if ($this->locked) {
if (
! $isCurrentUser &&
- ($currentUser->admin || ! $this->admin) &&
+ ($currentUser->isAdmin() || ! $this->admin) &&
$canModerateUsers &&
(
($impersonatorId = app(Impersonation::class)->getImpersonatorId()) === null ||
@@ -1580,8 +1508,12 @@ protected function destructiveActionMenuItems(): array
return parent::destructiveActionMenuItems();
}
- /** @var User $currentUser */
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
+
+ if (! $currentUser instanceof CraftUser) {
+ return parent::destructiveActionMenuItems();
+ }
+
$canAdministrateUsers = $currentUser->can('administrateUsers');
$isCurrentUser = $this->getIsCurrent();
@@ -1603,7 +1535,7 @@ protected function destructiveActionMenuItems(): array
}
// Destructive actions that should only be performed on non-admins, unless the current user is also an admin
- if (! $this->admin || $currentUser->admin) {
+ if (! $this->admin || $currentUser->isAdmin()) {
if (($isCurrentUser || $canAdministrateUsers) && ($this->active || $this->pending)) {
$items[] = [
'icon' => 'disabled',
@@ -1671,20 +1603,6 @@ public function getPreferences(): array
return $this->id ? Users::getUserPreferences($this->id) : [];
}
- /**
- * Returns one of the user’s preferences by its key.
- *
- * @param string $key The preference’s key
- * @param mixed $default The default value, if the preference hasn’t been set
- * @return mixed The user’s preference
- */
- public function getPreference(string $key, mixed $default = null): mixed
- {
- $preferences = $this->getPreferences();
-
- return $preferences[$key] ?? $default;
- }
-
/**
* Returns the user’s preferred language, if they have one.
*
@@ -1838,12 +1756,12 @@ protected function attributeHtml(string $attribute): string|Stringable
#[Override]
protected function htmlAttributes(string $context): array
{
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
return [
'data' => [
'suspended' => $this->suspended,
- 'can-suspend' => $currentUser && Users::canSuspend($currentUser, $this),
+ 'can-suspend' => $currentUser instanceof CraftUser && Users::canSuspend($currentUser, $this),
],
];
}
diff --git a/src/User/Models/User.php b/src/User/Models/User.php
index 4389aab5e85..63081c79b6e 100644
--- a/src/User/Models/User.php
+++ b/src/User/Models/User.php
@@ -5,21 +5,24 @@
namespace CraftCms\Cms\User\Models;
use CraftCms\Cms\Asset\Models\Asset;
+use CraftCms\Cms\Auth\Concerns\ConfirmsPasswords;
use CraftCms\Cms\Database\Table;
-use CraftCms\Cms\Edition;
use CraftCms\Cms\Element\Models\Element;
use CraftCms\Cms\Shared\BaseModel;
use CraftCms\Cms\Site\Models\Site;
use CraftCms\Cms\Support\Arr;
-use CraftCms\Cms\Support\Facades\UserGroups;
-use Illuminate\Auth\MustVerifyEmail;
+use CraftCms\Cms\User\Concerns\CraftUserTrait;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use Illuminate\Auth\Authenticatable;
+use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Database\Query\Builder;
-use Illuminate\Support\Collection;
+use Illuminate\Foundation\Auth\Access\Authorizable;
+use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\DB;
use Override;
@@ -27,15 +30,24 @@
'password',
'rememberToken',
])]
-class User extends BaseModel
+class User extends BaseModel implements CraftUser
{
+ use Authenticatable;
+ use Authorizable;
+ use CanResetPassword, CraftUserTrait {
+ CraftUserTrait::sendPasswordResetNotification insteadof CanResetPassword;
+ }
+ use ConfirmsPasswords;
use HasFactory;
- use MustVerifyEmail;
+ use Notifiable;
#[Override]
public $incrementing = false;
- private ?Collection $userGroupData = null;
+ #[Override]
+ protected $with = [
+ 'element',
+ ];
#[Override]
protected $casts = [
@@ -54,9 +66,45 @@ class User extends BaseModel
'lastPasswordChangeDate' => 'datetime',
];
- public function isAdmin(): bool
+ protected function getNameAttribute(): string
+ {
+ return $this->defaultName();
+ }
+
+ protected function getFriendlyNameAttribute(): ?string
{
- return (bool) $this->admin;
+ return $this->defaultFriendlyName();
+ }
+
+ protected function getUidAttribute(): ?string
+ {
+ if (! $this->id) {
+ return null;
+ }
+
+ return $this->relationLoaded('element')
+ ? $this->element?->uid
+ : $this->element()->value('uid');
+ }
+
+ protected function setUidAttribute(?string $value): void {}
+
+ public function haveIndexAttributesChanged(): bool
+ {
+ return $this->isDirty([
+ 'username',
+ 'fullName',
+ 'firstName',
+ 'lastName',
+ 'email',
+ 'admin',
+ 'active',
+ 'pending',
+ 'locked',
+ 'suspended',
+ 'lastLoginDate',
+ 'photoId',
+ ]);
}
#[Override]
@@ -73,16 +121,27 @@ protected function newBaseQueryBuilder(): Builder
public function asElement(): \CraftCms\Cms\User\Elements\User
{
$element = new \CraftCms\Cms\User\Elements\User(Arr::except($this->toArray(), [
+ 'element',
'invalidLoginWindowStart',
]));
$element->password = $this->password;
- unset($this->uid);
+ if ($this->id && ! $element->uid) {
+ $element->uid = $this->relationLoaded('element')
+ ? $this->element?->uid
+ : $this->element()->value('uid');
+ }
return $element;
}
+ #[Override]
+ public function getRememberTokenName(): string
+ {
+ return 'rememberToken';
+ }
+
/**
* @return BelongsTo
*/
@@ -121,42 +180,4 @@ public function permissions(): BelongsToMany
->withTimestamps('dateCreated', 'dateUpdated')
->withPivot('uid');
}
-
- /**
- * @return Collection<\CraftCms\Cms\User\Data\UserGroup>
- */
- public function getGroups(): Collection
- {
- if (isset($this->userGroupData)) {
- return $this->userGroupData;
- }
-
- if (Edition::get() < Edition::Pro || ! isset($this->id)) {
- return collect();
- }
-
- return $this->userGroupData = UserGroups::getGroupsByUserId($this->id);
- }
-
- /**
- * Returns whether any properties that affect the user's status have changed.
- */
- public function haveIndexAttributesChanged(): bool
- {
- if (! $this->exists) {
- return false;
- }
-
- return ! empty(Arr::only($this->getDirty(), [
- 'active',
- 'email',
- 'firstName',
- 'fullName',
- 'lastLoginDate',
- 'lastName',
- 'pending',
- 'suspended',
- 'username',
- ]));
- }
}
diff --git a/src/User/Notifications/ActivationNotification.php b/src/User/Notifications/ActivationNotification.php
index b30f1c4897e..8f1bf28684a 100644
--- a/src/User/Notifications/ActivationNotification.php
+++ b/src/User/Notifications/ActivationNotification.php
@@ -9,7 +9,7 @@
use CraftCms\Cms\Support\Template;
use CraftCms\Cms\SystemMessage\Mailables\SystemMessageMailable;
use CraftCms\Cms\SystemMessage\SystemMessages;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Channels\MailChannel;
@@ -32,8 +32,10 @@ public function via(mixed $notifiable): array
return [MailChannel::class];
}
- public function toMail(User $user): SystemMessageMailable
+ public function toMail(CraftUser $user): SystemMessageMailable
{
+ $user = $user->asElement();
+
$url = Users::getActivationUrl($user, $this->token);
return app(SystemMessages::class)->mailable(
diff --git a/src/User/Notifications/ResetPasswordNotification.php b/src/User/Notifications/ResetPasswordNotification.php
index e9c516fabd8..38ca7095420 100644
--- a/src/User/Notifications/ResetPasswordNotification.php
+++ b/src/User/Notifications/ResetPasswordNotification.php
@@ -9,7 +9,7 @@
use CraftCms\Cms\Support\Template;
use CraftCms\Cms\SystemMessage\Mailables\SystemMessageMailable;
use CraftCms\Cms\SystemMessage\SystemMessages;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Channels\MailChannel;
@@ -30,8 +30,10 @@ public function via(mixed $notifiable): array
return [MailChannel::class];
}
- public function toMail(User $user): SystemMessageMailable
+ public function toMail(CraftUser $user): SystemMessageMailable
{
+ $user = $user->asElement();
+
$url = Users::getPasswordResetUrl($user, $this->token);
return app(SystemMessages::class)->mailable(
diff --git a/src/User/Notifications/VerifyEmailNotification.php b/src/User/Notifications/VerifyEmailNotification.php
index b063339f3b5..504715f551e 100644
--- a/src/User/Notifications/VerifyEmailNotification.php
+++ b/src/User/Notifications/VerifyEmailNotification.php
@@ -9,7 +9,7 @@
use CraftCms\Cms\Support\Template;
use CraftCms\Cms\SystemMessage\Mailables\SystemMessageMailable;
use CraftCms\Cms\SystemMessage\SystemMessages;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Contracts\CraftUser;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Channels\MailChannel;
@@ -32,8 +32,10 @@ public function via(mixed $notifiable): array
return [MailChannel::class];
}
- public function toMail(User $user): SystemMessageMailable
+ public function toMail(CraftUser $user): SystemMessageMailable
{
+ $user = $user->asElement();
+
$url = Users::getEmailVerifyUrl($user, $this->token);
return app(SystemMessages::class)->mailable(
diff --git a/src/User/Policies/UserPolicy.php b/src/User/Policies/UserPolicy.php
index 1087e059efa..95930b53315 100644
--- a/src/User/Policies/UserPolicy.php
+++ b/src/User/Policies/UserPolicy.php
@@ -6,7 +6,9 @@
use CraftCms\Cms\Edition;
use CraftCms\Cms\Element\Policies\ElementPolicy;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\Support\Facades\Users;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use CraftCms\Cms\User\Elements\User as UserElement;
use CraftCms\Cms\User\UserPermissions;
class UserPolicy extends ElementPolicy
@@ -15,33 +17,37 @@ public function __construct(
private readonly UserPermissions $userPermissions,
) {}
- public function view(User $user, User $target): bool
+ public function view(CraftUser $user, UserElement $target): bool
{
- return $user->id === $target->id || $user->can('viewUsers');
+ if ($user->getCraftUserId() === $target->id) {
+ return true;
+ }
+
+ return $user->can('viewUsers');
}
- public function save(User $user, User $target): bool
+ public function save(CraftUser $user, UserElement $target): bool
{
// New user registration
if (! $target->id) {
- return $user->canRegisterUsers();
+ return $user->can('registerUsers') && Users::canCreateUsers();
}
// User can always save themselves
- if ($user->id === $target->id) {
+ if ($user->getCraftUserId() === $target->id) {
return true;
}
return $user->can('editUsers');
}
- public function delete(User $user, User $target): bool
+ public function delete(CraftUser $user, UserElement $target): bool
{
if (Edition::get() === Edition::Solo) {
return false;
}
- if ($user->id === $target->id) {
+ if ($user->getCraftUserId() === $target->id) {
return true;
}
@@ -51,27 +57,27 @@ public function delete(User $user, User $target): bool
}
// Non-admins cannot delete admins
- if ($target->admin && ! $user->admin) {
+ if ($target->admin && ! $user->isAdmin()) {
return false;
}
return true;
}
- public function duplicate(User $user, User $target): bool
+ public function duplicate(CraftUser $user, UserElement $target): bool
{
return false;
}
- public function copy(User $user, User $target): bool
+ public function copy(CraftUser $user, UserElement $target): bool
{
return false;
}
- public function impersonate(User $user, User $target): bool
+ public function impersonate(CraftUser $user, UserElement $target): bool
{
// Admins can do whatever they want
- if ($user->admin) {
+ if ($user->isAdmin()) {
return true;
}
@@ -85,8 +91,14 @@ public function impersonate(User $user, User $target): bool
return false;
}
+ $userId = $user->getCraftUserId();
+
+ if (! $userId) {
+ return false;
+ }
+
// Make sure the impersonator has at least all the same permissions as the target
- $userPermissions = $this->userPermissions->getPermissionsByUserId($user->id)->flip();
+ $userPermissions = $this->userPermissions->getPermissionsByUserId($userId)->flip();
$targetPermissions = $this->userPermissions->getPermissionsByUserId($target->id);
foreach ($targetPermissions as $permission) {
@@ -98,14 +110,14 @@ public function impersonate(User $user, User $target): bool
return true;
}
- public function suspend(User $user, User $target): bool
+ public function suspend(CraftUser $user, UserElement $target): bool
{
if (! $user->can('moderateUsers')) {
return false;
}
// Even if you have moderateUsers permissions, only an admin should be able to suspend another admin
- if (! $user->admin && $target->admin) {
+ if (! $user->isAdmin() && $target->admin) {
return false;
}
diff --git a/src/User/UserGroups.php b/src/User/UserGroups.php
index a10d8a1b1db..4c21f327fb6 100644
--- a/src/User/UserGroups.php
+++ b/src/User/UserGroups.php
@@ -67,14 +67,14 @@ public function getAllGroups(): Collection
*/
public function getAssignableGroups(?User $user = null): Collection
{
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
if (! $currentUser && ! $user) {
return collect();
}
// If either user is an admin, all groups are fair game
- if (($currentUser !== null && $currentUser->admin) || ($user !== null && $user->admin)) {
+ if (($currentUser !== null && $currentUser->isAdmin()) || ($user !== null && $user->admin)) {
return $this->getAllGroups();
}
diff --git a/src/User/UserPermissions.php b/src/User/UserPermissions.php
index 0a8ed62f751..a3434d69ef5 100644
--- a/src/User/UserPermissions.php
+++ b/src/User/UserPermissions.php
@@ -119,7 +119,7 @@ public function getAllPermissions(): Collection
public function getAssignablePermissions(?User $user = null): Collection
{
// If either user is an admin, all permissions are fair game
- if (Auth::user()?->isAdmin() || ($user !== null && $user->admin)) {
+ if (Auth::craftUser()?->isAdmin() || ($user !== null && $user->admin)) {
return $this->getAllPermissions();
}
@@ -746,7 +746,7 @@ private function utilityPermissions(Collection $permissions): void
*/
private function filterUnassignablePermissions(Collection $permissions, ?User $user = null): Collection
{
- $currentUser = Auth::user();
+ $currentUser = Auth::craftUser();
if (! $currentUser && ! $user) {
return new Collection;
diff --git a/src/User/Users.php b/src/User/Users.php
index e251073b1e0..d2ec946520a 100644
--- a/src/User/Users.php
+++ b/src/User/Users.php
@@ -34,6 +34,7 @@
use CraftCms\Cms\Support\Json;
use CraftCms\Cms\Support\Str;
use CraftCms\Cms\Support\Url;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Data\UserGroup;
use CraftCms\Cms\User\Elements\User;
use CraftCms\Cms\User\Events\DefaultUserGroupsResolving;
@@ -57,6 +58,7 @@
use CraftCms\Cms\User\Events\UserUnsuspended;
use CraftCms\Cms\User\Events\UserUnsuspending;
use CraftCms\Cms\User\Models\User as UserModel;
+use CraftCms\Cms\User\Notifications\ActivationNotification;
use CraftCms\Cms\User\Validation\UserRules;
use CraftCms\DependencyAwareCache\Dependency\TagDependency;
use DateTime;
@@ -209,21 +211,27 @@ public function getUserPreferences(int $userId): array
/**
* Saves a user’s preferences.
*
- * @param User $user The user
+ * @param CraftUser $user The user
* @param array $preferences The user’s new preferences
*/
- public function saveUserPreferences(User $user, array $preferences): void
+ public function saveUserPreferences(CraftUser $user, array $preferences): void
{
+ $userId = $user->getCraftUserId();
+
+ if (! $userId) {
+ throw new InvalidArgumentException('Cannot save preferences for a user without an ID.');
+ }
+
// Merge in any other saved preferences
- $preferences += $this->getUserPreferences($user->id);
+ $preferences += $this->getUserPreferences($userId);
DB::table(Table::USERPREFERENCES)
->upsert([
- 'userId' => $user->id,
+ 'userId' => $userId,
'preferences' => Json::encode($preferences),
], ['userId']);
- $this->userPreferences[$user->id] = $preferences;
+ $this->userPreferences[$userId] = $preferences;
}
/**
@@ -249,7 +257,7 @@ public function getUserPreference(int $userId, string $key, mixed $default = nul
*/
public function sendPasswordResetEmail(User $user): bool
{
- return Password::broker('craft')->sendResetLink(['loginName' => $user->email]) === Password::RESET_LINK_SENT;
+ return Password::broker('craft')->sendResetLink(['email' => $user->email]) === Password::RESET_LINK_SENT;
}
/**
@@ -259,7 +267,7 @@ public function sendPasswordResetEmail(User $user): bool
*/
public function sendActivationEmail(User $user): bool
{
- $user->sendActivationNotification($this->setVerificationCodeOnUser($user));
+ $user->notify(new ActivationNotification($this->setVerificationCodeOnUser($user)));
return true;
}
@@ -1204,7 +1212,7 @@ public function saveLayout(FieldLayout $layout, bool $runValidation = true): boo
/**
* Returns whether a user is allowed to impersonate another user.
*/
- public function canImpersonate(User $impersonator, User $impersonatee): bool
+ public function canImpersonate(CraftUser $impersonator, User $impersonatee): bool
{
return $impersonator->can('impersonate', $impersonatee);
}
@@ -1212,7 +1220,7 @@ public function canImpersonate(User $impersonator, User $impersonatee): bool
/**
* Returns whether the user can suspend the given user
*/
- public function canSuspend(User $suspender, User $suspendee): bool
+ public function canSuspend(CraftUser $suspender, User $suspendee): bool
{
return $suspender->can('suspend', $suspendee);
}
diff --git a/src/Utility/Utilities.php b/src/Utility/Utilities.php
index 93bb7b47f84..281391e57ae 100644
--- a/src/Utility/Utilities.php
+++ b/src/Utility/Utilities.php
@@ -94,7 +94,7 @@ public function getAuthorizedUtilityTypes(): Collection
*/
public function checkAuthorization(string $class): bool
{
- $user = Auth::user();
+ $user = Auth::craftUser();
// The Project Config utility is for admins only!
if ($class === ProjectConfigUtility::class && ! $user?->isAdmin()) {
diff --git a/src/View/LegacyAssets/CpAsset.php b/src/View/LegacyAssets/CpAsset.php
index d95b6cca2c2..53b6c1e0231 100644
--- a/src/View/LegacyAssets/CpAsset.php
+++ b/src/View/LegacyAssets/CpAsset.php
@@ -36,10 +36,10 @@
use CraftCms\Cms\View\Enums\Position;
use CraftCms\Cms\View\HtmlStack;
use Illuminate\Pagination\Paginator;
-use Illuminate\Support\Facades\Auth;
use stdClass;
use function CraftCms\Cms\craftAsset;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
/**
@@ -116,7 +116,7 @@ private function _craftData(): array
$formattingLocale = I18N::getFormattingLocale();
$locale = I18N::getLocale();
$orientation = $locale->getOrientation();
- $currentUser = Auth::user();
+ $currentUser = currentUserElement();
$primarySite = $upToDate ? Sites::getPrimarySite() : null;
$data = [
diff --git a/src/View/TemplateGlobals.php b/src/View/TemplateGlobals.php
index a8b7eb5d1cf..64f3a57ce3c 100644
--- a/src/View/TemplateGlobals.php
+++ b/src/View/TemplateGlobals.php
@@ -13,8 +13,8 @@
use CraftCms\Cms\View\Events\TemplateGlobalsResolving;
use Illuminate\Container\Attributes\Scoped;
use Illuminate\Foundation\Application;
-use Illuminate\Support\Facades\Auth;
+use function CraftCms\Cms\currentUserElement;
use function CraftCms\Cms\t;
#[Scoped]
@@ -50,7 +50,7 @@ public function resolve(): array
$globals = [
'craft' => $this->craftVariable,
'currentSite' => $currentSite,
- 'currentUser' => Auth::user(),
+ 'currentUser' => currentUserElement(),
'siteName' => $siteName,
'siteUrl' => $siteUrl,
'systemName' => $systemName,
diff --git a/src/View/TemplateProfiler.php b/src/View/TemplateProfiler.php
index 11946431890..9dfe63320a0 100644
--- a/src/View/TemplateProfiler.php
+++ b/src/View/TemplateProfiler.php
@@ -96,13 +96,13 @@ private function shouldProfile(): bool
return $this->shouldProfile = true;
}
- $user = Auth::user();
+ $user = Auth::craftUser();
if (! $user) {
return $this->shouldProfile = false;
}
- return $this->shouldProfile = ($user->admin && $user->getPreference('profileTemplates'));
+ return $this->shouldProfile = ($user->isAdmin() && $user->getPreference('profileTemplates'));
}
private function profileToken(string $type, string $name, int $count): string
diff --git a/src/helpers.php b/src/helpers.php
index f6403e06c75..89eaec8a9cc 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -11,8 +11,11 @@
use CraftCms\Cms\Support\Typecast;
use CraftCms\Cms\Support\Url;
use CraftCms\Cms\Twig\TemplateRenderer;
+use CraftCms\Cms\User\Contracts\CraftUser;
+use CraftCms\Cms\User\Elements\User as UserElement;
use CraftCms\Cms\View\TemplateMode;
use Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Stringable;
use UnitEnum;
@@ -71,6 +74,15 @@ function debugbar()
return app()->bound('debugbar') ? app('debugbar') : optional();
}
+function currentUserElement(): ?UserElement
+{
+ $user = Auth::craftUser();
+
+ return $user instanceof CraftUser
+ ? $user->asElement()
+ : null;
+}
+
/**
* Normalizes an environment variable/constant name/CLI command option.
*
diff --git a/tests/Feature/Asset/Policies/AssetPolicyTest.php b/tests/Feature/Asset/Policies/AssetPolicyTest.php
index de048c442e5..750c7ddd902 100644
--- a/tests/Feature/Asset/Policies/AssetPolicyTest.php
+++ b/tests/Feature/Asset/Policies/AssetPolicyTest.php
@@ -9,7 +9,7 @@
use CraftCms\Cms\Filesystem\Contracts\FsInterface;
use CraftCms\Cms\Filesystem\Filesystems\Local;
use CraftCms\Cms\Filesystem\Filesystems\Temp;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Models\User;
use Illuminate\Support\Facades\Gate;
beforeEach(function () {
diff --git a/tests/Feature/Auth/AuthCraftUserMacroTest.php b/tests/Feature/Auth/AuthCraftUserMacroTest.php
new file mode 100644
index 00000000000..c0b165e544d
--- /dev/null
+++ b/tests/Feature/Auth/AuthCraftUserMacroTest.php
@@ -0,0 +1,68 @@
+toBe($user);
+});
+
+it('throws when the current auth user is not a Craft user', function () {
+ Auth::guard('craft')->setUser(new class implements Authenticatable
+ {
+ public function getAuthIdentifierName()
+ {
+ return 'id';
+ }
+
+ public function getAuthIdentifier()
+ {
+ return 123;
+ }
+
+ public function getAuthPasswordName()
+ {
+ return 'password';
+ }
+
+ public function getAuthPassword()
+ {
+ return '';
+ }
+
+ public function getRememberToken()
+ {
+ return null;
+ }
+
+ public function setRememberToken($value) {}
+
+ public function getRememberTokenName()
+ {
+ return 'remember_token';
+ }
+ });
+
+ expect(fn () => Auth::craftUser())
+ ->toThrow(AuthenticationException::class, 'The authenticated user must implement');
+});
+
+it('returns null when there is no current auth user', function () {
+ Auth::guard('craft')->forgetUser();
+
+ expect(Auth::craftUser())->toBeNull();
+});
diff --git a/tests/Feature/Auth/UserProviderTest.php b/tests/Feature/Auth/UserProviderTest.php
deleted file mode 100644
index e5ed61b69ab..00000000000
--- a/tests/Feature/Auth/UserProviderTest.php
+++ /dev/null
@@ -1,137 +0,0 @@
-provider = app(UserProvider::class);
-});
-
-test('retrieveById returns user with password', function () {
- $user = UserModel::factory()->createElement();
-
- $retrieved = $this->provider->retrieveById($user->id);
-
- expect($retrieved)->toBeInstanceOf(User::class);
- expect($retrieved->id)->toBe($user->id);
- expect($retrieved->password)->not->toBeNull();
-});
-
-test('retrieveById returns null for invalid id', function () {
- $retrieved = $this->provider->retrieveById(9999);
-
- expect($retrieved)->toBeNull();
-});
-
-test('retrieveByToken returns user when token matches', function () {
- $user = UserModel::factory()->createElement([
- 'rememberToken' => 'test-token',
- ]);
-
- $retrieved = $this->provider->retrieveByToken($user->id, 'test-token');
-
- expect($retrieved)->toBeInstanceOf(User::class);
- expect($retrieved->id)->toBe($user->id);
-});
-
-test('retrieveByToken returns null when token mismatch', function () {
- $user = UserModel::factory()->createElement([
- 'rememberToken' => 'test-token',
- ]);
-
- $retrieved = $this->provider->retrieveByToken($user->id, 'wrong-token');
-
- expect($retrieved)->toBeNull();
-});
-
-test('updateRememberToken updates database', function () {
- $user = UserModel::factory()->createElement();
-
- $this->provider->updateRememberToken($user, 'new-token');
-
- $dbToken = DB::table(Table::USERS)
- ->where('id', $user->id)
- ->value('rememberToken');
-
- expect($dbToken)->toBe('new-token');
-});
-
-test('retrieveByCredentials works with username', function () {
- $user = UserModel::factory()->createElement([
- 'username' => 'testuser',
- ]);
-
- $retrieved = $this->provider->retrieveByCredentials(['loginName' => 'testuser']);
-
- expect($retrieved)->toBeInstanceOf(User::class);
- expect($retrieved->id)->toBe($user->id);
-});
-
-test('retrieveByCredentials works with email', function () {
- $user = UserModel::factory()->createElement([
- 'email' => 'test@example.com',
- ]);
-
- $retrieved = $this->provider->retrieveByCredentials(['loginName' => 'test@example.com']);
-
- expect($retrieved)->toBeInstanceOf(User::class);
- expect($retrieved->id)->toBe($user->id);
-});
-
-test('retrieveByCredentials respects LoginUserRetrieving event', function () {
- $user = UserModel::factory()->createElement();
-
- Event::listen(LoginUserRetrieving::class, function (LoginUserRetrieving $event) use ($user) {
- $event->user = $user;
- });
-
- $retrieved = $this->provider->retrieveByCredentials(['loginName' => 'some-other-user']);
-
- expect($retrieved->id)->toBe($user->id);
-});
-
-test('retrieveByCredentials respects LoginUserRetrieved event', function () {
- $user = UserModel::factory()->createElement();
- $newUser = UserModel::factory()->createElement();
-
- Event::listen(LoginUserRetrieved::class, function (LoginUserRetrieved $event) use ($newUser) {
- $event->user = $newUser;
- });
-
- $retrieved = $this->provider->retrieveByCredentials(['loginName' => $user->username]);
-
- expect($retrieved->id)->toBe($newUser->id);
-});
-
-test('validateCredentials delegates to Auth service', function () {
- $user = UserModel::factory()->admin()->createElement();
-
- $result = $this->provider->validateCredentials($user, ['password' => 'password']);
-
- expect($result)->toBeTrue();
-});
-
-test('rehashPasswordIfRequired updates password when needed', function () {
- $user = UserModel::factory()->createElement([
- 'password' => Hash::make('password', ['rounds' => 4]),
- ]);
-
- // Force a rehash by using a different rounds count or just using $force = true
- $this->provider->rehashPasswordIfRequired($user, ['password' => 'newpassword'], true);
-
- $newPasswordHash = DB::table(Table::USERS)
- ->where('id', $user->id)
- ->value('password');
-
- expect(Hash::check('newpassword', $newPasswordHash))->toBeTrue();
- expect($newPasswordHash)->not->toBe($user->password);
-});
diff --git a/tests/Feature/Dashboard/DashboardTest.php b/tests/Feature/Dashboard/DashboardTest.php
index 6277c4563b1..1e896534686 100644
--- a/tests/Feature/Dashboard/DashboardTest.php
+++ b/tests/Feature/Dashboard/DashboardTest.php
@@ -93,7 +93,7 @@ class MyWidget extends Widget {}
});
it('returns missing widgets for saved widget types that can no longer be resolved', function () {
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
UserModel::where('id', $user->id)->update(['hasDashboard' => true]);
$user->hasDashboard = true;
@@ -113,7 +113,7 @@ class MyWidget extends Widget {}
});
it('can test if a user has a widget', function () {
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
UserModel::where('id', $user->id)->update(['hasDashboard' => true]);
$user->hasDashboard = true;
diff --git a/tests/Feature/Element/DeletionBlockersTest.php b/tests/Feature/Element/DeletionBlockersTest.php
index 5c0193f0355..473e19f98e3 100644
--- a/tests/Feature/Element/DeletionBlockersTest.php
+++ b/tests/Feature/Element/DeletionBlockersTest.php
@@ -53,7 +53,7 @@
it('reports entry author blockers with details and actions', function () {
$author = UserModel::factory()->createElement();
$entry = EntryModel::factory()
- ->hasAttached(UserModel::find($author->id), ['sortOrder' => 0], 'authors')
+ ->hasAttached(UserModel::find()->id($author->id)->status(null)->one(), ['sortOrder' => 0], 'authors')
->create();
$this->mock(ElementIndexHtml::class, function (MockInterface $mock) use ($author) {
diff --git a/tests/Feature/Element/Policies/ElementPolicyTest.php b/tests/Feature/Element/Policies/ElementPolicyTest.php
index 9d8df75923d..c540b6a19d0 100644
--- a/tests/Feature/Element/Policies/ElementPolicyTest.php
+++ b/tests/Feature/Element/Policies/ElementPolicyTest.php
@@ -22,7 +22,7 @@
});
it('is registered with the gate', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
$result = Gate::forUser($user)->allows('view', $element);
@@ -31,7 +31,7 @@
});
it('returns null from before for non-elements', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$result = $this->policy->before($user, 'view', 'not-an-element');
@@ -39,7 +39,7 @@
});
it('delegates unpublished save canonical checks to a cloned save check', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
$element->draftId = 100;
@@ -58,7 +58,7 @@
});
it('delegates published save canonical checks to the canonical element', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$canonical = createElementPolicyElement();
$element = createElementPolicyElement(canonical: $canonical);
@@ -75,7 +75,7 @@
});
it('allows gate save canonical checks for unpublished drafts when save is authorized', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
$element->draftId = 100;
@@ -94,7 +94,7 @@
});
it('denies gate save canonical checks when the delegated save check is denied', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
$element->draftId = 100;
@@ -108,7 +108,7 @@
});
it('returns false for view when the site does not exist', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement(siteId: 999999);
$result = $this->policy->before($user, 'view', $element);
@@ -117,7 +117,7 @@
});
it('returns false for save when the user cannot edit the site', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$site = Site::factory()->create();
$element = createElementPolicyElement(siteId: $site->id);
@@ -128,7 +128,7 @@
it('continues save checks when the user can edit the site', function () {
$site = Site::factory()->create();
- $user = UserModel::factory()->withPermissions(["editSite:$site->uid"])->createElement();
+ $user = UserModel::factory()->withPermissions(["editSite:$site->uid"])->create();
$element = createElementPolicyElement(siteId: $site->id);
Event::listen(ElementAuthorizing::class, function (ElementAuthorizing $event) use ($element): void {
@@ -144,7 +144,7 @@
});
it('bypasses site authorization for other abilities', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$site = Site::factory()->create();
$element = createElementPolicyElement(siteId: $site->id);
@@ -154,7 +154,7 @@
});
it('falls through when nested elements do not have a container field', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyNestedElement();
$result = $this->policy->before($user, 'view', $element);
@@ -163,7 +163,7 @@
});
it('delegates nested view checks to the field', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(view: true);
$element = createElementPolicyNestedElement(field: $field);
@@ -173,7 +173,7 @@
});
it('returns false when nested save is denied by the field', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(save: false);
$element = createElementPolicyNestedElement(field: $field);
@@ -183,7 +183,7 @@
});
it('returns null when nested save authorization is unresolved', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(save: null);
$element = createElementPolicyNestedElement(field: $field);
@@ -193,7 +193,7 @@
});
it('allows nested save when the field allows it without a layout element', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(save: true);
$element = createElementPolicyNestedElement(field: $field);
@@ -203,7 +203,7 @@
});
it('returns false for nested save when the field layout element exists but there is no owner', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(save: true);
$field->layoutElement = createElementPolicyLayoutElement(editable: true);
$element = createElementPolicyNestedElement(field: $field);
@@ -214,7 +214,7 @@
});
it('returns the layout element editability for nested save when an owner exists', function (bool $editable, bool $expected) {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(save: true);
$field->layoutElement = createElementPolicyLayoutElement(editable: $editable);
$element = createElementPolicyNestedElement(
@@ -231,7 +231,7 @@
]);
it('delegates nested delete checks to the field', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(delete: true);
$element = createElementPolicyNestedElement(field: $field);
@@ -241,7 +241,7 @@
});
it('delegates nested duplicate checks to the field', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(duplicate: true);
$element = createElementPolicyNestedElement(field: $field);
@@ -251,7 +251,7 @@
});
it('delegates nested duplicate as draft checks to the field', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(duplicate: true);
$element = createElementPolicyNestedElement(field: $field);
@@ -261,7 +261,7 @@
});
it('delegates nested delete for site checks to the field', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField(deleteForSite: true);
$element = createElementPolicyNestedElement(field: $field);
@@ -271,7 +271,7 @@
});
it('falls through to the authorizing event for nested abilities without field delegation', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$field = createElementPolicyField();
$element = createElementPolicyNestedElement(field: $field);
@@ -281,7 +281,7 @@
});
it('returns the authorizing event default authorization', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
$result = $this->policy->before($user, 'view', $element);
@@ -290,7 +290,7 @@
});
it('allows authorizing event listeners to authorize an element', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
Event::listen(ElementAuthorizing::class, function (ElementAuthorizing $event): void {
@@ -303,7 +303,7 @@
});
it('allows authorizing event listeners to deny an element', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
Event::listen(ElementAuthorizing::class, function (ElementAuthorizing $event): void {
@@ -316,7 +316,7 @@
});
it('returns false for built-in abilities via __call', function (string $ability) {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
$result = $this->policy->$ability($user, $element);
@@ -334,7 +334,7 @@
]);
it('throws for unsupported methods via __call', function () {
- $user = UserModel::factory()->createElement();
+ $user = UserModel::factory()->create();
$element = createElementPolicyElement();
$this->policy->unsupportedAbility($user, $element);
diff --git a/tests/Feature/Field/Policies/ContentBlockPolicyTest.php b/tests/Feature/Field/Policies/ContentBlockPolicyTest.php
index 4cf7067be0a..a3b3e834d95 100644
--- a/tests/Feature/Field/Policies/ContentBlockPolicyTest.php
+++ b/tests/Feature/Field/Policies/ContentBlockPolicyTest.php
@@ -5,7 +5,7 @@
use CraftCms\Cms\Entry\Elements\Entry;
use CraftCms\Cms\Field\Elements\ContentBlock;
use CraftCms\Cms\Field\Policies\ContentBlockPolicy;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Models\User;
beforeEach(function () {
$this->policy = app(ContentBlockPolicy::class);
@@ -13,7 +13,7 @@
it('is registered with the gate', function () {
$contentBlock = new ContentBlock;
- $user = User::findOne();
+ $user = User::findOrFail(CraftCms\Cms\User\Elements\User::findOne()->id);
$result = $user->can('view', $contentBlock);
@@ -21,7 +21,7 @@
});
it('returns false without owner for view', function () {
- $user = User::findOne();
+ $user = User::findOrFail(CraftCms\Cms\User\Elements\User::findOne()->id);
$contentBlock = createTestContentBlock(owner: null);
$result = $user->can('view', $contentBlock);
@@ -30,7 +30,7 @@
});
it('returns false without owner for save', function () {
- $user = User::findOne();
+ $user = User::findOrFail(CraftCms\Cms\User\Elements\User::findOne()->id);
$contentBlock = createTestContentBlock(owner: null);
$result = $user->can('save', $contentBlock);
@@ -39,7 +39,7 @@
});
it('returns false without owner for delete', function () {
- $user = User::findOne();
+ $user = User::findOrFail(CraftCms\Cms\User\Elements\User::findOne()->id);
$contentBlock = createTestContentBlock(owner: null);
$result = $user->can('delete', $contentBlock);
@@ -48,7 +48,7 @@
});
it('returns false without owner for duplicate', function () {
- $user = User::findOne();
+ $user = User::findOrFail(CraftCms\Cms\User\Elements\User::findOne()->id);
$contentBlock = createTestContentBlock(owner: null);
$result = $user->can('duplicate', $contentBlock);
@@ -57,7 +57,7 @@
});
it('create drafts always returns true', function () {
- $user = User::findOne();
+ $user = User::findOrFail(CraftCms\Cms\User\Elements\User::findOne()->id);
$contentBlock = createTestContentBlock(owner: null);
$result = $user->can('createDrafts', $contentBlock);
diff --git a/tests/Feature/Http/Controllers/AnnouncementsControllerTest.php b/tests/Feature/Http/Controllers/AnnouncementsControllerTest.php
index 1f27d54dfa8..012ebdfd256 100644
--- a/tests/Feature/Http/Controllers/AnnouncementsControllerTest.php
+++ b/tests/Feature/Http/Controllers/AnnouncementsControllerTest.php
@@ -15,7 +15,7 @@
$announcement = Announcement::factory()
->unread()
->create([
- 'userId' => auth()->user()->id,
+ 'userId' => auth('craft')->craftUser()?->getCraftUserId(),
]);
expect($announcement->fresh()->unread)->toBeTrue();
diff --git a/tests/Feature/Http/Controllers/Auth/LoginControllerTest.php b/tests/Feature/Http/Controllers/Auth/LoginControllerTest.php
index 6ae92d29be2..74bf06557f9 100644
--- a/tests/Feature/Http/Controllers/Auth/LoginControllerTest.php
+++ b/tests/Feature/Http/Controllers/Auth/LoginControllerTest.php
@@ -2,10 +2,13 @@
declare(strict_types=1);
+use CraftCms\Cms\Auth\Events\LoginUserRetrieved;
+use CraftCms\Cms\Auth\Events\LoginUserRetrieving;
use CraftCms\Cms\Cms;
use CraftCms\Cms\Database\Table;
use CraftCms\Cms\Http\Controllers\Auth\LoginController;
use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Models\User as UserModel;
use Illuminate\Auth\Events\Failed;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@@ -196,3 +199,40 @@
expect(Auth::check())->toBeTrue();
});
+
+test('attemptLogin respects LoginUserRetrieving event', function () {
+ $user = UserModel::factory()->admin()->create([
+ 'email' => 'event-user@example.com',
+ ]);
+
+ Event::listen(LoginUserRetrieving::class, function (LoginUserRetrieving $event) use ($user) {
+ $event->user = $user;
+ });
+
+ postJson(action([LoginController::class, 'attemptLogin']), [
+ 'loginName' => 'some-other-user',
+ 'password' => 'password',
+ ])->assertOk();
+
+ expect(Auth::id())->toBe($user->id);
+});
+
+test('attemptLogin respects LoginUserRetrieved event', function () {
+ $original = UserModel::factory()->admin()->create([
+ 'email' => 'original@example.com',
+ ]);
+ $replacement = UserModel::factory()->admin()->create([
+ 'email' => 'replacement@example.com',
+ ]);
+
+ Event::listen(LoginUserRetrieved::class, function (LoginUserRetrieved $event) use ($replacement) {
+ $event->user = $replacement;
+ });
+
+ postJson(action([LoginController::class, 'attemptLogin']), [
+ 'loginName' => $original->email,
+ 'password' => 'password',
+ ])->assertOk();
+
+ expect(Auth::id())->toBe($replacement->id);
+});
diff --git a/tests/Feature/Http/Controllers/Elements/ElementDraftsControllerTest.php b/tests/Feature/Http/Controllers/Elements/ElementDraftsControllerTest.php
index dc58b5aa85a..5392c81e240 100644
--- a/tests/Feature/Http/Controllers/Elements/ElementDraftsControllerTest.php
+++ b/tests/Feature/Http/Controllers/Elements/ElementDraftsControllerTest.php
@@ -19,6 +19,7 @@
use CraftCms\Cms\Section\Models\Section;
use CraftCms\Cms\Support\Facades\Elements as ElementsFacade;
use CraftCms\Cms\Support\Facades\Sites;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Elements\User;
use CraftCms\Cms\User\Models\User as UserModel;
use Illuminate\Http\Request;
@@ -124,9 +125,9 @@ function elementDraftsControllerPayload(Entry $entry, array $overrides = []): ar
->once()
->with([], true)
->andReturn($entry);
- $request->shouldReceive('user')
+ $request->shouldReceive('craftUser')
->once()
- ->andReturn(auth()->user());
+ ->andReturn(auth('craft')->craftUser());
app()->instance('request', Request::create('/actions/elements/ensure-draft', 'POST', [], [], [], [
'HTTP_ACCEPT' => 'application/json',
@@ -227,7 +228,7 @@ function elementDraftsControllerPayload(Entry $entry, array $overrides = []): ar
->where('canonicalId', $entry->id)
->where('draftName', 'Ported Draft')
->where('draftNotes', 'Ported draft notes')
- ->where('creator', auth()->user()->getName())
+ ->where('creator', auth('craft')->craftUser()->asElement()->getName())
->etc()
);
@@ -464,7 +465,7 @@ function elementDraftsControllerPayload(Entry $entry, array $overrides = []): ar
'draftId' => $draft->draftId,
'siteId' => $draft->siteId,
]);
- $request->setUserResolver(fn () => auth()->user());
+ $request->setUserResolver(fn () => auth('craft')->craftUser());
app()->instance('request', $request);
$controller = new class($request, app(Drafts::class), app(Elements::class), app(ElementActivity::class)) extends ElementDraftsController
@@ -473,7 +474,7 @@ function elementDraftsControllerPayload(Entry $entry, array $overrides = []): ar
protected function applyParamsToElement(ElementInterface $element): void {}
- protected function canSave(ElementInterface $element, User $user): bool
+ protected function canSave(ElementInterface $element, CraftUser $user): bool
{
return ++$this->canSaveCalls === 1;
}
diff --git a/tests/Feature/Http/Controllers/Entries/StoreEntryControllerTest.php b/tests/Feature/Http/Controllers/Entries/StoreEntryControllerTest.php
index d1b5dbdd152..9202a03c1c3 100644
--- a/tests/Feature/Http/Controllers/Entries/StoreEntryControllerTest.php
+++ b/tests/Feature/Http/Controllers/Entries/StoreEntryControllerTest.php
@@ -76,7 +76,7 @@ function createContentBlockSettings(Field $field): array
}
beforeEach(function () {
- $this->user = User::factory()->admin()->create()->asElement();
+ $this->user = User::factory()->admin()->create();
actingAs($this->user);
$this->entryType = EntryType::factory()->create();
diff --git a/tests/Feature/Http/Controllers/PluginStore/PluginStoreControllerTest.php b/tests/Feature/Http/Controllers/PluginStore/PluginStoreControllerTest.php
index bc5af5ecc9a..f652481a7d6 100644
--- a/tests/Feature/Http/Controllers/PluginStore/PluginStoreControllerTest.php
+++ b/tests/Feature/Http/Controllers/PluginStore/PluginStoreControllerTest.php
@@ -37,7 +37,7 @@
it('can return craft data', function () {
getJson(action([PluginStoreController::class, 'craftData']))
->assertOk()
- ->assertJsonFragment(['currentUser' => auth()->user()->email])
+ ->assertJsonFragment(['currentUser' => auth('craft')->craftUser()?->asElement()->email])
->assertJsonFragment(['CraftSolo' => Edition::Solo->value])
->assertJsonFragment(['CraftTeam' => Edition::Team->value])
->assertJsonFragment(['CraftPro' => Edition::Pro->value])
diff --git a/tests/Feature/Http/Controllers/User/AddressesControllerTest.php b/tests/Feature/Http/Controllers/User/AddressesControllerTest.php
index 33f89d1068c..780a65f2f80 100644
--- a/tests/Feature/Http/Controllers/User/AddressesControllerTest.php
+++ b/tests/Feature/Http/Controllers/User/AddressesControllerTest.php
@@ -85,7 +85,7 @@
});
test('store ignores ownership attributes', function () {
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
$otherUser = UserModel::factory()->createElement();
postJson(action([AddressesController::class, 'store']), [
diff --git a/tests/Feature/Http/Controllers/User/PasswordControllerTest.php b/tests/Feature/Http/Controllers/User/PasswordControllerTest.php
index 7d85e9c43fd..380e71ea1ef 100644
--- a/tests/Feature/Http/Controllers/User/PasswordControllerTest.php
+++ b/tests/Feature/Http/Controllers/User/PasswordControllerTest.php
@@ -21,7 +21,7 @@
use function Pest\Laravel\postJson;
beforeEach(function () {
- actingAs(User::find()->addSelect('password')->first());
+ actingAs(UserModel::first());
Session::passwordConfirmed();
});
@@ -195,7 +195,7 @@
])->assertOk();
Notification::assertSentTo(
- $user,
+ UserModel::findOrFail($user->id),
ResetPasswordNotification::class,
fn ($notification, $channels) => in_array(MailChannel::class, $channels)
);
@@ -270,7 +270,7 @@
it('aborts for users without current password', function () {
UserModel::first()->update(['password' => null]);
- actingAs(User::find()->addSelect('password')->first());
+ actingAs(UserModel::first());
post(action([PasswordController::class, 'store']), [
'newPassword' => 'validPassword123!',
@@ -305,7 +305,7 @@
describe('verifyPassword', function () {
beforeEach(function () {
- actingAs(User::find()->addSelect('password')->first());
+ actingAs(UserModel::first());
});
test('verifyPassword requires login', function () {
diff --git a/tests/Feature/Http/Controllers/User/PermissionsControllerTest.php b/tests/Feature/Http/Controllers/User/PermissionsControllerTest.php
index faf4ea0d7be..ce3f93b89be 100644
--- a/tests/Feature/Http/Controllers/User/PermissionsControllerTest.php
+++ b/tests/Feature/Http/Controllers/User/PermissionsControllerTest.php
@@ -43,7 +43,7 @@
$this->withoutExceptionHandling();
Edition::set(Edition::Pro);
- $user = Auth::user();
+ $user = Auth::craftUser();
$group = UserGroup::factory()->create();
expect(UserPermissions::doesUserHavePermission($user->id, 'accessCp'))->toBeFalse();
@@ -83,7 +83,7 @@
session()->passwordConfirmed();
Edition::set(Edition::Pro);
- $user = Auth::user();
+ $user = Auth::craftUser();
$group1 = UserGroup::factory()->create();
$group2 = UserGroup::factory()->create();
@@ -102,7 +102,7 @@
session()->passwordConfirmed();
Edition::set(Edition::Pro);
- $user = Auth::user();
+ $user = Auth::craftUser();
// First assign some permissions
postJson(action([PermissionsController::class, 'store']), [
@@ -123,7 +123,7 @@
session()->passwordConfirmed();
Edition::set(Edition::Pro);
- $user = Auth::user();
+ $user = Auth::craftUser();
$group = UserGroup::factory()->create();
// First assign a group
@@ -163,7 +163,7 @@
session()->passwordConfirmed();
Edition::set(Edition::Pro);
- $user = Auth::user();
+ $user = Auth::craftUser();
postJson(action([PermissionsController::class, 'store']), [
'userId' => $user->id,
diff --git a/tests/Feature/Http/Controllers/User/PreferencesControllerTest.php b/tests/Feature/Http/Controllers/User/PreferencesControllerTest.php
index 6fd7327b510..51406720048 100644
--- a/tests/Feature/Http/Controllers/User/PreferencesControllerTest.php
+++ b/tests/Feature/Http/Controllers/User/PreferencesControllerTest.php
@@ -28,7 +28,7 @@
test('store', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
expect($user->getPreference('language'))->toBe('en-US');
@@ -41,7 +41,7 @@
test('store saves multiple preferences at once', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
postJson(action([PreferencesController::class, 'store'], [
'preferredLanguage' => 'fr',
@@ -58,7 +58,7 @@
test('store handles __blank__ value for locale', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
postJson(action([PreferencesController::class, 'store'], [
'preferredLocale' => '__blank__',
@@ -69,7 +69,7 @@
test('store saves notification preferences', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
postJson(action([PreferencesController::class, 'store'], [
'notificationDuration' => 5000,
@@ -82,7 +82,7 @@
test('store saves slideout position preference', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
postJson(action([PreferencesController::class, 'store'], [
'slideoutPosition' => 'left',
@@ -93,7 +93,7 @@
test('store saves admin-only preferences for admin users', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
if (! $user->admin) {
$this->markTestSkipped('User must be admin for this test');
@@ -112,7 +112,7 @@
test('store preserves existing preferences when not provided', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
// Set initial preference
postJson(action([PreferencesController::class, 'store'], [
@@ -131,7 +131,7 @@
test('store handles boolean preferences correctly', function () {
/** @var User $user */
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
postJson(action([PreferencesController::class, 'store'], [
'useShapes' => false,
diff --git a/tests/Feature/Http/Controllers/User/RecoveryCodesControllerTest.php b/tests/Feature/Http/Controllers/User/RecoveryCodesControllerTest.php
index b87a5236927..1bfd43a5544 100644
--- a/tests/Feature/Http/Controllers/User/RecoveryCodesControllerTest.php
+++ b/tests/Feature/Http/Controllers/User/RecoveryCodesControllerTest.php
@@ -128,7 +128,7 @@
$recoveryCodes = $auth->getMethod(RecoveryCodes::class);
$recoveryCodes->generateRecoveryCodes();
- $user = auth()->user();
+ $user = auth('craft')->craftUser();
$content = postJson(action([RecoveryCodesController::class, 'download']))
->assertOk()
->getContent();
diff --git a/tests/Feature/Http/Controllers/User/SaveUserController/PublicRegistrationTest.php b/tests/Feature/Http/Controllers/User/SaveUserController/PublicRegistrationTest.php
index c1521475afb..a8e7b64a4cd 100644
--- a/tests/Feature/Http/Controllers/User/SaveUserController/PublicRegistrationTest.php
+++ b/tests/Feature/Http/Controllers/User/SaveUserController/PublicRegistrationTest.php
@@ -299,7 +299,7 @@
->assertSessionHasNoErrors();
expect(Auth::check())->toBeTrue();
- expect(Auth::user()->email)->toBe('autologin@example.com');
+ expect(Auth::craftUser()?->asElement()->email)->toBe('autologin@example.com');
});
it('can upload a photo', function () {
diff --git a/tests/Feature/Http/Middleware/EnforceLicensesTest.php b/tests/Feature/Http/Middleware/EnforceLicensesTest.php
index ec294555a56..78db13c942c 100644
--- a/tests/Feature/Http/Middleware/EnforceLicensesTest.php
+++ b/tests/Feature/Http/Middleware/EnforceLicensesTest.php
@@ -4,6 +4,7 @@
use CraftCms\Cms\Http\Middleware\EnforceLicenses;
use CraftCms\Cms\License\License;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\View\TemplateMode;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@@ -25,7 +26,7 @@
it('passes through if edition can test', function () {
$request = Request::create('foo');
- $request->setUserResolver(fn () => new class {});
+ $request->setUserResolver(fn () => Mockery::mock(CraftUser::class));
$result = $this->middleware->handle($request, fn () => 'bar');
@@ -40,7 +41,7 @@
$middleware = new EnforceLicenses($mockLicense);
$request = Request::create('foo');
- $request->setUserResolver(fn () => new class {});
+ $request->setUserResolver(fn () => Mockery::mock(CraftUser::class));
$result = $middleware->handle($request, fn () => 'bar');
@@ -62,7 +63,7 @@
$middleware = new EnforceLicenses($mockLicense);
$request = Request::create('foo');
- $request->setUserResolver(fn () => new class {});
+ $request->setUserResolver(fn () => Mockery::mock(CraftUser::class));
$result = $middleware->handle($request, fn () => new Response);
diff --git a/tests/Feature/Http/Requests/ElementRequestTest.php b/tests/Feature/Http/Requests/ElementRequestTest.php
index afea64a7460..fff4a69dc94 100644
--- a/tests/Feature/Http/Requests/ElementRequestTest.php
+++ b/tests/Feature/Http/Requests/ElementRequestTest.php
@@ -29,7 +29,7 @@
Elements::saveElement($draft);
$request = ElementRequest::create('/', 'POST');
- $request->setUserResolver(fn () => auth()->user());
+ $request->setUserResolver(fn () => auth('craft')->craftUser());
app()->instance('request', $request);
app(RequestedSite::class)->reset();
@@ -57,7 +57,7 @@
app(Drafts::class)->saveElementAsDraft($draft, auth()->id(), markAsSaved: false);
$request = ElementRequest::create('/', 'POST');
- $request->setUserResolver(fn () => auth()->user());
+ $request->setUserResolver(fn () => auth('craft')->craftUser());
app()->instance('request', $request);
app(RequestedSite::class)->reset();
@@ -87,7 +87,7 @@
$request = ElementRequest::create('/', 'POST', [
'elementUid' => $draft->uid,
]);
- $request->setUserResolver(fn () => auth()->user());
+ $request->setUserResolver(fn () => auth('craft')->craftUser());
app()->instance('request', $request);
app(RequestedSite::class)->reset();
diff --git a/tests/Feature/Twig/Tags/RequireLoginGuestTagTest.php b/tests/Feature/Twig/Tags/RequireLoginGuestTagTest.php
index afef07f91b5..375e5ae28a1 100644
--- a/tests/Feature/Twig/Tags/RequireLoginGuestTagTest.php
+++ b/tests/Feature/Twig/Tags/RequireLoginGuestTagTest.php
@@ -12,9 +12,9 @@
beforeEach(function () {
$this->renderer = app(TemplateRenderer::class);
- // Ensure request()->user() delegates to the Auth guard,
+ // Ensure request()->craftUser() delegates to the Auth guard,
// which is needed by the yii2-adapter's Controller::requireLogin().
- request()->setUserResolver(fn () => Auth::user());
+ request()->setUserResolver(fn () => Auth::craftUser());
});
describe('requireLogin', function () {
diff --git a/tests/Feature/User/Actions/SuspendUsersActionTest.php b/tests/Feature/User/Actions/SuspendUsersActionTest.php
index 01b6ae5fdb1..a80fb958df0 100644
--- a/tests/Feature/User/Actions/SuspendUsersActionTest.php
+++ b/tests/Feature/User/Actions/SuspendUsersActionTest.php
@@ -11,7 +11,7 @@
use function Pest\Laravel\postJson;
beforeEach(function () {
- actingAs(User::findOne());
+ actingAs(UserModel::findOrFail(User::findOne()->id));
});
it('suspends users via the Laravel perform-action route', function () {
diff --git a/tests/Feature/User/Actions/UnsuspendUsersActionTest.php b/tests/Feature/User/Actions/UnsuspendUsersActionTest.php
index 3c9cf06e618..4bfd5d59959 100644
--- a/tests/Feature/User/Actions/UnsuspendUsersActionTest.php
+++ b/tests/Feature/User/Actions/UnsuspendUsersActionTest.php
@@ -12,7 +12,7 @@
use function Pest\Laravel\postJson;
beforeEach(function () {
- actingAs(User::findOne());
+ actingAs(UserModel::findOrFail(User::findOne()->id));
});
it('unsuspends users via the Laravel perform-action route', function () {
diff --git a/tests/Feature/User/PasswordResetTest.php b/tests/Feature/User/PasswordResetTest.php
index 6ec0d85bf58..76ea070d242 100644
--- a/tests/Feature/User/PasswordResetTest.php
+++ b/tests/Feature/User/PasswordResetTest.php
@@ -23,7 +23,7 @@
Users::sendPasswordResetEmail($user);
Notification::assertSentTo(
- $user,
+ UserModel::findOrFail($user->id),
ResetPasswordNotification::class,
fn ($notification, $channels) => in_array(MailChannel::class, $channels)
);
diff --git a/tests/Feature/User/Policies/UserPolicyTest.php b/tests/Feature/User/Policies/UserPolicyTest.php
index e2f6f509584..624c567e4a0 100644
--- a/tests/Feature/User/Policies/UserPolicyTest.php
+++ b/tests/Feature/User/Policies/UserPolicyTest.php
@@ -3,7 +3,8 @@
declare(strict_types=1);
use CraftCms\Cms\Edition;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Elements\User as UserElement;
+use CraftCms\Cms\User\Models\User;
use CraftCms\Cms\User\Policies\UserPolicy;
use Illuminate\Support\Facades\Gate;
@@ -15,7 +16,7 @@
$targetUser = createUserTestUser(id: 2);
$currentUser = createUserTestUser(id: 1, permissions: ['viewUsers']);
- $result = Gate::forUser($currentUser)->allows('view', $targetUser);
+ $result = Gate::forUser($currentUser)->allows('view', $targetUser->asElement());
expect($result)->toBeBool();
});
@@ -23,7 +24,7 @@
it('allows user to view themselves', function () {
$user = createUserTestUser(id: 1);
- $result = $this->policy->view($user, $user);
+ $result = $this->policy->view($user, $user->asElement());
expect($result)->toBeTrue();
});
@@ -32,7 +33,7 @@
$currentUser = createUserTestUser(id: 1, permissions: ['viewUsers']);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->view($currentUser, $targetUser);
+ $result = $this->policy->view($currentUser, $targetUser->asElement());
expect($result)->toBeTrue();
});
@@ -41,7 +42,7 @@
$currentUser = createUserTestUser(id: 1);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->view($currentUser, $targetUser);
+ $result = $this->policy->view($currentUser, $targetUser->asElement());
expect($result)->toBeFalse();
});
@@ -49,7 +50,7 @@
it('allows user to save themselves', function () {
$user = createUserTestUser(id: 1);
- $result = $this->policy->save($user, $user);
+ $result = $this->policy->save($user, $user->asElement());
expect($result)->toBeTrue();
});
@@ -58,7 +59,7 @@
$currentUser = createUserTestUser(id: 1, permissions: ['editUsers']);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->save($currentUser, $targetUser);
+ $result = $this->policy->save($currentUser, $targetUser->asElement());
expect($result)->toBeTrue();
});
@@ -67,16 +68,16 @@
$currentUser = createUserTestUser(id: 1);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->save($currentUser, $targetUser);
+ $result = $this->policy->save($currentUser, $targetUser->asElement());
expect($result)->toBeFalse();
});
it('allows register users permission to create new user', function () {
- $currentUser = createUserTestUser(id: 1, canRegister: true);
+ $currentUser = createUserTestUser(id: 1, permissions: ['registerUsers']);
$newUser = createUserTestUser(); // new user without id
- $result = $this->policy->save($currentUser, $newUser);
+ $result = $this->policy->save($currentUser, $newUser->asElement());
expect($result)->toBeTrue();
});
@@ -84,7 +85,7 @@
it('does not prevent user from deleting themselves', function () {
$user = createUserTestUser(id: 1, permissions: ['deleteUsers']);
- $result = $this->policy->delete($user, $user);
+ $result = $this->policy->delete($user, $user->asElement());
expect($result)->toBeTrue();
});
@@ -94,7 +95,7 @@
$user = createUserTestUser(id: 1, permissions: ['deleteUsers']);
- $result = $this->policy->delete($user, $user);
+ $result = $this->policy->delete($user, $user->asElement());
expect($result)->toBeFalse();
});
@@ -103,7 +104,7 @@
$currentUser = createUserTestUser(id: 1, permissions: ['deleteUsers']);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->delete($currentUser, $targetUser);
+ $result = $this->policy->delete($currentUser, $targetUser->asElement());
expect($result)->toBeTrue();
});
@@ -112,7 +113,7 @@
$currentUser = createUserTestUser(id: 1);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->delete($currentUser, $targetUser);
+ $result = $this->policy->delete($currentUser, $targetUser->asElement());
expect($result)->toBeFalse();
});
@@ -121,7 +122,7 @@
$nonAdmin = createUserTestUser(id: 1, permissions: ['deleteUsers'], isAdmin: false);
$adminTarget = createUserTestUser(id: 2, isAdmin: true);
- $result = $this->policy->delete($nonAdmin, $adminTarget);
+ $result = $this->policy->delete($nonAdmin, $adminTarget->asElement());
expect($result)->toBeFalse();
});
@@ -130,7 +131,7 @@
$admin = createUserTestUser(id: 1, permissions: ['deleteUsers'], isAdmin: true);
$adminTarget = createUserTestUser(id: 2, isAdmin: true);
- $result = $this->policy->delete($admin, $adminTarget);
+ $result = $this->policy->delete($admin, $adminTarget->asElement());
expect($result)->toBeTrue();
});
@@ -139,7 +140,7 @@
$user = createUserTestUser(id: 1, isAdmin: true);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->duplicate($user, $targetUser);
+ $result = $this->policy->duplicate($user, $targetUser->asElement());
expect($result)->toBeFalse();
});
@@ -148,7 +149,7 @@
$user = createUserTestUser(id: 1, isAdmin: true);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->copy($user, $targetUser);
+ $result = $this->policy->copy($user, $targetUser->asElement());
expect($result)->toBeFalse();
});
@@ -158,7 +159,7 @@
$admin = createUserTestUser(id: 1, isAdmin: true);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->impersonate($admin, $targetUser);
+ $result = $this->policy->impersonate($admin, $targetUser->asElement());
expect($result)->toBeTrue();
});
@@ -167,7 +168,7 @@
$admin = createUserTestUser(id: 1, isAdmin: true);
$targetAdmin = createUserTestUser(id: 2, isAdmin: true);
- $result = $this->policy->impersonate($admin, $targetAdmin);
+ $result = $this->policy->impersonate($admin, $targetAdmin->asElement());
expect($result)->toBeTrue();
});
@@ -176,7 +177,7 @@
$user = createUserTestUser(id: 1, permissions: ['impersonateUsers']);
$admin = createUserTestUser(id: 2, isAdmin: true);
- $result = $this->policy->impersonate($user, $admin);
+ $result = $this->policy->impersonate($user, $admin->asElement());
expect($result)->toBeFalse();
});
@@ -185,7 +186,7 @@
$user = createUserTestUser(id: 1);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->impersonate($user, $targetUser);
+ $result = $this->policy->impersonate($user, $targetUser->asElement());
expect($result)->toBeFalse();
});
@@ -195,7 +196,7 @@
$moderator = createUserTestUser(id: 1, permissions: ['moderateUsers']);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->suspend($moderator, $targetUser);
+ $result = $this->policy->suspend($moderator, $targetUser->asElement());
expect($result)->toBeTrue();
});
@@ -204,7 +205,7 @@
$user = createUserTestUser(id: 1);
$targetUser = createUserTestUser(id: 2);
- $result = $this->policy->suspend($user, $targetUser);
+ $result = $this->policy->suspend($user, $targetUser->asElement());
expect($result)->toBeFalse();
});
@@ -213,7 +214,7 @@
$moderator = createUserTestUser(id: 1, permissions: ['moderateUsers']);
$admin = createUserTestUser(id: 2, isAdmin: true);
- $result = $this->policy->suspend($moderator, $admin);
+ $result = $this->policy->suspend($moderator, $admin->asElement());
expect($result)->toBeFalse();
});
@@ -222,7 +223,7 @@
$admin = createUserTestUser(id: 1, isAdmin: true, permissions: ['moderateUsers']);
$targetAdmin = createUserTestUser(id: 2, isAdmin: true);
- $result = $this->policy->suspend($admin, $targetAdmin);
+ $result = $this->policy->suspend($admin, $targetAdmin->asElement());
expect($result)->toBeTrue();
});
@@ -231,7 +232,7 @@
$moderator = createUserTestUser(id: 1, permissions: ['moderateUsers']);
$ssoUser = createUserTestUser(id: 2, hasSsoIdentity: true);
- $result = $this->policy->suspend($moderator, $ssoUser);
+ $result = $this->policy->suspend($moderator, $ssoUser->asElement());
expect($result)->toBeFalse();
});
@@ -241,15 +242,12 @@ function createUserTestUser(
?int $id = null,
array $permissions = [],
bool $isAdmin = false,
- bool $canRegister = false,
bool $hasSsoIdentity = false,
): User {
$user = new class extends User
{
public array $grantedPermissions = [];
- public bool $canRegister = false;
-
public bool $hasSso = false;
public function can($abilities, $arguments = []): bool
@@ -261,14 +259,24 @@ public function can($abilities, $arguments = []): bool
return in_array($abilities, $this->grantedPermissions, true);
}
- public function canRegisterUsers(): bool
- {
- return $this->canRegister;
- }
-
- public function getHasSsoIdentity(): bool
+ public function asElement(): UserElement
{
- return $this->hasSso;
+ $element = new class extends UserElement
+ {
+ public bool $hasSso = false;
+
+ public function getHasSsoIdentity(): bool
+ {
+ return $this->hasSso;
+ }
+ };
+
+ $element->id = $this->id;
+ $element->siteId = null;
+ $element->admin = $this->admin;
+ $element->hasSso = $this->hasSso;
+
+ return $element;
}
};
@@ -276,7 +284,6 @@ public function getHasSsoIdentity(): bool
$user->siteId = null;
$user->admin = $isAdmin;
$user->grantedPermissions = $permissions;
- $user->canRegister = $canRegister;
$user->hasSso = $hasSsoIdentity;
return $user;
diff --git a/tests/Unit/Address/Policies/AddressPolicyTest.php b/tests/Unit/Address/Policies/AddressPolicyTest.php
index 3604d492ee0..3cef6baf2a0 100644
--- a/tests/Unit/Address/Policies/AddressPolicyTest.php
+++ b/tests/Unit/Address/Policies/AddressPolicyTest.php
@@ -4,7 +4,8 @@
use CraftCms\Cms\Address\Elements\Address;
use CraftCms\Cms\Address\Policies\AddressPolicy;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Elements\User as UserElement;
+use CraftCms\Cms\User\Models\User;
use Illuminate\Support\Facades\Gate;
beforeEach(function () {
@@ -81,13 +82,13 @@ public function can($abilities, $arguments = []): bool
return $user;
}
-function createAddressTestAddress(?User $owner): Address
+function createAddressTestAddress(?UserElement $owner): Address
{
$address = new class extends Address
{
- public ?User $mockOwner = null;
+ public ?UserElement $mockOwner = null;
- public function getOwner(): ?User
+ public function getOwner(): ?UserElement
{
return $this->mockOwner;
}
diff --git a/tests/Unit/Asset/AssetsHelperTest.php b/tests/Unit/Asset/AssetsHelperTest.php
index a8e3aad9f42..a0dacb6ae57 100644
--- a/tests/Unit/Asset/AssetsHelperTest.php
+++ b/tests/Unit/Asset/AssetsHelperTest.php
@@ -9,6 +9,10 @@
use CraftCms\Cms\Support\Path;
use Illuminate\Support\Facades\Event;
+beforeEach(function () {
+ Cms::setIsInstalled(false);
+});
+
describe('prepareAssetName', function () {
test('returns expected results for various inputs', function (string $expected, string $name, bool $isFilename, bool $preventPluginModifications) {
expect(AssetsHelper::prepareAssetName($name, $isFilename, $preventPluginModifications))->toBe($expected);
diff --git a/tests/Unit/CmsTest.php b/tests/Unit/CmsTest.php
index de9732105b2..22cfb9c7d0f 100644
--- a/tests/Unit/CmsTest.php
+++ b/tests/Unit/CmsTest.php
@@ -13,6 +13,7 @@
use CraftCms\Cms\Support\Facades\Sites;
use CraftCms\Cms\Support\Facades\Updates;
use CraftCms\Cms\Support\Facades\Users;
+use CraftCms\Cms\User\Contracts\CraftUser;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -141,19 +142,9 @@ function createInfoRow(string $uid = 'system-uid'): void
Cms::setIsInstalled();
Cms::config()->cpTrigger = 'admin';
Updates::shouldReceive('isCraftUpdatePending')->once()->andReturn(false);
- Auth::shouldReceive('guard')->once()->with('craft')->andReturn(new class
- {
- public function user(): object
- {
- return new class
- {
- public function getAuthIdentifier(): int
- {
- return 42;
- }
- };
- }
- });
+ $user = Mockery::mock(CraftUser::class);
+ $user->shouldReceive('getAuthIdentifier')->once()->andReturn(42);
+ Auth::shouldReceive('craftUser')->once()->andReturn($user);
Users::shouldReceive('getUserPreference')->once()->with(42, 'language')->andReturn('pt-BR');
I18N::shouldReceive('validateAppLocaleId')->once()->with('pt-BR')->andReturn(true);
@@ -165,13 +156,7 @@ public function getAuthIdentifier(): int
Cms::config()->cpTrigger = 'admin';
Cms::config()->defaultCpLanguage = 'es';
Updates::shouldReceive('isCraftUpdatePending')->once()->andReturn(false);
- Auth::shouldReceive('guard')->once()->with('craft')->andReturn(new class
- {
- public function user(): null
- {
- return null;
- }
- });
+ Auth::shouldReceive('craftUser')->once()->andReturnNull();
expect(Cms::targetLanguage(Request::create('/admin')))->toBe('es');
});
@@ -181,19 +166,9 @@ public function user(): null
Cms::config()->cpTrigger = 'admin';
Cms::config()->defaultCpLanguage = null;
Updates::shouldReceive('isCraftUpdatePending')->once()->andReturn(false);
- Auth::shouldReceive('guard')->once()->with('craft')->andReturn(new class
- {
- public function user(): object
- {
- return new class
- {
- public function getAuthIdentifier(): int
- {
- return 42;
- }
- };
- }
- });
+ $user = Mockery::mock(CraftUser::class);
+ $user->shouldReceive('getAuthIdentifier')->once()->andReturn(42);
+ Auth::shouldReceive('craftUser')->once()->andReturn($user);
Users::shouldReceive('getUserPreference')->once()->with(42, 'language')->andReturn('not-real');
I18N::shouldReceive('validateAppLocaleId')->once()->with('not-real')->andReturn(false);
I18N::shouldReceive('getAppLocaleIds')->once()->andReturn(collect(['it', 'en']));
diff --git a/tests/Unit/Cp/NavigationTest.php b/tests/Unit/Cp/NavigationTest.php
index 620303976d2..f9b8e125895 100644
--- a/tests/Unit/Cp/NavigationTest.php
+++ b/tests/Unit/Cp/NavigationTest.php
@@ -9,8 +9,8 @@
use CraftCms\Cms\Support\Facades\Sections;
use CraftCms\Cms\Support\Facades\Volumes;
use CraftCms\Cms\Twig\Variables\Cp;
+use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\Utility\Utilities;
-use Illuminate\Contracts\Auth\Access\Authorizable;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
@@ -24,30 +24,11 @@
Sections::shouldReceive('getTotalEditableSections')->andReturn(0);
Volumes::shouldReceive('getTotalViewableVolumes')->andReturn(0);
- $user = new class implements Authorizable
- {
- public function isAdmin(): bool
- {
- return true;
- }
+ $user = Mockery::mock(CraftUser::class);
+ $user->shouldReceive('isAdmin')->andReturnTrue();
+ $user->shouldReceive('can')->andReturnTrue();
- public function can($abilities, $arguments = []): bool
- {
- return true;
- }
-
- public function cant($abilities, $arguments = []): bool
- {
- return false;
- }
-
- public function cannot($abilities, $arguments = []): bool
- {
- return false;
- }
- };
-
- Auth::shouldReceive('user')->andReturn($user);
+ Auth::shouldReceive('craftUser')->andReturn($user);
Auth::shouldReceive('userResolver')->andReturn(fn () => $user);
});
diff --git a/tests/Unit/Entry/Policies/EntryPolicyTest.php b/tests/Unit/Entry/Policies/EntryPolicyTest.php
index b459e9c33d2..750b7d159e1 100644
--- a/tests/Unit/Entry/Policies/EntryPolicyTest.php
+++ b/tests/Unit/Entry/Policies/EntryPolicyTest.php
@@ -7,7 +7,7 @@
use CraftCms\Cms\Entry\Policies\EntryPolicy;
use CraftCms\Cms\Section\Data\Section;
use CraftCms\Cms\Section\Enums\SectionType;
-use CraftCms\Cms\User\Elements\User;
+use CraftCms\Cms\User\Models\User;
use Illuminate\Support\Facades\Gate;
beforeEach(function () {
diff --git a/tests/Unit/Http/RequestMixinTest.php b/tests/Unit/Http/RequestMixinTest.php
index cf9dd790d41..1597af32605 100644
--- a/tests/Unit/Http/RequestMixinTest.php
+++ b/tests/Unit/Http/RequestMixinTest.php
@@ -5,6 +5,9 @@
use CraftCms\Cms\Cms;
use CraftCms\Cms\Http\Middleware\HandleTokenRequest;
use CraftCms\Cms\Http\Routing\ActionRoute;
+use CraftCms\Cms\User\Models\User;
+use Illuminate\Auth\AuthenticationException;
+use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Crypt;
@@ -22,6 +25,63 @@
Context::forgetHidden(HandleTokenRequest::HAD_TOKEN_KEY);
});
+describe('craftUser', function () {
+ it('returns the current Craft user', function () {
+ $user = new User(['id' => 123]);
+ $request = Request::create('/admin');
+ $request->setUserResolver(fn () => $user);
+
+ expect($request->craftUser())->toBe($user);
+ });
+
+ it('throws when the current user is not a Craft user', function () {
+ $request = Request::create('/admin');
+ $request->setUserResolver(fn () => new class implements Authenticatable
+ {
+ public function getAuthIdentifierName()
+ {
+ return 'id';
+ }
+
+ public function getAuthIdentifier()
+ {
+ return 123;
+ }
+
+ public function getAuthPasswordName()
+ {
+ return 'password';
+ }
+
+ public function getAuthPassword()
+ {
+ return '';
+ }
+
+ public function getRememberToken()
+ {
+ return null;
+ }
+
+ public function setRememberToken($value) {}
+
+ public function getRememberTokenName()
+ {
+ return 'remember_token';
+ }
+ });
+
+ expect(fn () => $request->craftUser())
+ ->toThrow(AuthenticationException::class, 'The request user must implement');
+ });
+
+ it('returns null when there is no current user', function () {
+ $request = Request::create('/admin');
+
+ expect($request->craftUser())->toBeNull();
+ });
+});
+
describe('isCpRequest', function () {
it('returns true for control panel requests', function () {
expect(Request::create('/admin/settings')->isCpRequest())->toBeTrue();
diff --git a/yii2-adapter/legacy/console/User.php b/yii2-adapter/legacy/console/User.php
index 8fb390cad5d..53e0ea41cc1 100644
--- a/yii2-adapter/legacy/console/User.php
+++ b/yii2-adapter/legacy/console/User.php
@@ -27,9 +27,7 @@ class User extends Component
*/
public function getIsAdmin(): bool
{
- $user = Auth::user();
-
- return ($user && $user->admin);
+ return Auth::craftUser()?->isAdmin() ?? false;
}
/**
@@ -40,9 +38,7 @@ public function getIsAdmin(): bool
*/
public function checkPermission(string $permissionName): bool
{
- $user = Auth::user();
-
- return ($user && $user->can($permissionName));
+ return Auth::craftUser()?->can($permissionName) ?? false;
}
/**
@@ -53,7 +49,7 @@ public function checkPermission(string $permissionName): bool
*/
public function getIdentity(bool $autoRenew = true): UserElement|null
{
- return Auth::user();
+ return Auth::craftUser()?->asElement();
}
/**
@@ -74,7 +70,7 @@ public function setIdentity(?UserElement $identity = null): void
*/
public function getIsGuest(): bool
{
- return Auth::user() === null;
+ return Auth::craftUser() === null;
}
/**
@@ -85,6 +81,6 @@ public function getIsGuest(): bool
*/
public function getId(): ?int
{
- return Auth::user()?->getId();
+ return Auth::craftUser()?->getCraftUserId();
}
}
diff --git a/yii2-adapter/legacy/elements/actions/DeleteUsers.php b/yii2-adapter/legacy/elements/actions/DeleteUsers.php
index 0ff1243698f..327c46e6884 100644
--- a/yii2-adapter/legacy/elements/actions/DeleteUsers.php
+++ b/yii2-adapter/legacy/elements/actions/DeleteUsers.php
@@ -144,7 +144,6 @@ public function performAction(ElementQueryInterface $query): bool
foreach ($users as $user) {
if (Gate::check('delete', $user)) {
- /** @phpstan-ignore-next-line */
$user->inheritorOnDelete = $transferContentTo;
if (Elements::deleteElement($user, $this->hard)) {
$deletedCount++;
diff --git a/yii2-adapter/legacy/services/Users.php b/yii2-adapter/legacy/services/Users.php
index c3f3cb6be13..fbda0fa552c 100644
--- a/yii2-adapter/legacy/services/Users.php
+++ b/yii2-adapter/legacy/services/Users.php
@@ -292,14 +292,11 @@ public function isVerificationCodeValidForUser(User $user, string $code): bool
return false;
}
- /** @phpstan-ignore-next-line */
if (!$user->verificationCode || !$user->verificationCodeIssuedDate) {
// Fetch from the DB
$userModel = UserModel::findOrFail($user->id);
- /** @phpstan-ignore-next-line */
$user->verificationCode = $userModel->verificationCode;
- /** @phpstan-ignore-next-line */
$user->verificationCodeIssuedDate = $userModel->verificationCodeIssuedDate?->setTimezone('UTC')->toDateTime();
if (!$user->verificationCode || !$user->verificationCodeIssuedDate) {
@@ -313,9 +310,7 @@ public function isVerificationCodeValidForUser(User $user, string $code): bool
// Make sure it’s not expired
if ($user->verificationCodeIssuedDate < $minCodeIssueDate) {
$userModel ??= UserModel::findOrFail($user->id);
- /** @phpstan-ignore-next-line */
$userModel->verificationCode = $user->verificationCode = null;
- /** @phpstan-ignore-next-line */
$userModel->verificationCodeIssuedDate = $user->verificationCodeIssuedDate = null;
$userModel->save();
diff --git a/yii2-adapter/legacy/web/Controller.php b/yii2-adapter/legacy/web/Controller.php
index 563478e1d7a..fc39ffa9ee0 100644
--- a/yii2-adapter/legacy/web/Controller.php
+++ b/yii2-adapter/legacy/web/Controller.php
@@ -294,7 +294,7 @@ private function _enforceAllowAnonymous(Action $action): void
*/
public static function currentUser(bool $autoRenew = true): ?User
{
- return Auth::user();
+ return Auth::craftUser()?->asElement();
}
/**
diff --git a/yii2-adapter/src/Mixins/UserMixin.php b/yii2-adapter/src/Mixins/UserMixin.php
index f0580565420..7119529a713 100644
--- a/yii2-adapter/src/Mixins/UserMixin.php
+++ b/yii2-adapter/src/Mixins/UserMixin.php
@@ -29,7 +29,7 @@ public function authenticate(): Closure
public function authenticateWithPasskey(): Closure
{
return function(PublicKeyCredentialRequestOptions|array|string $requestOptions, string $response): bool {
- Deprecator::log('User-authenticateWithPasskey', 'Calling ->authenticateWithPasskey on a User is deprecated. Use app(UserProvider::class)->validatePasskey() instead.');
+ Deprecator::log('User-authenticateWithPasskey', 'Calling ->authenticateWithPasskey on a User is deprecated. Use app(AuthMethods::class)->authenticateWithPasskey() instead.');
if (is_array($requestOptions)) {
$requestOptions = Json::encode($requestOptions);