composer.json
{
"name": "advanced-audit/logger",
"description": "Enhanced auditing for Flarum actions.",
"type": "flarum-extension",
"require": {
"flarum/core": "1.8 || 2.0"
},
"extra": {
"flarum-extension": {
"title": "Advanced Audit Logger",
"icon": {
"name": "fas fa-clipboard-list",
"backgroundColor": "#2c3e50",
"color": "#fff"
}
}
},
"autoload": {
"psr-4": {
"AdvancedAudit\Logger\": "src/"
}
}
}
extend.php
<?php
use Flarum\Extend;
use Flarum\Audit\Extend\Audit;
use Flarum\Discussion\Event\Saving as DiscussionSaving;
use Flarum\Post\Event\Deleted as PostDeleted;
use AdvancedAudit\Logger\Listener\LogDiscussionChanges;
use AdvancedAudit\Logger\Listener\LogPostDeletion;
use AdvancedAudit\Logger\Blueprint\DiscussionTitleBlueprint;
use AdvancedAudit\Logger\Blueprint\PostDeletedBlueprint;
return [
(new Extend\Conditional())
->whenExtensionEnabled('flarum-audit', [
(new Audit())
->action('discussion.title_changed', DiscussionTitleBlueprint::class)
->action('post.deleted_permanently', PostDeletedBlueprint::class),
(new Extend\Event())
->listen(DiscussionSaving::class, LogDiscussionChanges::class)
->listen(PostDeleted::class, LogPostDeletion::class)
])
];
DiscussionTitleBlueprint.php
<?php
namespace AdvancedAudit\Logger\Blueprint;
use Flarum\Audit\Blueprint\Blueprint;
use Flarum\User\User;
class DiscussionTitleBlueprint implements Blueprint
{
protected $discussion;
protected $actor;
protected $oldTitle;
protected $newTitle;
public function __construct($discussion, User $actor, string $oldTitle, string $newTitle)
{
$this->discussion = $discussion;
$this->actor = $actor;
$this->oldTitle = $oldTitle;
$this->newTitle = $newTitle;
}
public function getType(): string { return 'discussion.title_changed'; }
public function getActor(): ?User { return $this->actor; }
public function getSubject(): ?object { return $this->discussion; }
public function getData(): array
{
return [
'old_title' => $this->oldTitle,
'new_title' => $this->newTitle,
];
}
}
PostDeletedBlueprint.php
<?php
namespace AdvancedAudit\Logger\Blueprint;
use Flarum\Audit\Blueprint\Blueprint;
use Flarum\User\User;
class PostDeletedBlueprint implements Blueprint
{
protected $post;
protected $actor;
protected $contentPreview;
public function __construct($post, User $actor, string $contentPreview)
{
$this->post = $post;
$this->actor = $actor;
$this->contentPreview = $contentPreview;
}
public function getType(): string { return 'post.deleted_permanently'; }
public function getActor(): ?User { return $this->actor; }
public function getSubject(): ?object { return $this->post; }
public function getData(): array
{
return [
'content_preview' => substr($this->contentPreview, 0, 150),
];
}
}
LogDiscussionChanges.php
<?php
namespace AdvancedAudit\Logger\Listener;
use Flarum\Discussion\Event\Saving;
use Flarum\Audit\AuditLogger;
use AdvancedAudit\Logger\Blueprint\DiscussionTitleBlueprint;
class LogDiscussionChanges
{
protected $logger;
public function __construct(AuditLogger $logger) { $this->logger = $logger; }
public function handle(Saving $event)
{
$discussion = $event->discussion;
if ($discussion->isDirty('title')) {
$this->logger->log(new DiscussionTitleBlueprint(
$discussion,
$event->actor,
$discussion->getOriginal('title'),
$discussion->title
));
}
}
}
LogPostDeletion.php
<?php
namespace AdvancedAudit\Logger\Listener;
use Flarum\Post\Event\Deleted;
use Flarum\Audit\AuditLogger;
use AdvancedAudit\Logger\Blueprint\PostDeletedBlueprint;
class LogPostDeletion
{
protected $logger;
public function __construct(AuditLogger $logger) { $this->logger = $logger; }
public function handle(Deleted $event)
{
$this->logger->log(new PostDeletedBlueprint(
$event->post,
$event->actor,
$event->post->content
));
}
}
js/src/admin/index.js
import app from 'flarum/admin/app';
app.initializers.add('advanced-audit-logger', () => {
// 1. Format the Discussion Title Change log layout
app.extensionData.for('flarum-audit').registerAction('discussion.title_changed', {
icon: 'fas fa-edit',
label: (log) => {
const actor = log.actor();
const discussion = log.subject();
return app.translator.trans('advanced-audit-logger.admin.logs.discussion_title_changed', {
user: actor ? actor.displayName() : 'System',
discussion: discussion ? discussion.title() : `#${log.subjectId()}`
});
},
// The details layout expanding under the main log item row
details: (log) => {
const data = log.data();
return (
<div className="AuditLog-details-diff">
<div className="diff-removed"><i className="fas fa-minus-circle"/> <strong>Old:</strong> "{data.old_title}"</div>
<div className="diff-added"><i className="fas fa-plus-circle"/> <strong>New:</strong> "{data.new_title}"</div>
</div>
);
}
});
// 2. Format the Permanent Post Deletion log layout
app.extensionData.for('flarum-audit').registerAction('post.deleted_permanently', {
icon: 'fas fa-trash-alt',
label: (log) => {
const actor = log.actor();
return app.translator.trans('advanced-audit-logger.admin.logs.post_deleted', {
user: actor ? actor.displayName() : 'System'
});
},
details: (log) => {
const data = log.data();
return (
<div className="AuditLog-details-preview">
<strong>Content Snippet:</strong> <em>"{data.content_preview}..."</em>
</div>
);
}
});
});
app.translator.trans
advanced-audit-logger:
admin:
logs:
discussion_title_changed: "{user} changed the title of discussion: {discussion}"
post_deleted: "{user} permanently deleted a post"