diff --git a/extensions/likes/js/src/forum/index.js b/extensions/likes/js/src/forum/index.js index c3309b73e2..5064bc853b 100644 --- a/extensions/likes/js/src/forum/index.js +++ b/extensions/likes/js/src/forum/index.js @@ -32,7 +32,7 @@ app.initializers.add('flarum-likes', () => { }); extend('flarum/forum/components/SearchModal', 'defaultFilters', function (filters) { if (app.current.data.routeName && app.current.data.routeName.includes('user.likes') && app.current.data.user) { - filters.posts.likedBy = app.current.data.user.id(); + filters.posts.likedBy = app.current.data.user.username(); } }); }); diff --git a/extensions/likes/locale/en.yml b/extensions/likes/locale/en.yml index 2efad1149c..42d3024c63 100644 --- a/extensions/likes/locale/en.yml +++ b/extensions/likes/locale/en.yml @@ -53,4 +53,4 @@ flarum-likes: posts: likedBy: key: likedBy - hint: The ID of the user + hint: The ID or username of the user diff --git a/extensions/likes/src/Query/LikedByFilter.php b/extensions/likes/src/Query/LikedByFilter.php index 6fdb89c9a2..5398f522a7 100644 --- a/extensions/likes/src/Query/LikedByFilter.php +++ b/extensions/likes/src/Query/LikedByFilter.php @@ -13,6 +13,7 @@ use Flarum\Search\Filter\FilterInterface; use Flarum\Search\SearchState; use Flarum\Search\ValidateFilterTrait; +use Flarum\User\UserRepository; /** * @implements FilterInterface @@ -21,6 +22,11 @@ class LikedByFilter implements FilterInterface { use ValidateFilterTrait; + public function __construct( + protected UserRepository $users + ) { + } + public function getFilterKey(): string { return 'likedBy'; @@ -28,7 +34,13 @@ public function getFilterKey(): string public function filter(SearchState $state, string|array $value, bool $negate): void { - $likedId = $this->asInt($value); + $likedUsername = $this->asString($value); + + $likedId = $this->users->getIdForUsername($likedUsername); + + if (! $likedId) { + $likedId = intval($likedUsername); + } $state ->getQuery() diff --git a/extensions/likes/src/Query/LikedFilter.php b/extensions/likes/src/Query/LikedFilter.php index b291d97eaf..e22cd674be 100644 --- a/extensions/likes/src/Query/LikedFilter.php +++ b/extensions/likes/src/Query/LikedFilter.php @@ -13,6 +13,7 @@ use Flarum\Search\Filter\FilterInterface; use Flarum\Search\SearchState; use Flarum\Search\ValidateFilterTrait; +use Flarum\User\UserRepository; /** * @implements FilterInterface @@ -21,6 +22,11 @@ class LikedFilter implements FilterInterface { use ValidateFilterTrait; + public function __construct( + protected UserRepository $users + ) { + } + public function getFilterKey(): string { return 'liked'; diff --git a/extensions/mentions/js/src/forum/index.js b/extensions/mentions/js/src/forum/index.js index 8091701173..fb5f5fcb17 100644 --- a/extensions/mentions/js/src/forum/index.js +++ b/extensions/mentions/js/src/forum/index.js @@ -92,7 +92,7 @@ app.initializers.add('flarum-mentions', () => { }); extend('flarum/forum/components/SearchModal', 'defaultFilters', function (filters) { if (app.current.data.routeName && app.current.data.routeName.includes('user.mentions') && app.current.data.user) { - filters.posts.mentioned = app.current.data.user.id(); + filters.posts.mentioned = app.current.data.user.username(); } }); }); diff --git a/extensions/mentions/locale/en.yml b/extensions/mentions/locale/en.yml index 661be1b243..7457c54ee6 100644 --- a/extensions/mentions/locale/en.yml +++ b/extensions/mentions/locale/en.yml @@ -119,4 +119,4 @@ flarum-mentions: posts: mentioned: key: mentioned - hint: The ID of the mentioned user + hint: The ID or username of the mentioned user diff --git a/extensions/mentions/src/Filter/MentionedFilter.php b/extensions/mentions/src/Filter/MentionedFilter.php index 2c555de981..4c5e11a9ec 100644 --- a/extensions/mentions/src/Filter/MentionedFilter.php +++ b/extensions/mentions/src/Filter/MentionedFilter.php @@ -13,6 +13,7 @@ use Flarum\Search\Filter\FilterInterface; use Flarum\Search\SearchState; use Flarum\Search\ValidateFilterTrait; +use Flarum\User\UserRepository; /** * @implements FilterInterface @@ -21,6 +22,11 @@ class MentionedFilter implements FilterInterface { use ValidateFilterTrait; + public function __construct( + protected UserRepository $users + ) { + } + public function getFilterKey(): string { return 'mentioned'; @@ -28,7 +34,13 @@ public function getFilterKey(): string public function filter(SearchState $state, string|array $value, bool $negate): void { - $mentionedId = $this->asInt($value); + $mentionedUsername = $this->asString($value); + + $mentionedId = $this->users->getIdForUsername($mentionedUsername); + + if (! $mentionedId) { + $mentionedId = intval($mentionedUsername); + } $state ->getQuery() diff --git a/framework/core/js/src/forum/components/DiscussionListItem.tsx b/framework/core/js/src/forum/components/DiscussionListItem.tsx index f8d5df09b9..e71d5597d8 100644 --- a/framework/core/js/src/forum/components/DiscussionListItem.tsx +++ b/framework/core/js/src/forum/components/DiscussionListItem.tsx @@ -21,11 +21,14 @@ import type { DiscussionListParams } from '../states/DiscussionListState'; import Icon from '../../common/components/Icon'; import Avatar from '../../common/components/Avatar'; import Post from '../../common/models/Post'; +import type User from '../../common/models/User'; export interface IDiscussionListItemAttrs extends ComponentAttrs { discussion: Discussion; post?: Post; params: DiscussionListParams; + jumpTo?: number; + author?: User; } /** @@ -142,7 +145,7 @@ export default class DiscussionListItem(); const discussion = this.attrs.discussion; - const user = discussion.user(); + const user = this.attrs.author || discussion.user(); items.add( 'avatar', @@ -181,6 +184,10 @@ export default class DiscussionListItem { return (
  • - +
  • ); }) as Array; diff --git a/framework/core/locale/core.yml b/framework/core/locale/core.yml index 9c8c3dc082..db81e0a6dd 100644 --- a/framework/core/locale/core.yml +++ b/framework/core/locale/core.yml @@ -915,7 +915,7 @@ core: # Translations in this namespace are used in messages output by the API. api: - invalid_username_message: "The username may only contain letters, numbers, and dashes." + invalid_username_message: "The username may only contain letters, numbers, and dashes. With at least one letter." invalid_filter_type: must_be_numeric_message: "The {filter} filter must be numeric." must_not_be_array_message: "The {filter} filter must not be an array." diff --git a/framework/core/src/Api/Resource/UserResource.php b/framework/core/src/Api/Resource/UserResource.php index 8a5be23013..5160dc3eba 100644 --- a/framework/core/src/Api/Resource/UserResource.php +++ b/framework/core/src/Api/Resource/UserResource.php @@ -146,7 +146,7 @@ public function fields(): array Schema\Str::make('username') ->requiredOnCreateWithout(['token']) ->unique('users', 'username', true) - ->regex('/^[a-z0-9_-]+$/i') + ->regex('/^(?![0-9]*$)[a-z0-9_-]+$/i') ->validationMessages([ 'username.regex' => $translator->trans('core.api.invalid_username_message'), 'username.required_without' => $translator->trans('validation.required', ['attribute' => $translator->trans('validation.attributes.username')]) diff --git a/framework/core/src/Discussion/Search/Filter/AuthorFilter.php b/framework/core/src/Discussion/Search/Filter/AuthorFilter.php index 138cbc975f..8e27bc4f5a 100644 --- a/framework/core/src/Discussion/Search/Filter/AuthorFilter.php +++ b/framework/core/src/Discussion/Search/Filter/AuthorFilter.php @@ -44,6 +44,13 @@ protected function constrain(Builder $query, string|array $rawUsernames, bool $n $ids = $this->users->getIdsForUsernames($usernames); + // To be able to also use IDs. + $actualIds = array_diff($usernames, array_keys($ids)); + + if (! empty($actualIds)) { + $ids = array_merge($ids, $actualIds); + } + $query->whereIn('discussions.user_id', $ids, 'and', $negate); } } diff --git a/framework/core/src/Post/Filter/AuthorFilter.php b/framework/core/src/Post/Filter/AuthorFilter.php index a692481e32..eedf9a650c 100644 --- a/framework/core/src/Post/Filter/AuthorFilter.php +++ b/framework/core/src/Post/Filter/AuthorFilter.php @@ -38,6 +38,13 @@ public function filter(SearchState $state, string|array $value, bool $negate): v $ids = $this->users->getIdsForUsernames($usernames); + // To be able to also use IDs. + $actualIds = array_diff($usernames, array_keys($ids)); + + if (! empty($actualIds)) { + $ids = array_merge($ids, $actualIds); + } + $state->getQuery()->whereIn('posts.user_id', $ids, 'and', $negate); } } diff --git a/framework/core/src/User/UserRepository.php b/framework/core/src/User/UserRepository.php index ca45483ae4..c5a43032ad 100644 --- a/framework/core/src/User/UserRepository.php +++ b/framework/core/src/User/UserRepository.php @@ -86,7 +86,7 @@ public function getIdsForUsernames(array $usernames, User $actor = null): array { $query = $this->query()->whereIn('username', $usernames); - return $this->scopeVisibleTo($query, $actor)->pluck('id')->all(); + return $this->scopeVisibleTo($query, $actor)->pluck('id', 'username')->all(); } /**