diff --git a/framework/core/js/forum/src/components/EditUserModal.js b/framework/core/js/forum/src/components/EditUserModal.js new file mode 100644 index 000000000..6f7cbb96b --- /dev/null +++ b/framework/core/js/forum/src/components/EditUserModal.js @@ -0,0 +1,131 @@ +import Modal from 'flarum/components/Modal'; +import Button from 'flarum/components/Button'; +import GroupBadge from 'flarum/components/GroupBadge'; +import Group from 'flarum/models/Group'; + +/** + * The `EditUserModal` component displays a modal dialog with a login form. + */ +export default class EditUserModal extends Modal { + constructor(...args) { + super(...args); + + const user = this.props.user; + + this.username = m.prop(user.username() || ''); + this.email = m.prop(user.email() || ''); + this.setPassword = m.prop(false); + this.password = m.prop(user.password() || ''); + this.groups = {}; + + app.store.all('groups') + .filter(group => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1) + .forEach(group => this.groups[group.id()] = m.prop(user.groups().indexOf(group) !== -1)); + } + + className() { + return 'EditUserModal Modal--small'; + } + + title() { + return 'Edit User'; + } + + content() { + return ( +
+
+
+ + +
+ +
+ +
+ +
+
+ +
+ +
+ + {this.setPassword() ? ( + + ) : ''} +
+
+ +
+ +
+ {Object.keys(this.groups) + .map(id => app.store.getById('groups', id)) + .map(group => ( + + ))} +
+
+ +
+ {Button.component({ + className: 'Button Button--primary', + type: 'submit', + loading: this.loading, + children: app.trans('core.save_changes') + })} +
+
+
+ ); + } + + onsubmit(e) { + e.preventDefault(); + + this.loading = true; + + const groups = Object.keys(this.groups) + .filter(id => this.groups[id]()) + .map(id => app.store.getById('groups', id)); + + const data = { + username: this.username(), + email: this.email(), + relationships: {groups} + }; + + if (this.setPassword()) { + data.password = this.password(); + } + + this.props.user.save(data).then( + () => this.hide(), + response => { + this.loading = false; + this.handleErrors(response); + } + ); + } +} diff --git a/framework/core/js/forum/src/utils/UserControls.js b/framework/core/js/forum/src/utils/UserControls.js index 756810a6d..2f58338e2 100644 --- a/framework/core/js/forum/src/utils/UserControls.js +++ b/framework/core/js/forum/src/utils/UserControls.js @@ -1,5 +1,7 @@ import Button from 'flarum/components/Button'; import Separator from 'flarum/components/Separator'; +import EditUserModal from 'flarum/components/EditUserModal'; +import DeleteUserModal from 'flarum/components/DeleteUserModal'; import ItemList from 'flarum/utils/ItemList'; /** @@ -93,13 +95,13 @@ export default { * Delete the user. */ deleteAction() { - // TODO + app.modal.show(new DeleteUserModal({user: this})); }, /** * Edit the user. */ editAction() { - // TODO + app.modal.show(new EditUserModal({user: this})); } }; diff --git a/framework/core/js/lib/components/Modal.js b/framework/core/js/lib/components/Modal.js index 2e94f14fd..f582a5673 100644 --- a/framework/core/js/lib/components/Modal.js +++ b/framework/core/js/lib/components/Modal.js @@ -109,10 +109,10 @@ export default class Modal extends Component { const errors = response && response.errors; if (errors) { - this.alert(new Alert({ - type: 'warning', - message: errors.map((error, k) => [error.detail, k < errors.length - 1 ? m('br') : '']) - })); + this.alert = new Alert({ + type: 'error', + children: errors.map((error, k) => [error.detail, k < errors.length - 1 ? m('br') : '']) + }); } m.redraw(); diff --git a/framework/core/js/lib/models/User.js b/framework/core/js/lib/models/User.js index 331e381c1..c2f87edc1 100644 --- a/framework/core/js/lib/models/User.js +++ b/framework/core/js/lib/models/User.js @@ -10,7 +10,7 @@ import Badge from 'flarum/components/Badge'; export default class User extends mixin(Model, { username: Model.attribute('username'), email: Model.attribute('email'), - isConfirmed: Model.attribute('isConfirmed'), + isActivated: Model.attribute('isActivated'), password: Model.attribute('password'), avatarUrl: Model.attribute('avatarUrl'), diff --git a/framework/core/less/forum/EditUserModal.less b/framework/core/less/forum/EditUserModal.less new file mode 100644 index 000000000..37821b0b7 --- /dev/null +++ b/framework/core/less/forum/EditUserModal.less @@ -0,0 +1,9 @@ +.EditUserModal-groups { + .checkbox { + margin-bottom: 10px; + } + .Badge { + margin: -3px 3px -3px 0; + vertical-align: 1px; + } +} diff --git a/framework/core/less/forum/app.less b/framework/core/less/forum/app.less index 4290e8bff..40bac2b0a 100644 --- a/framework/core/less/forum/app.less +++ b/framework/core/less/forum/app.less @@ -7,6 +7,7 @@ @import "DiscussionList.less"; @import "DiscussionListItem.less"; @import "DiscussionPage.less"; +@import "EditUserModal.less"; @import "Hero.less"; @import "IndexPage.less"; @import "LogInModal.less"; diff --git a/framework/core/less/lib/Form.less b/framework/core/less/lib/Form.less index 030382e13..246b4b52a 100755 --- a/framework/core/less/lib/Form.less +++ b/framework/core/less/lib/Form.less @@ -1,3 +1,11 @@ +.Form-group { + margin-bottom: 24px; + + &:last-child { + margin-bottom: 0 !important; + } +} + .Form--centered { text-align: center; @@ -8,17 +16,13 @@ padding: 15px 20px; font-size: 15px; } -} -.Form-group { - margin-bottom: 12px; - - &:last-child { - margin-bottom: 0; + .Form-group { + margin-bottom: 12px; } } -.Form-group label { +.Form-group > label { font-size: 14px; font-weight: bold; margin-bottom: 10px; diff --git a/framework/core/src/Api/Actions/Users/UpdateAction.php b/framework/core/src/Api/Actions/Users/UpdateAction.php index 616b61f14..da49858e2 100644 --- a/framework/core/src/Api/Actions/Users/UpdateAction.php +++ b/framework/core/src/Api/Actions/Users/UpdateAction.php @@ -21,7 +21,9 @@ class UpdateAction extends SerializeResourceAction /** * @inheritdoc */ - public $include = []; + public $include = [ + 'groups' => true + ]; /** * @inheritdoc diff --git a/framework/core/src/Api/Serializers/UserSerializer.php b/framework/core/src/Api/Serializers/UserSerializer.php index 2eaf1170b..a0bae1ebc 100644 --- a/framework/core/src/Api/Serializers/UserSerializer.php +++ b/framework/core/src/Api/Serializers/UserSerializer.php @@ -29,8 +29,7 @@ class UserSerializer extends UserBasicSerializer if ($canEdit) { $attributes += [ 'isActivated' => $user->is_activated, - 'email' => $user->email, - 'isConfirmed' => $user->is_confirmed + 'email' => $user->email ]; } diff --git a/framework/core/src/Core/Users/Commands/EditUserHandler.php b/framework/core/src/Core/Users/Commands/EditUserHandler.php index 477b842a5..0ec1a2e37 100644 --- a/framework/core/src/Core/Users/Commands/EditUserHandler.php +++ b/framework/core/src/Core/Users/Commands/EditUserHandler.php @@ -3,7 +3,9 @@ use Flarum\Core\Users\User; use Flarum\Core\Users\UserRepository; use Flarum\Events\UserWillBeSaved; +use Flarum\Events\UserGroupsWereChanged; use Flarum\Core\Support\DispatchesEvents; +use Flarum\Core\Exceptions\PermissionDeniedException; class EditUserHandler { @@ -33,38 +35,68 @@ class EditUserHandler $data = $command->data; $user = $this->users->findOrFail($command->userId, $actor); - - $user->assertCan($actor, 'edit'); + $isSelf = $actor->id === $user->id; $attributes = array_get($data, 'attributes', []); + $relationships = array_get($data, 'relationships', []); if (isset($attributes['username'])) { - $user->assertCan($actor, 'rename'); + $user->assertCan($actor, 'edit'); $user->rename($attributes['username']); } if (isset($attributes['email'])) { - $user->requestEmailChange($attributes['email']); + if ($isSelf) { + $user->requestEmailChange($attributes['email']); + } else { + $user->assertCan($actor, 'edit'); + $user->changeEmail($attributes['email']); + } } if (isset($attributes['password'])) { + $user->assertCan($actor, 'edit'); $user->changePassword($attributes['password']); } if (isset($attributes['bio'])) { + if (! $isSelf) { + $user->assertCan($actor, 'edit'); + } + $user->changeBio($attributes['bio']); } if (! empty($attributes['readTime'])) { + $this->assert($isSelf); $user->markAllAsRead(); } if (! empty($attributes['preferences'])) { + $this->assert($isSelf); + foreach ($attributes['preferences'] as $k => $v) { $user->setPreference($k, $v); } } + if (isset($relationships['groups']['data']) && is_array($relationships['groups']['data'])) { + $user->assertCan($actor, 'edit'); + + $newGroupIds = []; + foreach ($relationships['groups']['data'] as $group) { + if ($id = array_get($group, 'id')) { + $newGroupIds[] = $id; + } + } + + $user->raise(new UserGroupsWereChanged($user, $user->groups()->get()->all())); + + User::saved(function ($user) use ($newGroupIds) { + $user->groups()->sync($newGroupIds); + }); + } + event(new UserWillBeSaved($user, $actor, $data)); $user->save(); @@ -72,4 +104,11 @@ class EditUserHandler return $user; } + + protected function assert($true) + { + if (! $true) { + throw new PermissionDeniedException; + } + } } diff --git a/framework/core/src/Core/Users/UsersServiceProvider.php b/framework/core/src/Core/Users/UsersServiceProvider.php index 5060e5bcc..8f7682810 100644 --- a/framework/core/src/Core/Users/UsersServiceProvider.php +++ b/framework/core/src/Core/Users/UsersServiceProvider.php @@ -29,11 +29,6 @@ class UsersServiceProvider extends ServiceProvider $events->listen(ModelAllow::class, function (ModelAllow $event) { if ($event->model instanceof User) { - if ($event->action === 'edit' && - $event->model->id == $event->actor->id) { - return true; - } - if ($event->actor->hasPermission('user.'.$event->action)) { return true; } diff --git a/framework/core/src/Events/UserGroupsWereChanged.php b/framework/core/src/Events/UserGroupsWereChanged.php new file mode 100644 index 000000000..50b0d4e63 --- /dev/null +++ b/framework/core/src/Events/UserGroupsWereChanged.php @@ -0,0 +1,28 @@ +user = $user; + $this->oldGroups = $oldGroups; + } +}