diff --git a/extensions/audit/extend.php b/extensions/audit/extend.php
index fd726dbeb0..f7d1455551 100644
--- a/extensions/audit/extend.php
+++ b/extensions/audit/extend.php
@@ -33,170 +33,165 @@
Search\AuditGambits::register('ip', 'ip:', 'flarum-audit.lib.browser.filters.ip');
Search\AuditGambits::register('discussion', 'discussion:', 'flarum-audit.lib.browser.filters.discussion');
-return array_merge(
- [
- (new Extend\Frontend('forum'))
- ->js(__DIR__.'/js/dist/forum.js')
- ->jsDirectory(__DIR__.'/js/dist/forum')
- ->css(__DIR__.'/less/forum.less'),
-
- (new Extend\Frontend('common'))
- ->jsDirectory(__DIR__.'/js/dist/common'),
-
- (new Extend\Frontend('admin'))
- ->js(__DIR__.'/js/dist/admin.js')
- ->jsDirectory(__DIR__.'/js/dist/admin')
- ->css(__DIR__.'/less/admin.less')
- ->content(Content\AdminPayload::class),
-
- new Extend\Locales(__DIR__.'/locale'),
-
- (new Extend\Middleware('forum'))
- ->add(Middleware\SetLoggerActor::class),
- (new Extend\Middleware('admin'))
- ->add(Middleware\SetLoggerActor::class),
- (new Extend\Middleware('api'))
- ->add(Middleware\SetLoggerActor::class)
- ->add(Middleware\ExtendSetPermissionController::class)
- ->add(Middleware\LogPasswordResetAttempt::class),
-
- // Core integrations.
-
- (new Audit())
- ->group(null)
- ->register('cache_cleared')
- ->listen(ClearingCache::class, 'cache_cleared', function () {
- return [];
- }),
-
- (new Audit())
- ->group(null)
- ->register('extension.disabled', 'extension.enabled', 'extension.uninstalled')
- ->listen(ExtensionEvent\Disabled::class, 'extension.disabled', function ($e) {
- return ['package' => $e->extension->name];
- })
- ->listen(ExtensionEvent\Enabled::class, 'extension.enabled', function ($e) {
- return ['package' => $e->extension->name];
- })
- ->listen(ExtensionEvent\Uninstalled::class, 'extension.uninstalled', function ($e) {
- return ['package' => $e->extension->name];
- }),
-
- (new Audit())
- ->group(null)
- ->register('discussion.created', 'discussion.deleted', 'discussion.hidden', 'discussion.renamed', 'discussion.restored')
- ->listen(DiscussionEvent\Started::class, 'discussion.created', function ($e) {
- return ['discussion_id' => $e->discussion->id];
- })
- ->listen(DiscussionEvent\Deleted::class, 'discussion.deleted', function ($e) {
- return ['discussion_id' => $e->discussion->id];
- })
- ->listen(DiscussionEvent\Hidden::class, 'discussion.hidden', function ($e) {
- return ['discussion_id' => $e->discussion->id];
- })
- ->listen(DiscussionEvent\Restored::class, 'discussion.restored', function ($e) {
- return ['discussion_id' => $e->discussion->id];
- })
- ->listen(DiscussionEvent\Renamed::class, 'discussion.renamed', function ($e) {
- return [
- 'discussion_id' => $e->discussion->id,
- 'old_title' => $e->oldTitle,
- 'new_title' => $e->discussion->title,
- ];
- }),
-
- (new Audit())
- ->group(null)
- ->register('post.created', 'post.deleted', 'post.hidden', 'post.restored', 'post.revised')
- ->listen(PostEvent\Deleted::class, 'post.deleted', function ($e) {
- return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
- })
- ->listen(PostEvent\Hidden::class, 'post.hidden', function ($e) {
- return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
- })
- ->listen(PostEvent\Restored::class, 'post.restored', function ($e) {
- return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
- })
- ->listen(PostEvent\Revised::class, 'post.revised', function ($e) {
- return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
- })
- // Not logging the first post. There's always going to be one created alongside the discussion.
- ->listen(PostEvent\Posted::class, 'post.created', function ($e) {
- return $e->post->number === 1 ? null : ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
- }),
-
- (new Audit())
- ->group(null)
- // permission_changed and password_reset_attempted are logged from middleware;
- // setting_changed and the user.* actions come from the integrations below.
- ->register('permission_changed', 'setting_changed', 'user.password_reset_attempted')
- ->using(new Integration\CoreSettingIntegration())
- ->using(new Integration\CoreUserIntegration()),
-
- (new Audit())
- ->group(null)
- ->register('group.created', 'group.renamed', 'group.deleted')
- // Group CRUD is request-scoped, so the acting admin is the ambient logger actor
- // (the events themselves carry no actor — same as the discussion/post listeners above).
- ->listen(GroupEvent\Created::class, 'group.created', function ($e) {
- return ['group_id' => $e->group->id, 'name' => $e->group->name_singular];
- })
- ->listen(GroupEvent\Renamed::class, 'group.renamed', function ($e) {
- return [
- 'group_id' => $e->group->id,
- 'old_name' => $e->oldNameSingular,
- 'new_name' => $e->group->name_singular,
- ];
- })
- ->listen(GroupEvent\Deleted::class, 'group.deleted', function ($e) {
- return ['group_id' => $e->group->id, 'name' => $e->group->name_singular];
- }),
-
- (new Audit())
- ->group(null)
- ->register('developer_token_created')
- // Logs the issuance of a long-lived developer API token. The raw token value is
- // deliberately never logged — only the owner and the human-readable title.
- ->listen(DeveloperTokenCreated::class, 'developer_token_created', function ($e) {
- return ['user_id' => $e->token->user_id, 'title' => $e->token->title];
- }),
-
- (new Audit())
- ->group(null)
- ->register('settings_reset')
- ->listen(SettingsEvent\Reset::class, 'settings_reset', function ($e) {
- return ['extension' => $e->extensionId, 'keys' => $e->keys];
- }),
-
- // Search.
-
- (new Extend\SearchDriver(DatabaseSearchDriver::class))
- ->addSearcher(AuditLog::class, AuditSearcher::class)
- ->setFulltext(AuditSearcher::class, Search\FulltextFilter::class)
- ->addFilter(AuditSearcher::class, Search\Filter\ActionFilter::class)
- ->addFilter(AuditSearcher::class, Search\Filter\ActorFilter::class)
- ->addFilter(AuditSearcher::class, Search\Filter\ClientFilter::class)
- ->addFilter(AuditSearcher::class, Search\Filter\DiscussionFilter::class)
- ->addFilter(AuditSearcher::class, Search\Filter\IpFilter::class)
- ->addFilter(AuditSearcher::class, Search\Filter\UserFilter::class),
-
- (new Extend\Console())
- ->command(Console\ClearLogsCommand::class),
-
- new Extend\ApiResource(Api\Resource\AuditLogResource::class),
-
- (new Extend\ApiResource(ForumResource::class))
- ->fields(ForumAttributes::class),
-
- (new Extend\ServiceProvider())
- ->register(LoggerServiceProvider::class),
-
- (new Extend\ModelVisibility(AuditLog::class))
- ->scope(Scope\View::class),
-
- new LogSelfEnabled(),
- ],
- // Audit integrations for third-party extensions live in a separate file because they
- // reference classes from extensions outside the Flarum monorepo. See the file header.
- require __DIR__.'/extend.thirdparty.php'
-);
+return [
+ (new Extend\Frontend('forum'))
+ ->js(__DIR__.'/js/dist/forum.js')
+ ->jsDirectory(__DIR__.'/js/dist/forum')
+ ->css(__DIR__.'/less/forum.less'),
+
+ (new Extend\Frontend('common'))
+ ->jsDirectory(__DIR__.'/js/dist/common'),
+
+ (new Extend\Frontend('admin'))
+ ->js(__DIR__.'/js/dist/admin.js')
+ ->jsDirectory(__DIR__.'/js/dist/admin')
+ ->css(__DIR__.'/less/admin.less')
+ ->content(Content\AdminPayload::class),
+
+ new Extend\Locales(__DIR__.'/locale'),
+
+ (new Extend\Middleware('forum'))
+ ->add(Middleware\SetLoggerActor::class),
+ (new Extend\Middleware('admin'))
+ ->add(Middleware\SetLoggerActor::class),
+ (new Extend\Middleware('api'))
+ ->add(Middleware\SetLoggerActor::class)
+ ->add(Middleware\ExtendSetPermissionController::class)
+ ->add(Middleware\LogPasswordResetAttempt::class),
+
+ // Core integrations.
+
+ (new Audit())
+ ->group(null)
+ ->register('cache_cleared')
+ ->listen(ClearingCache::class, 'cache_cleared', function () {
+ return [];
+ }),
+
+ (new Audit())
+ ->group(null)
+ ->register('extension.disabled', 'extension.enabled', 'extension.uninstalled')
+ ->listen(ExtensionEvent\Disabled::class, 'extension.disabled', function ($e) {
+ return ['package' => $e->extension->name];
+ })
+ ->listen(ExtensionEvent\Enabled::class, 'extension.enabled', function ($e) {
+ return ['package' => $e->extension->name];
+ })
+ ->listen(ExtensionEvent\Uninstalled::class, 'extension.uninstalled', function ($e) {
+ return ['package' => $e->extension->name];
+ }),
+
+ (new Audit())
+ ->group(null)
+ ->register('discussion.created', 'discussion.deleted', 'discussion.hidden', 'discussion.renamed', 'discussion.restored')
+ ->listen(DiscussionEvent\Started::class, 'discussion.created', function ($e) {
+ return ['discussion_id' => $e->discussion->id];
+ })
+ ->listen(DiscussionEvent\Deleted::class, 'discussion.deleted', function ($e) {
+ return ['discussion_id' => $e->discussion->id];
+ })
+ ->listen(DiscussionEvent\Hidden::class, 'discussion.hidden', function ($e) {
+ return ['discussion_id' => $e->discussion->id];
+ })
+ ->listen(DiscussionEvent\Restored::class, 'discussion.restored', function ($e) {
+ return ['discussion_id' => $e->discussion->id];
+ })
+ ->listen(DiscussionEvent\Renamed::class, 'discussion.renamed', function ($e) {
+ return [
+ 'discussion_id' => $e->discussion->id,
+ 'old_title' => $e->oldTitle,
+ 'new_title' => $e->discussion->title,
+ ];
+ }),
+
+ (new Audit())
+ ->group(null)
+ ->register('post.created', 'post.deleted', 'post.hidden', 'post.restored', 'post.revised')
+ ->listen(PostEvent\Deleted::class, 'post.deleted', function ($e) {
+ return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
+ })
+ ->listen(PostEvent\Hidden::class, 'post.hidden', function ($e) {
+ return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
+ })
+ ->listen(PostEvent\Restored::class, 'post.restored', function ($e) {
+ return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
+ })
+ ->listen(PostEvent\Revised::class, 'post.revised', function ($e) {
+ return ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
+ })
+ // Not logging the first post. There's always going to be one created alongside the discussion.
+ ->listen(PostEvent\Posted::class, 'post.created', function ($e) {
+ return $e->post->number === 1 ? null : ['discussion_id' => $e->post->discussion->id, 'post_id' => $e->post->id];
+ }),
+
+ (new Audit())
+ ->group(null)
+ // permission_changed and password_reset_attempted are logged from middleware;
+ // setting_changed and the user.* actions come from the integrations below.
+ ->register('permission_changed', 'setting_changed', 'user.password_reset_attempted')
+ ->using(new Integration\CoreSettingIntegration())
+ ->using(new Integration\CoreUserIntegration()),
+
+ (new Audit())
+ ->group(null)
+ ->register('group.created', 'group.renamed', 'group.deleted')
+ // Group CRUD is request-scoped, so the acting admin is the ambient logger actor
+ // (the events themselves carry no actor — same as the discussion/post listeners above).
+ ->listen(GroupEvent\Created::class, 'group.created', function ($e) {
+ return ['group_id' => $e->group->id, 'name' => $e->group->name_singular];
+ })
+ ->listen(GroupEvent\Renamed::class, 'group.renamed', function ($e) {
+ return [
+ 'group_id' => $e->group->id,
+ 'old_name' => $e->oldNameSingular,
+ 'new_name' => $e->group->name_singular,
+ ];
+ })
+ ->listen(GroupEvent\Deleted::class, 'group.deleted', function ($e) {
+ return ['group_id' => $e->group->id, 'name' => $e->group->name_singular];
+ }),
+
+ (new Audit())
+ ->group(null)
+ ->register('developer_token_created')
+ // Logs the issuance of a long-lived developer API token. The raw token value is
+ // deliberately never logged — only the owner and the human-readable title.
+ ->listen(DeveloperTokenCreated::class, 'developer_token_created', function ($e) {
+ return ['user_id' => $e->token->user_id, 'title' => $e->token->title];
+ }),
+
+ (new Audit())
+ ->group(null)
+ ->register('settings_reset')
+ ->listen(SettingsEvent\Reset::class, 'settings_reset', function ($e) {
+ return ['extension' => $e->extensionId, 'keys' => $e->keys];
+ }),
+
+ // Search.
+
+ (new Extend\SearchDriver(DatabaseSearchDriver::class))
+ ->addSearcher(AuditLog::class, AuditSearcher::class)
+ ->setFulltext(AuditSearcher::class, Search\FulltextFilter::class)
+ ->addFilter(AuditSearcher::class, Search\Filter\ActionFilter::class)
+ ->addFilter(AuditSearcher::class, Search\Filter\ActorFilter::class)
+ ->addFilter(AuditSearcher::class, Search\Filter\ClientFilter::class)
+ ->addFilter(AuditSearcher::class, Search\Filter\DiscussionFilter::class)
+ ->addFilter(AuditSearcher::class, Search\Filter\IpFilter::class)
+ ->addFilter(AuditSearcher::class, Search\Filter\UserFilter::class),
+
+ (new Extend\Console())
+ ->command(Console\ClearLogsCommand::class),
+
+ new Extend\ApiResource(Api\Resource\AuditLogResource::class),
+
+ (new Extend\ApiResource(ForumResource::class))
+ ->fields(ForumAttributes::class),
+
+ (new Extend\ServiceProvider())
+ ->register(LoggerServiceProvider::class),
+
+ (new Extend\ModelVisibility(AuditLog::class))
+ ->scope(Scope\View::class),
+
+ new LogSelfEnabled(),
+];
diff --git a/extensions/audit/extend.thirdparty.php b/extensions/audit/extend.thirdparty.php
deleted file mode 100644
index 1d09e33e7f..0000000000
--- a/extensions/audit/extend.thirdparty.php
+++ /dev/null
@@ -1,130 +0,0 @@
-whenExtensionEnabled('flarum-audit', ...). Once an
- * extension adopts the public API, its block here can be removed.
- */
-return [
- (new Conditional())
- ->whenExtensionEnabled('fof-ban-ips', function () {
- return [
- (new Audit())
- ->group('fof-ban-ips')
- ->listen(BanIPsEvents\IPWasBanned::class, 'fof_ban_ips.banned', function ($e) {
- return array_filter([
- 'ip' => $e->bannedIP->address,
- 'reason' => $e->bannedIP->reason,
- 'user_id' => $e->bannedIP->user_id ?: null,
- ], function ($v) {
- return $v !== null;
- });
- })
- ->listen(BanIPsEvents\IPWasUnbanned::class, 'fof_ban_ips.unbanned', function ($e) {
- return array_filter([
- 'ip' => $e->unbannedIP->address,
- 'user_id' => $e->unbannedIP->user_id ?: null,
- ], function ($v) {
- return $v !== null;
- });
- }),
- ];
- })
- ->whenExtensionEnabled('fof-impersonate', function () {
- return [
- (new Audit())
- ->group('fof-impersonate')
- ->listen(Impersonated::class, 'user.impersonated', function ($e) {
- return [
- 'user_id' => $e->user->id,
- 'reason' => $e->switchReason ?: null,
- ];
- }),
- ];
- })
- ->whenExtensionEnabled('fof-merge-discussions', function () {
- return [
- (new Audit())
- ->group('fof-merge-discussions')
- ->register('discussion.merged_away', 'discussion.merged_into')
- ->using(function () {
- // Merge dispatches multiple logs per event, so it uses a raw listener.
- resolve('events')->listen(DiscussionWasMerged::class, function (DiscussionWasMerged $event) {
- foreach ($event->mergedDiscussions as $discussion) {
- AuditLogger::log('discussion.merged_away', [
- 'discussion_id' => $discussion->id,
- 'new_discussion_id' => $event->discussion->id,
- ]);
- }
-
- AuditLogger::log('discussion.merged_into', [
- 'discussion_id' => $event->discussion->id,
- 'original_discussion_ids' => $event->mergedDiscussions->pluck('id')->all(),
- 'post_count' => $event->posts->count(),
- ]);
- });
- }),
- ];
- })
- ->whenExtensionEnabled('fof-split', function () {
- return [
- (new Audit())
- ->group('fof-split')
- ->register('discussion.split_away', 'discussion.split_into')
- ->using(function () {
- resolve('events')->listen(DiscussionWasSplit::class, function (DiscussionWasSplit $event) {
- AuditLogger::log('discussion.split_away', [
- 'discussion_id' => $event->originalDiscussion->id,
- 'new_discussion_id' => $event->newDiscussion->id,
- 'post_count' => $event->posts->count(),
- ]);
- AuditLogger::log('discussion.split_into', [
- 'discussion_id' => $event->newDiscussion->id,
- 'original_discussion_id' => $event->originalDiscussion->id,
- 'post_count' => $event->posts->count(),
- ]);
- });
- }),
- ];
- })
- ->whenExtensionEnabled('fof-user-bio', function () {
- return [
- (new Audit())
- ->group('fof-user-bio')
- ->listen(BioChanged::class, 'user.bio_changed', function ($e) {
- return ['user_id' => $e->user->id];
- }),
- ];
- })
- ->whenExtensionEnabled('fof-username-request', function () {
- return [
- (new Audit())
- ->group('fof-username-request')
- ->using(new Integration\FoFUsernameRequestIntegration()),
- ];
- }),
-];
diff --git a/extensions/audit/locale/en.yml b/extensions/audit/locale/en.yml
index 4c2a111d06..b799e4b821 100644
--- a/extensions/audit/locale/en.yml
+++ b/extensions/audit/locale/en.yml
@@ -84,19 +84,12 @@ flarum-audit:
created: Started discussion {discussion}
deleted: Deleted discussion {discussion}
hidden: Hid discussion {discussion}
- merged_away: Merged {discussion} into {new_discussion}
- merged_into: Merged {post_count} posts from {original_discussion_ids_count} discussions into {discussion}
renamed: Renamed discussion from {old_title} to {new_title}
restored: Restored discussion {discussion}
- split_away: Split {post_count} posts from {discussion} into {new_discussion}
- split_into: Split {post_count} posts into new discussion {discussion}
extension:
disabled: Disabled extension {package}
enabled: Enabled extension {package}
uninstalled: Uninstalled extension {package}
- fof_ban_ips:
- banned: Banned ip {ip}
- unbanned: Unbanned ip {ip}
group:
created: Created group {name}
deleted: Deleted group {name}
@@ -116,25 +109,17 @@ flarum-audit:
activated_with_email: Activated {username}'s account via confirmation link
avatar_changed: Changed {username}'s avatar
avatar_removed: Removed {username}'s avatar
- bio_changed: Edited {username}'s bio
created: Created {username}'s account
deleted: Deleted {username}
email_changed: Changed {username}'s email from {old_email} to {new_email}
email_change_requested: Requested to change {username}'s email to {new_email}
groups_changed: Changed {username}'s groups from {old_groups} to {new_groups}
- impersonated: Impersonated {username}
logged_in: Logged in {username} account
logged_in_with_provider: Logged in {username} account using {provider}
logged_out: Logged out {username} account
- nickname_requested: Requested new nickname {new_nickname} for user {username}
- nickname_request_approved: Approved {username}'s nickname change from {old_nickname} to {new_nickname}
- nickname_request_rejected: Rejected {username}'s nickname change
password_changed: Changed {username}'s password
password_change_requested: Requested to change {username}'s password
password_reset_attempted: Requested a password reset for {username}
password_reset_attempted_unmatched: Requested a password reset for {email} (no matching account)
provider_connected: Connected provider {provider} to {username} account
username_changed: Changed {username}'s username from {old_username} to {new_username}
- username_requested: Requested new username {new_username} for user {username}
- username_request_approved: Approved {username}'s username change from {old_username} to {new_username}
- username_request_rejected: Rejected {username}'s username change
diff --git a/extensions/audit/src/Integration/FoFUsernameRequestIntegration.php b/extensions/audit/src/Integration/FoFUsernameRequestIntegration.php
deleted file mode 100644
index 2aad60f79f..0000000000
--- a/extensions/audit/src/Integration/FoFUsernameRequestIntegration.php
+++ /dev/null
@@ -1,112 +0,0 @@
-make(Dispatcher::class);
-
- $events->listen('eloquent.updated: '.User::class, [$this, 'userUpdated']);
- $events->listen('eloquent.saved: '.UsernameRequest::class, [$this, 'requestSaved']);
- }
-
- public function userUpdated(User $user): void
- {
- $this->oldNickname = $user->getOriginal('nickname');
- $this->oldUsername = $user->getOriginal('username');
- }
-
- public function requestSaved(UsernameRequest $request): void
- {
- switch ($request->status) {
- case 'Sent':
- if ($request->for_nickname) {
- AuditLogger::log('user.nickname_requested', [
- 'user_id' => $request->user_id,
- 'new_nickname' => $request->requested_username ?: null,
- ]);
- } else {
- AuditLogger::log('user.username_requested', [
- 'user_id' => $request->user_id,
- 'new_username' => $request->requested_username,
- ]);
- }
- break;
- case 'Approved':
- if ($request->for_nickname) {
- AuditLogger::log('user.nickname_request_approved', [
- 'user_id' => $request->user_id,
- 'old_nickname' => $this->oldNickname ?: null,
- 'new_nickname' => $request->requested_username ?: null,
- ]);
- } else {
- AuditLogger::log('user.username_request_approved', [
- 'user_id' => $request->user_id,
- 'old_username' => $this->oldUsername,
- 'new_username' => $request->requested_username,
- ]);
- }
- break;
- case 'Rejected':
- if ($request->for_nickname) {
- AuditLogger::log('user.nickname_request_rejected', [
- 'user_id' => $request->user_id,
- 'new_nickname' => $request->requested_username ?: null,
- 'reason' => $request->reason,
- ]);
- } else {
- AuditLogger::log('user.username_request_rejected', [
- 'user_id' => $request->user_id,
- 'new_username' => $request->requested_username,
- 'reason' => $request->reason,
- ]);
- }
- break;
- }
- }
-}
diff --git a/extensions/audit/tests/integration/thirdparty/FoFBanIpsTest.php b/extensions/audit/tests/integration/thirdparty/FoFBanIpsTest.php
deleted file mode 100644
index ff4f750f5f..0000000000
--- a/extensions/audit/tests/integration/thirdparty/FoFBanIpsTest.php
+++ /dev/null
@@ -1,102 +0,0 @@
-extension('fof-ban-ips');
-
- $this->prepareDatabase([
- User::class => [
- [
- 'id' => 3,
- 'username' => 'user3',
- 'email' => 'user3@example.com',
- ],
- ],
- ]);
- }
-
- #[Test]
- public function banned()
- {
- $this->sendSuccessfulRequest('POST', '/api/fof/ban-ips', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'address' => '192.168.2.2',
- 'reason' => 'Because',
- ],
- ],
- ],
- ], 201);
-
- $this->assertLogExists('fof_ban_ips.banned', [
- 'ip' => '192.168.2.2',
- 'reason' => 'Because',
- ]);
- }
-
- #[Test]
- public function banned_user()
- {
- $this->sendSuccessfulRequest('POST', '/api/fof/ban-ips', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'userId' => 3,
- 'address' => '192.168.2.3',
- 'reason' => 'Because',
- ],
- ],
- ],
- ], 201);
-
- $this->assertLogExists('fof_ban_ips.banned', [
- 'ip' => '192.168.2.3',
- 'reason' => 'Because',
- 'user_id' => 3,
- ]);
- }
-
- // We can't test unbanned without user because the event is not dispatched
- // https://github.com/FriendsOfFlarum/ban-ips/issues/4
-
- #[Test]
- public function unbanned_user()
- {
- $this->sendSuccessfulRequest('POST', '/api/fof/ban-ips', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'userId' => 3,
- 'address' => '192.168.2.4',
- 'reason' => 'Because',
- ],
- ],
- ],
- ], 201);
-
- $this->sendSuccessfulRequest('POST', '/api/users/3/unban', []);
-
- $this->assertLogExists('fof_ban_ips.unbanned', [
- 'ip' => '192.168.2.4',
- 'user_id' => 3,
- ]);
- }
-}
diff --git a/extensions/audit/tests/integration/thirdparty/FoFImpersonateTest.php b/extensions/audit/tests/integration/thirdparty/FoFImpersonateTest.php
deleted file mode 100644
index 5dedd62db7..0000000000
--- a/extensions/audit/tests/integration/thirdparty/FoFImpersonateTest.php
+++ /dev/null
@@ -1,65 +0,0 @@
-extension('fof-impersonate');
-
- $this->prepareDatabase([
- User::class => [
- [
- 'id' => 3,
- 'username' => 'user3',
- 'email' => 'user3@example.com',
- ],
- ],
- ]);
- }
-
- #[Test]
- public function impersonate()
- {
- $adminSession = $this->sendForumCsrfRequest('POST', '/login', [
- 'json' => [
- 'identification' => 'admin',
- 'password' => 'password',
- ],
- ]);
-
- // We can't use authenticateAs because Impersonate only works with sessions and not access tokens
- $response = $this->send($this->request('POST', '/api/impersonate', [
- 'cookiesFrom' => $adminSession,
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'userId' => 3,
- 'reason' => 'because', // Currently not optional, it would result in undefined index if not included
- ],
- ],
- ],
- ])->withAddedHeader('X-CSRF-Token', $adminSession->getHeaderLine('X-CSRF-Token')));
-
- $this->assertEquals(200, $response->getStatusCode());
-
- $this->assertLogExists('user.impersonated', [
- 'user_id' => 3,
- 'reason' => 'because',
- ]);
- }
-}
diff --git a/extensions/audit/tests/integration/thirdparty/FoFMergeTest.php b/extensions/audit/tests/integration/thirdparty/FoFMergeTest.php
deleted file mode 100644
index 6cf269e3c5..0000000000
--- a/extensions/audit/tests/integration/thirdparty/FoFMergeTest.php
+++ /dev/null
@@ -1,89 +0,0 @@
-extension('fof-merge-discussions');
-
- $date = Carbon::parse('2021-01-01T12:00:00+00:00');
-
- $this->prepareDatabase([
- Discussion::class => [
- ['id' => 10, 'title' => 'A', 'created_at' => $date, 'last_posted_at' => $date, 'first_post_id' => 1, 'comment_count' => 1],
- ['id' => 11, 'title' => 'B', 'created_at' => $date, 'last_posted_at' => $date, 'first_post_id' => 2, 'comment_count' => 1],
- ['id' => 12, 'title' => 'C', 'created_at' => $date, 'last_posted_at' => $date, 'first_post_id' => 3, 'comment_count' => 2],
- ],
- Post::class => [
- ['id' => 1, 'number' => 1, 'discussion_id' => 10, 'created_at' => $date, 'type' => 'comment', 'content' => 'A
'],
- ['id' => 2, 'number' => 1, 'discussion_id' => 11, 'created_at' => $date, 'type' => 'comment', 'content' => 'B
'],
- ['id' => 3, 'number' => 1, 'discussion_id' => 12, 'created_at' => $date, 'type' => 'comment', 'content' => 'C1
'],
- ['id' => 4, 'number' => 2, 'discussion_id' => 12, 'created_at' => $date, 'type' => 'comment', 'content' => 'C2
'],
- ],
- ]);
- }
-
- #[Test]
- public function mergeSingle()
- {
- $this->sendSuccessfulRequest('POST', '/api/discussions/10/merge', [
- 'json' => [
- 'ids' => '11',
- ],
- ]);
-
- $this->assertLogExists('discussion.merged_into', [
- 'discussion_id' => 10,
- 'original_discussion_ids' => [11],
- 'post_count' => 1,
- ]);
-
- $this->assertLogExists('discussion.merged_away', [
- 'discussion_id' => 11,
- 'new_discussion_id' => 10,
- ]);
- }
-
- #[Test]
- public function mergeMultiple()
- {
- $this->sendSuccessfulRequest('POST', '/api/discussions/10/merge', [
- 'json' => [
- 'ids' => ['11', '12'],
- ],
- ]);
-
- $this->assertLogExists('discussion.merged_into', [
- 'discussion_id' => 10,
- 'original_discussion_ids' => [11, 12],
- 'post_count' => 3,
- ]);
-
- $this->assertLogExists('discussion.merged_away', [
- 'discussion_id' => 11,
- 'new_discussion_id' => 10,
- ]);
-
- $this->assertLogExists('discussion.merged_away', [
- 'discussion_id' => 12,
- 'new_discussion_id' => 10,
- ], 1, 1);
- }
-}
diff --git a/extensions/audit/tests/integration/thirdparty/FoFSplitTest.php b/extensions/audit/tests/integration/thirdparty/FoFSplitTest.php
deleted file mode 100644
index 7cae7d1c38..0000000000
--- a/extensions/audit/tests/integration/thirdparty/FoFSplitTest.php
+++ /dev/null
@@ -1,68 +0,0 @@
-extension('fof-split');
-
- $date = Carbon::parse('2021-01-01T12:00:00+00:00');
-
- $this->prepareDatabase([
- Discussion::class => [
- ['id' => 10, 'title' => 'A', 'created_at' => $date, 'last_posted_at' => $date, 'first_post_id' => 1, 'comment_count' => 4],
- ],
- Post::class => [
- ['id' => 1, 'number' => 1, 'discussion_id' => 10, 'created_at' => $date, 'type' => 'comment', 'content' => 'A
'],
- ['id' => 2, 'number' => 2, 'discussion_id' => 10, 'created_at' => $date, 'type' => 'comment', 'content' => 'B
', 'user_id' => 1],
- ['id' => 3, 'number' => 3, 'discussion_id' => 10, 'created_at' => $date, 'type' => 'comment', 'content' => 'C
'],
- ['id' => 4, 'number' => 4, 'discussion_id' => 10, 'created_at' => $date, 'type' => 'comment', 'content' => 'D
'],
- ],
- ]);
- }
-
- #[Test]
- public function split()
- {
- $response = $this->sendSuccessfulRequest('POST', '/api/split', [
- 'json' => [
- 'title' => 'Split',
- 'start_post_id' => 2,
- 'end_post_number' => 3,
- ],
- ]);
-
- $body = json_decode($response->getBody()->getContents(), true);
- $newDiscussionId = Arr::get($body, 'data.id');
-
- $this->assertLogExists('discussion.split_away', [
- 'discussion_id' => 10,
- 'new_discussion_id' => $newDiscussionId,
- 'post_count' => 2,
- ]);
-
- $this->assertLogExists('discussion.split_into', [
- 'discussion_id' => $newDiscussionId,
- 'original_discussion_id' => 10,
- 'post_count' => 2,
- ]);
- }
-}
diff --git a/extensions/audit/tests/integration/thirdparty/FoFUserBioTest.php b/extensions/audit/tests/integration/thirdparty/FoFUserBioTest.php
deleted file mode 100644
index 56e6b2ec5e..0000000000
--- a/extensions/audit/tests/integration/thirdparty/FoFUserBioTest.php
+++ /dev/null
@@ -1,52 +0,0 @@
-extension('fof-user-bio');
-
- $this->prepareDatabase([
- User::class => [
- [
- 'id' => 3,
- 'username' => 'user3',
- 'email' => 'user3@example.com',
- ],
- ],
- ]);
- }
-
- #[Test]
- public function update()
- {
- $this->sendSuccessfulRequest('PATCH', '/api/users/3', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'bio' => 'Hello World',
- ],
- ],
- ],
- ]);
-
- $this->assertLogExists('user.bio_changed', [
- 'user_id' => 3,
- ]);
- }
-}
diff --git a/extensions/audit/tests/integration/thirdparty/FoFUsernameRequestTest.php b/extensions/audit/tests/integration/thirdparty/FoFUsernameRequestTest.php
deleted file mode 100644
index c3a127182f..0000000000
--- a/extensions/audit/tests/integration/thirdparty/FoFUsernameRequestTest.php
+++ /dev/null
@@ -1,169 +0,0 @@
-extension('fof-username-request', 'flarum-nicknames');
-
- $this->prepareDatabase([
- User::class => [
- [
- 'id' => 3,
- 'username' => 'user3',
- 'email' => 'user3@example.com',
- ],
- ],
- 'username_requests' => [
- ['id' => 1, 'user_id' => 3, 'requested_username' => 'user33', 'status' => 'Sent'],
- ['id' => 2, 'user_id' => 3, 'requested_username' => 'user33', 'status' => 'Sent', 'for_nickname' => true],
- ],
- ]);
- }
-
- #[Test]
- public function createUsername()
- {
- $this->sendSuccessfulRequest('POST', '/api/username-requests', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'username' => 'admin2',
- ],
- ],
- 'meta' => [
- 'password' => 'password',
- ],
- ],
- ], 201);
-
- $this->assertLogExists('user.username_requested', [
- 'user_id' => 1,
- 'new_username' => 'admin2',
- ]);
- }
-
- #[Test]
- public function createNickname()
- {
- $this->sendSuccessfulRequest('POST', '/api/username-requests', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'username' => 'admin2',
- 'forNickname' => true,
- ],
- ],
- 'meta' => [
- 'password' => 'password',
- ],
- ],
- ], 201);
-
- $this->assertLogExists('user.nickname_requested', [
- 'user_id' => 1,
- 'new_nickname' => 'admin2',
- ]);
- }
-
- #[Test]
- public function approveUsername()
- {
- $this->sendSuccessfulRequest('PATCH', '/api/username-requests/1', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'action' => 'Approved',
- ],
- ],
- ],
- ]);
-
- $this->assertLogExists('user.username_request_approved', [
- 'user_id' => 3,
- 'old_username' => 'user3',
- 'new_username' => 'user33',
- ]);
-
- $this->assertLogDoesntExist('user.username_changed');
- }
-
- #[Test]
- public function approveNickname()
- {
- $this->sendSuccessfulRequest('PATCH', '/api/username-requests/2', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'action' => 'Approved',
- ],
- ],
- ],
- ]);
-
- $this->assertLogExists('user.nickname_request_approved', [
- 'user_id' => 3,
- 'old_nickname' => null,
- 'new_nickname' => 'user33',
- ]);
-
- $this->assertLogDoesntExist('user.nickname_changed');
- }
-
- #[Test]
- public function rejectUsername()
- {
- $this->sendSuccessfulRequest('PATCH', '/api/username-requests/1', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'action' => 'Rejected',
- 'reason' => 'because',
- ],
- ],
- ],
- ]);
-
- $this->assertLogExists('user.username_request_rejected', [
- 'user_id' => 3,
- 'new_username' => 'user33',
- 'reason' => 'because',
- ]);
- }
-
- #[Test]
- public function rejectNickname()
- {
- $this->sendSuccessfulRequest('PATCH', '/api/username-requests/2', [
- 'json' => [
- 'data' => [
- 'attributes' => [
- 'action' => 'Rejected',
- 'reason' => 'because',
- ],
- ],
- ],
- ]);
-
- $this->assertLogExists('user.nickname_request_rejected', [
- 'user_id' => 3,
- 'new_nickname' => 'user33',
- 'reason' => 'because',
- ]);
- }
-}
diff --git a/extensions/audit/tests/phpunit.integration.xml b/extensions/audit/tests/phpunit.integration.xml
index fc308069e8..849ceb516e 100644
--- a/extensions/audit/tests/phpunit.integration.xml
+++ b/extensions/audit/tests/phpunit.integration.xml
@@ -19,8 +19,6 @@
./integration
./integration/tmp
-
- ./integration/thirdparty
diff --git a/extensions/audit/tests/unit/IntegrationActionRegistrationTest.php b/extensions/audit/tests/unit/IntegrationActionRegistrationTest.php
index ec266bc688..bae48246c5 100644
--- a/extensions/audit/tests/unit/IntegrationActionRegistrationTest.php
+++ b/extensions/audit/tests/unit/IntegrationActionRegistrationTest.php
@@ -12,7 +12,6 @@
use Flarum\Audit\Extend\Audit;
use Flarum\Audit\Integration\CoreSettingIntegration;
use Flarum\Audit\Integration\CoreUserIntegration;
-use Flarum\Audit\Integration\FoFUsernameRequestIntegration;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
@@ -86,13 +85,12 @@ public function register_and_listen_also_contribute_actions()
public static function integrations(): array
{
- // First-party extension integrations (flags, nicknames, tags) now live in their own
- // extensions and are covered by those extensions' AuditTest suites. Only the core and
- // bundled-thirdparty integrations that still ship with flarum/audit are exercised here.
+ // Third-party extension integrations now live in their own extensions and are covered
+ // by those extensions' AuditTest suites. Only the core integrations that ship with
+ // flarum/audit are exercised here.
return [
'core user' => [CoreUserIntegration::class],
'core setting' => [CoreSettingIntegration::class],
- 'fof username request' => [FoFUsernameRequestIntegration::class],
];
}
}
diff --git a/phpstan.neon b/phpstan.neon
index fdc8567a0d..c59e59e6df 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -43,7 +43,4 @@ parameters:
- extensions/gdpr/src
excludePaths:
- *.blade.php
- # Audit integrations for third-party extensions reference classes outside the monorepo.
- - extensions/audit/extend.thirdparty.php
- - extensions/audit/src/Integration/FoFUsernameRequestIntegration.php
databaseMigrationsPath: ['framework/core/migrations']