diff --git a/config/users.php b/config/users.php index e04b07b3f58..39fbf8769ab 100644 --- a/config/users.php +++ b/config/users.php @@ -87,6 +87,19 @@ 'registration_form_honeypot_field' => null, + /* + |-------------------------------------------------------------------------- + | Delete replaced profile form assets + |-------------------------------------------------------------------------- + | + | When uploading replacement assets through the user:profile_form + | should existing assets be deleted from the container. If false + | they will be unlinked from the user but left in the container. + | + */ + + 'delete_replaced_profile_form_assets' => false, + /* |-------------------------------------------------------------------------- | User Wizard Invitation Email diff --git a/src/Auth/UserTags.php b/src/Auth/UserTags.php index 61f34cd36b0..5cc1ab41899 100644 --- a/src/Auth/UserTags.php +++ b/src/Auth/UserTags.php @@ -731,9 +731,7 @@ protected function getProfileTabs() 'display' => $section->display(), 'instructions' => $section->instructions(), 'fields' => $section->fields()->addValues($values)->preProcess()->all() - ->reject(fn ($field) => in_array($field->handle(), ['password', 'password_confirmation', 'roles', 'groups']) - || $field->fieldtype()->handle() === 'assets' - ) + ->reject(fn ($field) => in_array($field->handle(), ['password', 'password_confirmation', 'roles', 'groups'])) ->map(fn ($field) => $this->getRenderableField($field, 'user.profile')) ->values() ->all(), diff --git a/src/Http/Controllers/User/ProfileController.php b/src/Http/Controllers/User/ProfileController.php index 656d1226df9..67469e1583b 100644 --- a/src/Http/Controllers/User/ProfileController.php +++ b/src/Http/Controllers/User/ProfileController.php @@ -2,6 +2,7 @@ namespace Statamic\Http\Controllers\User; +use Statamic\Facades\Asset; use Statamic\Facades\User; use Statamic\Http\Requests\UserProfileRequest; @@ -19,6 +20,23 @@ public function __invoke(UserProfileRequest $request) $user->set($key, $value); } + $processedAssets = $request->processedAssets(); + if (config('statamic.users.delete_replaced_profile_form_assets')) { + User::blueprint() + ->fields() + ->addValues($user->data()->all()) + ->only($processedAssets->keys()) + ->preProcess() + ->values() + ->flatten() + ->map(fn ($id) => Asset::findById($id)) + ->filter() + ->each->delete(); + } + foreach ($processedAssets as $key => $value) { + $user->set($key, $value); + } + $user->save(); return $this->successfulResponse(); diff --git a/src/Http/Controllers/User/RegisterController.php b/src/Http/Controllers/User/RegisterController.php index 36114fd0443..2a30d70e6bc 100644 --- a/src/Http/Controllers/User/RegisterController.php +++ b/src/Http/Controllers/User/RegisterController.php @@ -19,7 +19,10 @@ public function __invoke(UserRegisterRequest $request) $user = User::make() ->email($request->email) ->password($request->password) - ->data($request->processedValues()); + ->data(array_merge( + $request->processedValues()->all(), + $request->processedAssets()->all(), + )); if ($roles = config('statamic.users.new_user_roles')) { $user->explicitRoles($roles); diff --git a/src/Http/Requests/UserProfileRequest.php b/src/Http/Requests/UserProfileRequest.php index 555cf1235f1..e88b06c64e8 100644 --- a/src/Http/Requests/UserProfileRequest.php +++ b/src/Http/Requests/UserProfileRequest.php @@ -9,7 +9,10 @@ use Illuminate\Validation\ValidationException; use Statamic\Facades\Site; use Statamic\Facades\User; +use Statamic\Forms\Uploaders\AssetsUploader; +use Statamic\Rules\AllowedFile; use Statamic\Rules\UniqueUserValue; +use Statamic\Support\Arr; class UserProfileRequest extends FormRequest { @@ -17,6 +20,7 @@ class UserProfileRequest extends FormRequest public $blueprintFields; public $submittedValues; + public $submittedAssets; public function authorize(): bool { @@ -49,24 +53,44 @@ protected function failedValidation(Validator $validator) public function processedValues() { - return $this->blueprintFields->process()->values() + return $this->blueprintFields + ->addValues($this->submittedValues) + ->process() + ->values() ->only(array_keys($this->submittedValues)) ->except(['email', 'password', 'groups', 'roles', 'super']); } + public function processedAssets() + { + return $this->blueprintFields + ->addValues($this->uploadAssetFiles($this->blueprintFields)) + ->process() + ->values() + ->only(array_keys($this->submittedAssets)); + } + public function validator() { $blueprint = User::blueprint(); $fields = $blueprint->fields(); $this->submittedValues = $this->valuesWithoutAssetFields($fields); - $this->blueprintFields = $fields->addValues($this->submittedValues); + $this->submittedAssets = $this->normalizeAssetsValues($fields); + $this->blueprintFields = $fields; $userId = User::current()->id(); - return $this->blueprintFields + return $fields + ->addValues(array_merge( + $this->submittedValues, + $this->submittedAssets, + )) ->validator() - ->withRules(['email' => ['required', 'email', new UniqueUserValue(except: $userId)]]) + ->withRules(array_merge( + ['email' => ['required', 'email', new UniqueUserValue(except: $userId)]], + $this->extraRules($fields), + )) ->withReplacements(['id' => $userId]) ->validator(); } @@ -86,4 +110,30 @@ private function valuesWithoutAssetFields($fields) return $this->except($assets); } + + private function normalizeAssetsValues($fields) + { + return $fields->all() + ->filter(fn ($field) => $field->fieldtype()->handle() === 'assets' && $this->hasFile($field->handle())) + ->map(fn ($field) => Arr::wrap($this->file($field->handle()))) + ->all(); + } + + protected function uploadAssetFiles($fields) + { + return $fields->all() + ->filter(fn ($field) => $field->fieldtype()->handle() === 'assets' && $this->hasFile($field->handle())) + ->map(fn ($field) => AssetsUploader::field($field)->upload($this->file($field->handle()))) + ->all(); + } + + private function extraRules($fields) + { + return $fields->all() + ->filter(fn ($field) => $field->fieldtype()->handle() === 'assets') + ->mapWithKeys(function ($field) { + return [$field->handle().'.*' => ['file', new AllowedFile]]; + }) + ->all(); + } } diff --git a/src/Http/Requests/UserRegisterRequest.php b/src/Http/Requests/UserRegisterRequest.php index bbf9ff3572a..7d70689da4a 100644 --- a/src/Http/Requests/UserRegisterRequest.php +++ b/src/Http/Requests/UserRegisterRequest.php @@ -10,7 +10,10 @@ use Illuminate\Validation\ValidationException; use Statamic\Facades\Site; use Statamic\Facades\User; +use Statamic\Forms\Uploaders\AssetsUploader; +use Statamic\Rules\AllowedFile; use Statamic\Rules\UniqueUserValue; +use Statamic\Support\Arr; class UserRegisterRequest extends FormRequest { @@ -18,6 +21,7 @@ class UserRegisterRequest extends FormRequest public $blueprintFields; public $submittedValues; + public $submittedAssets; public function authorize(): bool { @@ -50,11 +54,23 @@ protected function failedValidation(Validator $validator) public function processedValues() { - return $this->blueprintFields->process()->values() + return $this->blueprintFields + ->addValues($this->submittedValues) + ->process() + ->values() ->only(array_keys($this->submittedValues)) ->except(['email', 'groups', 'roles', 'super', 'password_confirmation']); } + public function processedAssets() + { + return $this->blueprintFields + ->addValues($this->uploadAssetFiles($this->blueprintFields)) + ->process() + ->values() + ->only(array_keys($this->submittedAssets)); + } + public function validator() { $blueprint = User::blueprint(); @@ -63,14 +79,19 @@ public function validator() $fields = $blueprint->fields(); $this->submittedValues = $this->valuesWithoutAssetFields($fields); - $this->blueprintFields = $fields->addValues($this->submittedValues); - - return $this->blueprintFields + $this->submittedAssets = $this->normalizeAssetsValues($fields); + $this->blueprintFields = $fields; + + return $fields + ->addValues(array_merge( + $this->submittedValues, + $this->submittedAssets, + )) ->validator() - ->withRules([ + ->withRules(array_merge([ 'email' => ['required', 'email', new UniqueUserValue], 'password' => ['required', 'confirmed', Password::default()], - ]) + ], $this->extraRules($fields))) ->validator(); } @@ -89,4 +110,30 @@ private function valuesWithoutAssetFields($fields) return $this->except($assets); } + + private function normalizeAssetsValues($fields) + { + return $fields->all() + ->filter(fn ($field) => $field->fieldtype()->handle() === 'assets' && $this->hasFile($field->handle())) + ->map(fn ($field) => Arr::wrap($this->file($field->handle()))) + ->all(); + } + + protected function uploadAssetFiles($fields) + { + return $fields->all() + ->filter(fn ($field) => $field->fieldtype()->handle() === 'assets' && $this->hasFile($field->handle())) + ->map(fn ($field) => AssetsUploader::field($field)->upload($this->file($field->handle()))) + ->all(); + } + + private function extraRules($fields) + { + return $fields->all() + ->filter(fn ($field) => $field->fieldtype()->handle() === 'assets') + ->mapWithKeys(function ($field) { + return [$field->handle().'.*' => ['file', new AllowedFile]]; + }) + ->all(); + } }