From 11095a2c46eba03eaee6edae65db30cbe0035881 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 22:17:32 +0100 Subject: [PATCH 01/18] chore: wip routing changes --- .../core/js/src/forum/components/SessionDropdown.tsx | 2 +- framework/core/js/src/forum/components/SettingsPage.tsx | 9 ++++++++- framework/core/js/src/forum/components/UserPage.tsx | 5 +++-- framework/core/js/src/forum/routes.ts | 4 ++-- framework/core/src/Forum/routes.php | 6 ------ 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/framework/core/js/src/forum/components/SessionDropdown.tsx b/framework/core/js/src/forum/components/SessionDropdown.tsx index 1d265a38af..0e12bd8aa7 100644 --- a/framework/core/js/src/forum/components/SessionDropdown.tsx +++ b/framework/core/js/src/forum/components/SessionDropdown.tsx @@ -59,7 +59,7 @@ export default class SessionDropdown + {app.translator.trans('core.forum.header.settings_button')} , 50 diff --git a/framework/core/js/src/forum/components/SettingsPage.tsx b/framework/core/js/src/forum/components/SettingsPage.tsx index 16c28499ff..f59a000ff8 100644 --- a/framework/core/js/src/forum/components/SettingsPage.tsx +++ b/framework/core/js/src/forum/components/SettingsPage.tsx @@ -27,7 +27,14 @@ export default class SettingsPage) { super.oninit(vnode); - this.show(app.session.user!); + const routeUsername = m.route.param('username'); + + // @ todo: check if admin is appropriate or a permission should be used + if (routeUsername !== app.session.user?.slug() && !app.session.user?.isAdmin()) { + m.route.set('/'); + } + + this.loadUser(routeUsername); app.setTitle(extractText(app.translator.trans('core.forum.settings.title'))); } diff --git a/framework/core/js/src/forum/components/UserPage.tsx b/framework/core/js/src/forum/components/UserPage.tsx index 636753f3c2..d88f3a94e4 100644 --- a/framework/core/js/src/forum/components/UserPage.tsx +++ b/framework/core/js/src/forum/components/UserPage.tsx @@ -153,11 +153,12 @@ export default class UserPage, -90); items.add( 'settings', - + {app.translator.trans('core.forum.user.settings_link')} , -100 diff --git a/framework/core/js/src/forum/routes.ts b/framework/core/js/src/forum/routes.ts index bdab000730..3ec3fbfb67 100644 --- a/framework/core/js/src/forum/routes.ts +++ b/framework/core/js/src/forum/routes.ts @@ -30,9 +30,9 @@ export default function (app: ForumApplication) { user: { path: '/u/:username', component: PostsUserPage }, 'user.posts': { path: '/u/:username', component: PostsUserPage }, 'user.discussions': { path: '/u/:username/discussions', component: () => import('./components/DiscussionsUserPage') }, - - settings: { path: '/settings', component: () => import('./components/SettingsPage') }, + 'user.settings': { path: '/u/:username/settings', component: () => import('./components/SettingsPage') }, 'user.security': { path: '/u/:username/security', component: () => import('./components/UserSecurityPage') }, + notifications: { path: '/notifications', component: () => import('./components/NotificationsPage') }, }; } diff --git a/framework/core/src/Forum/routes.php b/framework/core/src/Forum/routes.php index 1d08c9a622..d6998e12d6 100644 --- a/framework/core/src/Forum/routes.php +++ b/framework/core/src/Forum/routes.php @@ -37,12 +37,6 @@ $route->toForum(Content\User::class) ); - $map->get( - '/settings', - 'settings', - $route->toForum(Content\AssertRegistered::class) - ); - $map->get( '/notifications', 'notifications', From 77613864e9383a37842cc08e45dd26e731091a33 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 22:41:18 +0100 Subject: [PATCH 02/18] chore(core): allow admins to get and set any users preference --- framework/core/src/Api/Resource/UserResource.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/core/src/Api/Resource/UserResource.php b/framework/core/src/Api/Resource/UserResource.php index 59785b4c88..2ab5aaee06 100644 --- a/framework/core/src/Api/Resource/UserResource.php +++ b/framework/core/src/Api/Resource/UserResource.php @@ -277,8 +277,8 @@ public function fields(): array return $user->getNewNotificationCount(); }), Schema\Arr::make('preferences') - ->visible(fn (User $user, Context $context) => ($context->collection instanceof self || $context->collection instanceof ForumResource) && $context->getActor()->id === $user->id) - ->writable(fn (User $user, Context $context) => $context->getActor()->id === $user->id) + ->visible(fn (User $user, Context $context) => ($context->collection instanceof self || $context->collection instanceof ForumResource) && ($context->getActor()->id === $user->id || $context->getActor()->isAdmin())) + ->writable(fn (User $user, Context $context) => $context->getActor()->id === $user->id || $context->getActor()->isAdmin()) ->set(function (User $user, array $value) { foreach ($value as $k => $v) { $user->setPreference($k, $v); From d2a855c03ea8d1e8cf8804591858d44e575d93c1 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 22:42:38 +0100 Subject: [PATCH 03/18] chore: rolling with admin --- framework/core/js/src/forum/components/SettingsPage.tsx | 1 - framework/core/js/src/forum/components/UserPage.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/framework/core/js/src/forum/components/SettingsPage.tsx b/framework/core/js/src/forum/components/SettingsPage.tsx index f59a000ff8..e29da8ec16 100644 --- a/framework/core/js/src/forum/components/SettingsPage.tsx +++ b/framework/core/js/src/forum/components/SettingsPage.tsx @@ -29,7 +29,6 @@ export default class SettingsPage, -90); items.add( From 7006170456bfc4b4db3341c49e94cb75f3e73563 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 22:57:27 +0100 Subject: [PATCH 04/18] chore(core): refactor ChangeEmailModal --- .../src/forum/components/ChangeEmailModal.tsx | 62 ++++++++++--------- .../js/src/forum/components/SettingsPage.tsx | 2 +- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/framework/core/js/src/forum/components/ChangeEmailModal.tsx b/framework/core/js/src/forum/components/ChangeEmailModal.tsx index 6f149bdf6d..f76cdf0c22 100644 --- a/framework/core/js/src/forum/components/ChangeEmailModal.tsx +++ b/framework/core/js/src/forum/components/ChangeEmailModal.tsx @@ -7,11 +7,16 @@ import RequestError from '../../common/utils/RequestError'; import ItemList from '../../common/utils/ItemList'; import Form from '../../common/components/Form'; +import type User from '../../common/models/User'; + +export interface IChangeEmailModalAttrs extends IFormModalAttrs { + user: User; +} /** * The `ChangeEmailModal` component shows a modal dialog which allows the user * to change their email address. */ -export default class ChangeEmailModal extends FormModal { +export default class ChangeEmailModal extends FormModal { /** * The value of the email input. */ @@ -30,7 +35,7 @@ export default class ChangeEmailModal) { super.oninit(vnode); - this.email = Stream(app.session.user!.email() || ''); + this.email = Stream(this.attrs.user.email() || ''); this.password = Stream(''); } @@ -75,31 +80,26 @@ export default class ChangeEmailModal - + ); - items.add( - 'password', -
- -
- ); + if (!app.session.user?.isAdmin()) { + items.add( + 'password', +
+ +
+ ); + } items.add( 'submit', @@ -119,7 +119,7 @@ export default class ChangeEmailModal { - this.success = true; + if (!app.session.user?.isAdmin()) { + this.success = true; + } else { + this.hide(); + } }) .catch(() => {}) .then(this.loaded.bind(this)); diff --git a/framework/core/js/src/forum/components/SettingsPage.tsx b/framework/core/js/src/forum/components/SettingsPage.tsx index e29da8ec16..6b170a4911 100644 --- a/framework/core/js/src/forum/components/SettingsPage.tsx +++ b/framework/core/js/src/forum/components/SettingsPage.tsx @@ -101,7 +101,7 @@ export default class SettingsPage app.modal.show(ChangeEmailModal)}> + , 90 From 9d42acb7b994f0b51316ebf93106c6cc21e5e04b Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 23:27:55 +0100 Subject: [PATCH 05/18] chore(core): refactor RequestPasswordResetModal Renaming as the pas --- ...Modal.tsx => RequestPasswordResetModal.tsx} | 18 +++++++++++++----- .../js/src/forum/components/SettingsPage.tsx | 4 ++-- .../forum/components/Modals.test.ts | 6 +++--- 3 files changed, 18 insertions(+), 10 deletions(-) rename framework/core/js/src/forum/components/{ChangePasswordModal.tsx => RequestPasswordResetModal.tsx} (72%) diff --git a/framework/core/js/src/forum/components/ChangePasswordModal.tsx b/framework/core/js/src/forum/components/RequestPasswordResetModal.tsx similarity index 72% rename from framework/core/js/src/forum/components/ChangePasswordModal.tsx rename to framework/core/js/src/forum/components/RequestPasswordResetModal.tsx index f84c9a2037..be8459b246 100644 --- a/framework/core/js/src/forum/components/ChangePasswordModal.tsx +++ b/framework/core/js/src/forum/components/RequestPasswordResetModal.tsx @@ -1,17 +1,25 @@ -import app from '../../forum/app'; +import app from '../app'; import FormModal, { IFormModalAttrs } from '../../common/components/FormModal'; import Button from '../../common/components/Button'; import Mithril from 'mithril'; import ItemList from '../../common/utils/ItemList'; import Form from '../../common/components/Form'; +import type User from '../../common/models/User'; + +export interface IRequestPasswordResetModalAttrs extends IFormModalAttrs { + user: User; +} + /** - * The `ChangePasswordModal` component shows a modal dialog which allows the + * The `RequestPasswordResetModal` component shows a modal dialog which allows the * user to send themself a password reset email. */ -export default class ChangePasswordModal extends FormModal { +export default class RequestPasswordResetModal< + CustomAttrs extends IRequestPasswordResetModalAttrs = IRequestPasswordResetModalAttrs +> extends FormModal { className() { - return 'ChangePasswordModal Modal--small'; + return 'RequestPasswordResetModal Modal--small'; } title() { @@ -58,6 +66,6 @@ export default class ChangePasswordModal app.modal.show(ChangePasswordModal)}> + , 100 diff --git a/framework/core/js/tests/integration/forum/components/Modals.test.ts b/framework/core/js/tests/integration/forum/components/Modals.test.ts index baf9dbde10..1664893b42 100644 --- a/framework/core/js/tests/integration/forum/components/Modals.test.ts +++ b/framework/core/js/tests/integration/forum/components/Modals.test.ts @@ -4,7 +4,7 @@ import { app } from '../../../../src/forum'; import ModalManager from '../../../../src/common/components/ModalManager'; import GlobalDiscussionsSearchSource from '../../../../src/forum/components/GlobalDiscussionsSearchSource'; import ChangeEmailModal from '../../../../src/forum/components/ChangeEmailModal'; -import ChangePasswordModal from '../../../../src/forum/components/ChangePasswordModal'; +import RequestPasswordResetModal from '../../../../src/forum/components/RequestPasswordResetModal'; import ForgotPasswordModal from '../../../../src/forum/components/ForgotPasswordModal'; import LogInModal from '../../../../src/forum/components/LogInModal'; import NewAccessTokenModal from '../../../../src/forum/components/NewAccessTokenModal'; @@ -29,10 +29,10 @@ describe('Modals', () => { expect(manager).toHaveElement('.ModalManager'); }); - test('ChangePasswordModal renders', () => { + test('RequestPasswordResetModal renders', () => { const manager = mq(ModalManager, { state: app.modal }); - app.modal.show(ChangePasswordModal); + app.modal.show(RequestPasswordResetModal); manager.redraw(); From 0b9b54bad0d4340d892b34f0197477e033317e25 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 23:34:16 +0100 Subject: [PATCH 06/18] chore: change theme only conditionally --- framework/core/js/src/forum/components/SettingsPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/core/js/src/forum/components/SettingsPage.tsx b/framework/core/js/src/forum/components/SettingsPage.tsx index 1cb1fc42d0..449699301e 100644 --- a/framework/core/js/src/forum/components/SettingsPage.tsx +++ b/framework/core/js/src/forum/components/SettingsPage.tsx @@ -197,7 +197,11 @@ export default class SettingsPage { this.colorSchemeLoading = false; - app.setColorScheme(mode.id); + + if (this.user === app.session.user) { + app.setColorScheme(mode.id); + } + m.redraw(); }); }} From efb620ccf2ca90f88efdeccfd61319f57aa6d281 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 23:40:04 +0100 Subject: [PATCH 07/18] chore: improve naming --- .../js/src/forum/components/RequestPasswordResetModal.tsx | 6 +++--- framework/core/js/src/forum/components/SettingsPage.tsx | 4 ++-- framework/core/locale/core.yml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/framework/core/js/src/forum/components/RequestPasswordResetModal.tsx b/framework/core/js/src/forum/components/RequestPasswordResetModal.tsx index be8459b246..727c342c41 100644 --- a/framework/core/js/src/forum/components/RequestPasswordResetModal.tsx +++ b/framework/core/js/src/forum/components/RequestPasswordResetModal.tsx @@ -23,7 +23,7 @@ export default class RequestPasswordResetModal< } title() { - return app.translator.trans('core.forum.change_password.title'); + return app.translator.trans('core.forum.request_password_reset.title'); } content() { @@ -37,13 +37,13 @@ export default class RequestPasswordResetModal< fields() { const fields = new ItemList(); - fields.add('help',

{app.translator.trans('core.forum.change_password.text')}

); + fields.add('help',

{app.translator.trans('core.forum.request_password_reset.text')}

); fields.add( 'submit',
); diff --git a/framework/core/js/src/forum/components/SettingsPage.tsx b/framework/core/js/src/forum/components/SettingsPage.tsx index 449699301e..f6fd0ff477 100644 --- a/framework/core/js/src/forum/components/SettingsPage.tsx +++ b/framework/core/js/src/forum/components/SettingsPage.tsx @@ -92,9 +92,9 @@ export default class SettingsPage(); items.add( - 'changePassword', + 'requestPasswordReset', , 100 ); diff --git a/framework/core/locale/core.yml b/framework/core/locale/core.yml index 3d2a108e74..aca7f4ea1e 100644 --- a/framework/core/locale/core.yml +++ b/framework/core/locale/core.yml @@ -500,8 +500,8 @@ core: submit_button: => core.ref.save_changes title: => core.ref.change_email - # These translations are used in the Change Password modal dialog. - change_password: + # These translations are used in the Request Password Reset modal dialog. + request_password_reset: send_button: Send Password Reset Email text: Click the button below and check your email for a link to change your password. title: => core.ref.change_password @@ -720,7 +720,7 @@ core: settings: account_heading: Account change_email_button: => core.ref.change_email - change_password_button: => core.ref.change_password + request_password_reset_button: => core.ref.change_password color_scheme_heading: Color Scheme color_schemes: auto_mode_label: System preference From 8037b93aa6bb2b3357dd8d9b17880f5cc011dc46 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Fri, 20 Mar 2026 23:49:29 +0100 Subject: [PATCH 08/18] chore(nicknames): refactor nicknames --- ...cknameModal.js => ChangeNicknameModal.tsx} | 25 +++++++++++++------ extensions/nicknames/js/src/forum/index.js | 4 +-- 2 files changed, 19 insertions(+), 10 deletions(-) rename extensions/nicknames/js/src/forum/components/{NicknameModal.js => ChangeNicknameModal.tsx} (65%) diff --git a/extensions/nicknames/js/src/forum/components/NicknameModal.js b/extensions/nicknames/js/src/forum/components/ChangeNicknameModal.tsx similarity index 65% rename from extensions/nicknames/js/src/forum/components/NicknameModal.js rename to extensions/nicknames/js/src/forum/components/ChangeNicknameModal.tsx index ad73908663..673ad08b0b 100644 --- a/extensions/nicknames/js/src/forum/components/NicknameModal.js +++ b/extensions/nicknames/js/src/forum/components/ChangeNicknameModal.tsx @@ -1,17 +1,26 @@ import app from 'flarum/forum/app'; -import FormModal from 'flarum/common/components/FormModal'; +import FormModal, { IFormModalAttrs } from 'flarum/common/components/FormModal'; import Button from 'flarum/common/components/Button'; import Stream from 'flarum/common/utils/Stream'; import Form from 'flarum/common/components/Form'; -export default class NicknameModal extends FormModal { - oninit(vnode) { +import type Mithril from 'mithril'; +import type User from 'flarum/common/models/User'; + +export interface IChangeNicknameModalAttrs extends IFormModalAttrs { + user: User; +} + +export default class ChangeNicknameModal extends FormModal { + nickname!: Stream; + + oninit(vnode: Mithril.Vnode) { super.oninit(vnode); - this.nickname = Stream(app.session.user.displayName()); + this.nickname = Stream(this.attrs.user.displayName()); } className() { - return 'NickameModal Modal--small'; + return 'ChangeNicknameModal Modal--small'; } title() { @@ -35,17 +44,17 @@ export default class NicknameModal extends FormModal { ); } - onsubmit(e) { + onsubmit(e: Event) { e.preventDefault(); - if (this.nickname() === app.session.user.displayName()) { + if (this.nickname() === this.attrs.user.displayName()) { this.hide(); return; } this.loading = true; - app.session.user + this.attrs.user .save( { nickname: this.nickname() }, { diff --git a/extensions/nicknames/js/src/forum/index.js b/extensions/nicknames/js/src/forum/index.js index 73c7a78778..00fbb82cc1 100644 --- a/extensions/nicknames/js/src/forum/index.js +++ b/extensions/nicknames/js/src/forum/index.js @@ -3,7 +3,7 @@ import { extend } from 'flarum/common/extend'; import Button from 'flarum/common/components/Button'; import extractText from 'flarum/common/utils/extractText'; import Stream from 'flarum/common/utils/Stream'; -import NickNameModal from './components/NicknameModal'; +import ChangeNicknameModal from './components/ChangeNicknameModal'; export { default as extend } from './extend'; @@ -14,7 +14,7 @@ app.initializers.add('flarum-nicknames', () => { if (this.user.canEditNickname()) { items.add( 'changeNickname', - ); From 02fdbf7ffd7aa9749bcef3b7d5e511c5fc447fd9 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 00:19:40 +0100 Subject: [PATCH 09/18] chore: backend adjustments --- .../src/Forum/Controller/UnsubscribeViewController.php | 10 ++++++++-- framework/core/src/Notification/NotificationMailer.php | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/framework/core/src/Forum/Controller/UnsubscribeViewController.php b/framework/core/src/Forum/Controller/UnsubscribeViewController.php index 9a0e0962b9..a5b89dbb81 100644 --- a/framework/core/src/Forum/Controller/UnsubscribeViewController.php +++ b/framework/core/src/Forum/Controller/UnsubscribeViewController.php @@ -11,9 +11,11 @@ use Flarum\Http\Controller\AbstractHtmlController; use Flarum\Http\UrlGenerator; +use Flarum\Http\SlugManager; use Flarum\Locale\TranslatorInterface; use Flarum\Notification\UnsubscribeToken; use Flarum\Settings\SettingsRepositoryInterface; +use Flarum\User\User; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; use Illuminate\Support\Arr; @@ -25,7 +27,8 @@ public function __construct( protected Factory $view, protected UrlGenerator $url, protected TranslatorInterface $translator, - protected SettingsRepositoryInterface $settings + protected SettingsRepositoryInterface $settings, + protected SlugManager $slugManager, ) { } @@ -40,7 +43,10 @@ public function render(Request $request): View ->where('token', $token) ->first(); - $settingsLink = $this->url->to('forum')->route('settings'); + $user = User::find($userId); + $userSlug = $this->slugManager->forResource(User::class)->toSlug($user); + + $settingsLink = $this->url->to('forum')->route('user', ['username' => $userSlug, 'filter' => 'settings']); $forumTitle = $this->settings->get('forum_title'); // If record exists and has not been used before diff --git a/framework/core/src/Notification/NotificationMailer.php b/framework/core/src/Notification/NotificationMailer.php index 9d7034d8a5..5b389199da 100644 --- a/framework/core/src/Notification/NotificationMailer.php +++ b/framework/core/src/Notification/NotificationMailer.php @@ -10,6 +10,7 @@ namespace Flarum\Notification; use Flarum\Http\UrlGenerator; +use Flarum\Http\SlugManager; use Flarum\Locale\TranslatorInterface; use Flarum\Notification\Blueprint\BlueprintInterface; use Flarum\Settings\SettingsRepositoryInterface; @@ -27,6 +28,7 @@ public function __construct( protected SettingsRepositoryInterface $settings, protected UrlGenerator $url, protected Factory $view, + protected SlugManager $slugManager, ) { } @@ -39,8 +41,11 @@ public function send(MailableInterface&BlueprintInterface $blueprint, User $user $unsubscribeRecord = $this->generateUnsubscribeToken($user->id, $blueprint::getType()); $unsubscribeRecord->save(); + $user = User::find($user->id); + $userSlug = $this->slugManager->forResource(User::class)->toSlug($user); + $unsubscribeLink = $this->url->to('forum')->route('notifications.unsubscribe', ['userId' => $user->id, 'token' => $unsubscribeRecord->token]); - $settingsLink = $this->url->to('forum')->route('settings'); + $settingsLink = $this->url->to('forum')->route('user', ['username' => $userSlug, 'filter' => 'settings']); $type = $blueprint::getType(); $forumTitle = $this->settings->get('forum_title'); $username = $user->display_name; From fa43ad5744d8c5fdbc115734eb9edb912d48de19 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 00:31:19 +0100 Subject: [PATCH 10/18] style: change import order --- .../core/src/Forum/Controller/UnsubscribeViewController.php | 2 +- framework/core/src/Notification/NotificationMailer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/core/src/Forum/Controller/UnsubscribeViewController.php b/framework/core/src/Forum/Controller/UnsubscribeViewController.php index a5b89dbb81..642bff3b85 100644 --- a/framework/core/src/Forum/Controller/UnsubscribeViewController.php +++ b/framework/core/src/Forum/Controller/UnsubscribeViewController.php @@ -10,8 +10,8 @@ namespace Flarum\Forum\Controller; use Flarum\Http\Controller\AbstractHtmlController; -use Flarum\Http\UrlGenerator; use Flarum\Http\SlugManager; +use Flarum\Http\UrlGenerator; use Flarum\Locale\TranslatorInterface; use Flarum\Notification\UnsubscribeToken; use Flarum\Settings\SettingsRepositoryInterface; diff --git a/framework/core/src/Notification/NotificationMailer.php b/framework/core/src/Notification/NotificationMailer.php index 5b389199da..7aef2826a0 100644 --- a/framework/core/src/Notification/NotificationMailer.php +++ b/framework/core/src/Notification/NotificationMailer.php @@ -9,8 +9,8 @@ namespace Flarum\Notification; -use Flarum\Http\UrlGenerator; use Flarum\Http\SlugManager; +use Flarum\Http\UrlGenerator; use Flarum\Locale\TranslatorInterface; use Flarum\Notification\Blueprint\BlueprintInterface; use Flarum\Settings\SettingsRepositoryInterface; From 811aab2e5e879f589d7a2be259cafa7dc4ce2ff3 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 00:36:45 +0100 Subject: [PATCH 11/18] test: adjust test --- .../core/tests/unit/Notification/NotificationMailerTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/core/tests/unit/Notification/NotificationMailerTest.php b/framework/core/tests/unit/Notification/NotificationMailerTest.php index 06b4662463..0cbb0d65e1 100644 --- a/framework/core/tests/unit/Notification/NotificationMailerTest.php +++ b/framework/core/tests/unit/Notification/NotificationMailerTest.php @@ -10,6 +10,7 @@ namespace Flarum\Tests\unit\Notification; use Flarum\Http\RouteCollectionUrlGenerator; +use Flarum\Http\SlugManager; use Flarum\Http\UrlGenerator; use Flarum\Locale\TranslatorInterface; use Flarum\Notification\Blueprint\BlueprintInterface; @@ -32,6 +33,7 @@ class NotificationMailerTest extends TestCase private SettingsRepositoryInterface $settings; private UrlGenerator $url; private Factory $view; + private SlugManager $slugManager; private NotificationMailer $notificationMailer; protected function setUp(): void @@ -43,6 +45,7 @@ protected function setUp(): void $this->settings = m::mock(SettingsRepositoryInterface::class); $this->url = m::mock(UrlGenerator::class); $this->view = m::mock(Factory::class); + $this->slugManager = m::mock(SlugManager::class); // Common stub setup $this->translator->shouldReceive('setLocale')->once(); @@ -56,7 +59,7 @@ protected function setUp(): void $this->view->shouldReceive('share')->once(); // Use a testable subclass that stubs out the DB-touching unsubscribe token - $this->notificationMailer = new class($this->mailer, $this->translator, $this->settings, $this->url, $this->view) extends NotificationMailer { + $this->notificationMailer = new class($this->mailer, $this->translator, $this->settings, $this->url, $this->view, $this->slugManager) extends NotificationMailer { protected function generateUnsubscribeToken(int $userId, string $emailType): UnsubscribeToken { $token = m::mock(UnsubscribeToken::class)->shouldIgnoreMissing(); From e0557336873f9c5b50bf6e36a04b01cc8d47077f Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 00:40:24 +0100 Subject: [PATCH 12/18] chore: simplify --- framework/core/src/Notification/NotificationMailer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/core/src/Notification/NotificationMailer.php b/framework/core/src/Notification/NotificationMailer.php index 7aef2826a0..c735be65f6 100644 --- a/framework/core/src/Notification/NotificationMailer.php +++ b/framework/core/src/Notification/NotificationMailer.php @@ -41,7 +41,6 @@ public function send(MailableInterface&BlueprintInterface $blueprint, User $user $unsubscribeRecord = $this->generateUnsubscribeToken($user->id, $blueprint::getType()); $unsubscribeRecord->save(); - $user = User::find($user->id); $userSlug = $this->slugManager->forResource(User::class)->toSlug($user); $unsubscribeLink = $this->url->to('forum')->route('notifications.unsubscribe', ['userId' => $user->id, 'token' => $unsubscribeRecord->token]); From 17cb7f6a7f0ac3e618f436d35dabd99b3247a2e2 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 00:41:58 +0100 Subject: [PATCH 13/18] test: mock --- .../core/tests/unit/Notification/NotificationMailerTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/framework/core/tests/unit/Notification/NotificationMailerTest.php b/framework/core/tests/unit/Notification/NotificationMailerTest.php index 0cbb0d65e1..ac9c5bb8d4 100644 --- a/framework/core/tests/unit/Notification/NotificationMailerTest.php +++ b/framework/core/tests/unit/Notification/NotificationMailerTest.php @@ -10,6 +10,7 @@ namespace Flarum\Tests\unit\Notification; use Flarum\Http\RouteCollectionUrlGenerator; +use Flarum\Http\SlugDriverInterface; use Flarum\Http\SlugManager; use Flarum\Http\UrlGenerator; use Flarum\Locale\TranslatorInterface; @@ -56,6 +57,10 @@ protected function setUp(): void $routeGenerator->shouldReceive('route')->andReturn('https://example.com/some-url'); $this->url->shouldReceive('to')->andReturn($routeGenerator); + $slugDriver = m::mock(SlugDriverInterface::class); + $slugDriver->shouldReceive('toSlug')->andReturn('test-user'); + $this->slugManager->shouldReceive('forResource')->with(User::class)->andReturn($slugDriver); + $this->view->shouldReceive('share')->once(); // Use a testable subclass that stubs out the DB-touching unsubscribe token From 1589d04809626da3f1ddbee2ef555fb9682ffd74 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 00:49:31 +0100 Subject: [PATCH 14/18] test: add missing attribute --- .../core/js/tests/integration/forum/components/Modals.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/core/js/tests/integration/forum/components/Modals.test.ts b/framework/core/js/tests/integration/forum/components/Modals.test.ts index 1664893b42..ff0166743c 100644 --- a/framework/core/js/tests/integration/forum/components/Modals.test.ts +++ b/framework/core/js/tests/integration/forum/components/Modals.test.ts @@ -21,7 +21,7 @@ describe('Modals', () => { test('ChangeEmailModal renders', () => { const manager = mq(ModalManager, { state: app.modal }); - app.modal.show(ChangeEmailModal); + app.modal.show(ChangeEmailModal, { user: app.session.user! }); manager.redraw(); @@ -32,7 +32,7 @@ describe('Modals', () => { test('RequestPasswordResetModal renders', () => { const manager = mq(ModalManager, { state: app.modal }); - app.modal.show(RequestPasswordResetModal); + app.modal.show(RequestPasswordResetModal, { user: app.session.user! }); manager.redraw(); From 84782e47f5459b33c40767115bd774214eb98aaf Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 00:56:18 +0100 Subject: [PATCH 15/18] test: change assertion --- framework/core/tests/integration/api/users/UpdateTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/core/tests/integration/api/users/UpdateTest.php b/framework/core/tests/integration/api/users/UpdateTest.php index 970af56342..16aeaa75be 100644 --- a/framework/core/tests/integration/api/users/UpdateTest.php +++ b/framework/core/tests/integration/api/users/UpdateTest.php @@ -611,7 +611,7 @@ public function users_cant_activate_others_even_with_permissions() } #[Test] - public function admins_cant_update_others_preferences() + public function admins_can_update_others_preferences() { $response = $this->send( $this->request('PATCH', '/api/users/2', [ @@ -628,7 +628,7 @@ public function admins_cant_update_others_preferences() ], ]) ); - $this->assertEquals(403, $response->getStatusCode()); + $this->assertEquals(200, $response->getStatusCode()); } #[Test] From d6c483d2cc291eb19fef85162aa4fa367619f78d Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 01:10:52 +0100 Subject: [PATCH 16/18] chore --- framework/core/tests/integration/forum/DefaultRouteTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/core/tests/integration/forum/DefaultRouteTest.php b/framework/core/tests/integration/forum/DefaultRouteTest.php index fcf1acb7aa..5c5cf62b1b 100644 --- a/framework/core/tests/integration/forum/DefaultRouteTest.php +++ b/framework/core/tests/integration/forum/DefaultRouteTest.php @@ -73,7 +73,7 @@ public function nonexistent_custom_homepage_uses_default_payload() #[Test] public function existent_custom_homepage_doesnt_use_default_payload() { - $this->setDefaultRoute('/settings'); + $this->setDefaultRoute('/notifications'); $response = $this->send( $this->request('GET', '/') From ed50a9c1d05aa1548e72d314177169e34758a4ed Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 09:44:44 +0100 Subject: [PATCH 17/18] style: run prettier --- framework/core/js/src/forum/routes.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/core/js/src/forum/routes.ts b/framework/core/js/src/forum/routes.ts index 45ab8de18f..cf4f64c14b 100644 --- a/framework/core/js/src/forum/routes.ts +++ b/framework/core/js/src/forum/routes.ts @@ -30,7 +30,11 @@ export default function (app: ForumApplication) { user: { path: '/u/:username', component: PostsUserPage, resolverClass: UserPageResolver }, 'user.posts': { path: '/u/:username', component: PostsUserPage, resolverClass: UserPageResolver }, - 'user.discussions': { path: '/u/:username/discussions', component: () => import('./components/DiscussionsUserPage'), resolverClass: UserPageResolver }, + 'user.discussions': { + path: '/u/:username/discussions', + component: () => import('./components/DiscussionsUserPage'), + resolverClass: UserPageResolver, + }, 'user.settings': { path: '/u/:username/settings', component: () => import('./components/SettingsPage'), resolverClass: UserPageResolver }, 'user.security': { path: '/u/:username/security', component: () => import('./components/UserSecurityPage'), resolverClass: UserPageResolver }, From 826aca6724aad94c48d69b4dd2b8efca56bfceaf Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sat, 21 Mar 2026 10:05:46 +0100 Subject: [PATCH 18/18] chore(gdpr): remove erasure request if actor not the user --- .../extenders/extendUserSettingsPage.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/extensions/gdpr/js/src/forum/extenders/extendUserSettingsPage.tsx b/extensions/gdpr/js/src/forum/extenders/extendUserSettingsPage.tsx index 315bdb4057..c54a7b28a1 100644 --- a/extensions/gdpr/js/src/forum/extenders/extendUserSettingsPage.tsx +++ b/extensions/gdpr/js/src/forum/extenders/extendUserSettingsPage.tsx @@ -27,16 +27,18 @@ export default function extendUserSettingsPage() { override('flarum/forum/components/SettingsPage', 'dataItems', function (): ItemList { const items = new ItemList(); - items.add( - 'gdprErasure', -
-

{app.translator.trans('flarum-gdpr.forum.settings.request_erasure_help')}

- -
, - 50 - ); + if (this.user === app.session.user) { + items.add( + 'gdprErasure', +
+

{app.translator.trans('flarum-gdpr.forum.settings.request_erasure_help')}

+ +
, + 50 + ); + } items.add( 'gdprExport',