1
0
mirror of https://github.com/flarum/core.git synced 2025-07-18 15:21:16 +02:00

Rename to Flags, tweak flag controls

- Display post "destructiveControls" in flag instead of custom buttons
- Make flags more versatile/extensible
- Delete associated flags when a post is deleted

Uninstall the Reports extension before installing.
This commit is contained in:
Toby Zerner
2015-09-22 18:14:33 +09:30
parent 8da417c5b5
commit a1e01938ef
36 changed files with 390 additions and 401 deletions

View File

@@ -11,4 +11,4 @@
require __DIR__.'/vendor/autoload.php'; require __DIR__.'/vendor/autoload.php';
return 'Flarum\Reports\Extension'; return 'Flarum\Flags\Extension';

View File

@@ -1,7 +1,7 @@
{ {
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Flarum\\Reports\\": "src/" "Flarum\\Flags\\": "src/"
} }
}, },
"scripts": { "scripts": {

View File

@@ -1,7 +1,7 @@
{ {
"name": "reports", "name": "flags",
"title": "Reports", "title": "Flags",
"description": "Allow users to report posts for moderator review.", "description": "Allow users to flag posts for moderator review.",
"keywords": [], "keywords": [],
"version": "0.1.0-beta.2", "version": "0.1.0-beta.2",
"author": { "author": {
@@ -14,7 +14,7 @@
"flarum": ">=0.1.0-beta.2" "flarum": ">=0.1.0-beta.2"
}, },
"support": { "support": {
"source": "https://github.com/flarum/reports", "source": "https://github.com/flarum/flags",
"issues": "https://github.com/flarum/core/issues" "issues": "https://github.com/flarum/core/issues"
}, },
"icon": { "icon": {

View File

@@ -2,6 +2,6 @@ var gulp = require('flarum-gulp');
gulp({ gulp({
modules: { modules: {
'reports': 'src/**/*.js' 'flags': 'src/**/*.js'
} }
}); });

View File

@@ -2,18 +2,20 @@ import { extend } from 'flarum/extend';
import app from 'flarum/app'; import app from 'flarum/app';
import PermissionGrid from 'flarum/components/PermissionGrid'; import PermissionGrid from 'flarum/components/PermissionGrid';
app.initializers.add('reports', () => { app.initializers.add('flags', () => {
extend(PermissionGrid.prototype, 'moderateItems', items => { extend(PermissionGrid.prototype, 'moderateItems', items => {
items.add('viewReports', { items.add('viewFlags', {
label: 'View reported posts', icon: 'flag',
permission: 'discussion.viewReports' label: 'View flagged posts',
}); permission: 'discussion.viewFlags'
}, 65);
}); });
extend(PermissionGrid.prototype, 'replyItems', items => { extend(PermissionGrid.prototype, 'replyItems', items => {
items.add('reportPosts', { items.add('flagPosts', {
label: 'Report posts', icon: 'flag',
permission: 'discussion.reportPosts' label: 'Flag posts',
}); permission: 'discussion.flagPosts'
}, 70);
}); });
}); });

View File

@@ -2,6 +2,6 @@ var gulp = require('flarum-gulp');
gulp({ gulp({
modules: { modules: {
'reports': 'src/**/*.js' 'flags': 'src/**/*.js'
} }
}); });

View File

@@ -3,14 +3,14 @@ import app from 'flarum/app';
import PostControls from 'flarum/utils/PostControls'; import PostControls from 'flarum/utils/PostControls';
import Button from 'flarum/components/Button'; import Button from 'flarum/components/Button';
import ReportPostModal from 'reports/components/ReportPostModal'; import FlagPostModal from 'flags/components/FlagPostModal';
export default function() { export default function() {
extend(PostControls, 'userControls', function(items, post) { extend(PostControls, 'userControls', function(items, post) {
if (post.isHidden() || post.contentType() !== 'comment' || !post.canReport() || post.user() === app.session.user) return; if (post.isHidden() || post.contentType() !== 'comment' || !post.canFlag() || post.user() === app.session.user) return;
items.add('report', items.add('flag',
<Button icon="flag" onclick={() => app.modal.show(new ReportPostModal({post}))}>Report</Button> <Button icon="flag" onclick={() => app.modal.show(new FlagPostModal({post}))}>Flag</Button>
); );
}); });
} }

View File

@@ -1,12 +1,12 @@
import { extend } from 'flarum/extend'; import { extend } from 'flarum/extend';
import app from 'flarum/app'; import app from 'flarum/app';
import HeaderSecondary from 'flarum/components/HeaderSecondary'; import HeaderSecondary from 'flarum/components/HeaderSecondary';
import ReportsDropdown from 'reports/components/ReportsDropdown'; import FlagsDropdown from 'flags/components/FlagsDropdown';
export default function() { export default function() {
extend(HeaderSecondary.prototype, 'items', function(items) { extend(HeaderSecondary.prototype, 'items', function(items) {
if (app.forum.attribute('canViewReports')) { if (app.forum.attribute('canViewFlags')) {
items.add('reports', <ReportsDropdown/>, 15); items.add('flags', <FlagsDropdown/>, 15);
} }
}); });
} }

View File

@@ -0,0 +1,109 @@
import { extend } from 'flarum/extend';
import app from 'flarum/app';
import CommentPost from 'flarum/components/CommentPost';
import Button from 'flarum/components/Button';
import punctuate from 'flarum/helpers/punctuate';
import username from 'flarum/helpers/username';
import ItemList from 'flarum/utils/ItemList';
import PostControls from 'flarum/utils/PostControls';
export default function() {
extend(CommentPost.prototype, 'attrs', function(attrs) {
if (this.props.post.flags().length) {
attrs.className += ' Post--flagged';
}
});
CommentPost.prototype.dismissFlag = function(data) {
const post = this.props.post;
delete post.data.relationships.flags;
this.subtree.invalidate();
if (app.cache.flags) {
app.cache.flags.some((flag, i) => {
if (flag.post() === post) {
app.cache.flags.splice(i, 1);
if (app.cache.flagIndex === post) {
let next = app.cache.flags[i];
if (!next) next = app.cache.flags[0];
if (next) {
const nextPost = next.post();
app.cache.flagIndex = nextPost;
m.route(app.route.post(nextPost));
}
}
return true;
}
});
}
return app.request({
url: app.forum.attribute('apiUrl') + post.apiEndpoint() + '/flags',
method: 'DELETE',
data
});
};
CommentPost.prototype.flagActionItems = function() {
const items = new ItemList();
const controls = PostControls.destructiveControls(this.props.post);
Object.keys(controls).forEach(k => {
const props = controls[k].content.props;
props.className = 'Button';
extend(props, 'onclick', () => this.dismissFlag());
});
items.merge(controls);
items.add('dismiss', <Button className="Button Button--icon Button--link" icon="times" onclick={this.dismissFlag.bind(this)} title="Dismiss Flag"/>, -100);
return items;
};
extend(CommentPost.prototype, 'content', function(vdom) {
const post = this.props.post;
const flags = post.flags();
if (!flags.length) return;
if (post.isHidden()) this.revealContent = true;
vdom.unshift(
<div className="Post-flagged">
<div className="Post-flagged-flags">
{flags.map(flag =>
<div className="Post-flagged-flag">
{this.flagReason(flag)}
</div>
)}
</div>
<div className="Post-flagged-actions">
{this.flagActionItems().toArray()}
</div>
</div>
);
});
CommentPost.prototype.flagReason = function(flag) {
if (flag.type() === 'user') {
const user = flag.user();
const reason = flag.reason();
const detail = flag.reasonDetail();
return [
app.trans(reason ? 'flags.flagged_by_with_reason' : 'flags.flagged_by', {user, reason}),
detail ? <span className="Post-flagged-detail">{detail}</span> : ''
];
}
};
}

View File

@@ -1,134 +0,0 @@
import { extend } from 'flarum/extend';
import app from 'flarum/app';
import CommentPost from 'flarum/components/CommentPost';
import Button from 'flarum/components/Button';
import punctuate from 'flarum/helpers/punctuate';
import username from 'flarum/helpers/username';
import ItemList from 'flarum/utils/ItemList';
import PostControls from 'flarum/utils/PostControls';
export default function() {
extend(CommentPost.prototype, 'attrs', function(attrs) {
if (this.props.post.reports().length) {
attrs.className += ' Post--reported';
}
});
CommentPost.prototype.dismissReport = function(data) {
const post = this.props.post;
delete post.data.relationships.reports;
this.subtree.invalidate();
if (app.cache.reports) {
app.cache.reports.some((report, i) => {
if (report.post() === post) {
app.cache.reports.splice(i, 1);
if (app.cache.reportIndex === post) {
let next = app.cache.reports[i];
if (!next) next = app.cache.reports[0];
if (next) {
const nextPost = next.post();
app.cache.reportIndex = nextPost;
m.route(app.route.post(nextPost));
}
}
return true;
}
});
}
return app.request({
url: app.forum.attribute('apiUrl') + post.apiEndpoint() + '/reports',
method: 'DELETE',
data
});
};
CommentPost.prototype.reportActionItems = function() {
const items = new ItemList();
if (this.props.post.isHidden()) {
if (this.props.post.canDelete()) {
items.add('delete',
<Button className="Button"
icon="trash-o"
onclick={() => {
this.dismissReport().then(() => {
PostControls.deleteAction.apply(this.props.post);
m.redraw();
});
}}>
Delete Forever
</Button>,
100
);
}
} else {
items.add('hide',
<Button className="Button"
icon="trash-o"
onclick={() => {
this.dismissReport().then(() => {
PostControls.hideAction.apply(this.props.post);
m.redraw();
});
}}>
Delete Post
</Button>,
100
);
}
items.add('dismiss', <Button className="Button Button--icon Button--link" icon="times" onclick={this.dismissReport.bind(this)}>Dismiss Report</Button>, -100);
return items;
};
extend(CommentPost.prototype, 'content', function(vdom) {
const post = this.props.post;
const reports = post.reports();
if (!reports.length) return;
if (post.isHidden()) this.revealContent = true;
const users = reports.map(report => {
const user = report.user();
return user
? <a href={app.route.user(user)} config={m.route}>{username(user)}</a>
: report.reporter();
});
const usedReasons = [];
const reasons = reports.map(report => report.reason()).filter(reason => {
if (reason && usedReasons.indexOf(reason) === -1) {
usedReasons.push(reason);
return true;
}
});
const details = reports.map(report => report.reasonDetail()).filter(detail => detail);
vdom.unshift(
<div className="Post-reported">
<div className="Post-reported-summary">
{app.trans(reasons.length ? 'reports.reported_by_with_reason' : 'reports.reported_by', {
reasons: punctuate(reasons.map(reason => app.trans('reports.reason_' + reason, undefined, reason))),
users: punctuate(users)
})}
{details.map(detail => <div className="Post-reported-detail">{detail}</div>)}
</div>
<div className="Post-reported-actions">
{this.reportActionItems().toArray()}
</div>
</div>
);
});
}

View File

@@ -5,7 +5,7 @@ import username from 'flarum/helpers/username';
import icon from 'flarum/helpers/icon'; import icon from 'flarum/helpers/icon';
import humanTime from 'flarum/helpers/humanTime'; import humanTime from 'flarum/helpers/humanTime';
export default class ReportList extends Component { export default class FlagList extends Component {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
@@ -18,32 +18,32 @@ export default class ReportList extends Component {
} }
view() { view() {
const reports = app.cache.reports || []; const flags = app.cache.flags || [];
return ( return (
<div className="NotificationList ReportList"> <div className="NotificationList FlagList">
<div className="NotificationList-header"> <div className="NotificationList-header">
<h4 className="App-titleControl App-titleControl--text">Reported Posts</h4> <h4 className="App-titleControl App-titleControl--text">Flagged Posts</h4>
</div> </div>
<div className="NotificationList-content"> <div className="NotificationList-content">
<ul className="NotificationGroup-content"> <ul className="NotificationGroup-content">
{reports.length {flags.length
? reports.map(report => { ? flags.map(flag => {
const post = report.post(); const post = flag.post();
return ( return (
<li> <li>
<a href={app.route.post(post)} className="Notification Report" config={function(element, isInitialized) { <a href={app.route.post(post)} className="Notification Flag" config={function(element, isInitialized) {
m.route.apply(this, arguments); m.route.apply(this, arguments);
if (!isInitialized) $(element).on('click', () => app.cache.reportIndex = post); if (!isInitialized) $(element).on('click', () => app.cache.flagIndex = post);
}}> }}>
{avatar(post.user())} {avatar(post.user())}
{icon('flag', {className: 'Notification-icon'})} {icon('flag', {className: 'Notification-icon'})}
<span className="Notification-content"> <span className="Notification-content">
{username(post.user())} in <em>{post.discussion().title()}</em> {username(post.user())} in <em>{post.discussion().title()}</em>
</span> </span>
{humanTime(report.time())} {humanTime(flag.time())}
<div className="Notification-excerpt"> <div className="Notification-excerpt">
{post.contentPlain()} {post.contentPlain()}
</div> </div>
@@ -52,7 +52,7 @@ export default class ReportList extends Component {
); );
}) })
: !this.loading : !this.loading
? <div className="NotificationList-empty">{app.trans('reports.no_reports')}</div> ? <div className="NotificationList-empty">{app.trans('flags.no_flags')}</div>
: LoadingIndicator.component({className: 'LoadingIndicator--block'})} : LoadingIndicator.component({className: 'LoadingIndicator--block'})}
</ul> </ul>
</div> </div>
@@ -61,20 +61,20 @@ export default class ReportList extends Component {
} }
/** /**
* Load reports into the application's cache if they haven't already * Load flags into the application's cache if they haven't already
* been loaded. * been loaded.
*/ */
load() { load() {
if (app.cache.reports && !app.forum.attribute('unreadReportsCount')) { if (app.cache.flags && !app.forum.attribute('unreadFlagsCount')) {
return; return;
} }
this.loading = true; this.loading = true;
m.redraw(); m.redraw();
app.store.find('reports').then(reports => { app.store.find('flags').then(flags => {
app.forum.pushAttributes({unreadReportsCount: 0}); app.forum.pushAttributes({unreadFlagsCount: 0});
app.cache.reports = reports.sort((a, b) => b.time() - a.time()); app.cache.flags = flags.sort((a, b) => b.time() - a.time());
this.loading = false; this.loading = false;
m.redraw(); m.redraw();

View File

@@ -1,7 +1,7 @@
import Modal from 'flarum/components/Modal'; import Modal from 'flarum/components/Modal';
import Button from 'flarum/components/Button'; import Button from 'flarum/components/Button';
export default class ReportPostModal extends Modal { export default class FlagPostModal extends Modal {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
@@ -10,11 +10,11 @@ export default class ReportPostModal extends Modal {
} }
className() { className() {
return 'ReportPostModal Modal--small'; return 'FlagPostModal Modal--small';
} }
title() { title() {
return 'Report Post'; return 'Flag Post';
} }
content() { content() {
@@ -55,7 +55,7 @@ export default class ReportPostModal extends Modal {
type="submit" type="submit"
loading={this.loading} loading={this.loading}
disabled={!this.reason()}> disabled={!this.reason()}>
Report Post Flag Post
</Button> </Button>
</div> </div>
</div> </div>
@@ -68,7 +68,7 @@ export default class ReportPostModal extends Modal {
this.loading = true; this.loading = true;
app.store.createRecord('reports').save({ app.store.createRecord('flags').save({
reason: this.reason() === 'other' ? null : this.reason(), reason: this.reason() === 'other' ? null : this.reason(),
reasonDetail: this.reasonDetail(), reasonDetail: this.reasonDetail(),
relationships: { relationships: {

View File

@@ -0,0 +1,26 @@
import NotificationsDropdown from 'flarum/components/NotificationsDropdown';
import FlagList from 'flags/components/FlagList';
export default class FlagsDropdown extends NotificationsDropdown {
static initProps(props) {
props.label = props.label || 'Flagged Posts';
props.icon = props.icon || 'flag';
super.initProps(props);
}
constructor(...args) {
super(...args);
this.list = new FlagList();
}
goToRoute() {
m.route(app.route('flags'));
}
getUnreadCount() {
return app.forum.attribute('unreadFlagsCount');
}
}

View File

@@ -0,0 +1,24 @@
import Page from 'flarum/components/Page';
import FlagList from 'flags/components/FlagList';
/**
* The `FlagsPage` component shows the flags list. It is only
* used on mobile devices where the flags dropdown is within the drawer.
*/
export default class FlagsPage extends Page {
constructor(...args) {
super(...args);
app.history.push('flags');
this.list = new FlagList();
this.list.load();
this.bodyClass = 'App--flags';
}
view() {
return <div className="FlagsPage">{this.list.render()}</div>;
}
}

View File

@@ -1,26 +0,0 @@
import NotificationsDropdown from 'flarum/components/NotificationsDropdown';
import ReportList from 'reports/components/ReportList';
export default class ReportsDropdown extends NotificationsDropdown {
static initProps(props) {
props.label = props.label || 'Reports';
props.icon = props.icon || 'flag';
super.initProps(props);
}
constructor(...args) {
super(...args);
this.list = new ReportList();
}
goToRoute() {
m.route(app.route('reports'));
}
getUnreadCount() {
return app.forum.attribute('unreadReportsCount');
}
}

View File

@@ -1,24 +0,0 @@
import Page from 'flarum/components/Page';
import ReportList from 'reports/components/ReportList';
/**
* The `ReportsPage` component shows the reports list. It is only
* used on mobile devices where the reports dropdown is within the drawer.
*/
export default class ReportsPage extends Page {
constructor(...args) {
super(...args);
app.history.push('reports');
this.list = new ReportList();
this.list.load();
this.bodyClass = 'App--reports';
}
view() {
return <div className="ReportsPage">{this.list.render()}</div>;
}
}

View File

@@ -1,21 +1,21 @@
import app from 'flarum/app'; import app from 'flarum/app';
import Model from 'flarum/Model'; import Model from 'flarum/Model';
import Report from 'reports/models/Report'; import Flag from 'flags/models/Flag';
import ReportsPage from 'reports/components/ReportsPage'; import FlagsPage from 'flags/components/FlagsPage';
import addReportControl from 'reports/addReportControl'; import addFlagControl from 'flags/addFlagControl';
import addReportsDropdown from 'reports/addReportsDropdown'; import addFlagsDropdown from 'flags/addFlagsDropdown';
import addReportsToPosts from 'reports/addReportsToPosts'; import addFlagsToPosts from 'flags/addFlagsToPosts';
app.initializers.add('reports', () => { app.initializers.add('flags', () => {
app.store.models.posts.prototype.reports = Model.hasMany('reports'); app.store.models.posts.prototype.flags = Model.hasMany('flags');
app.store.models.posts.prototype.canReport = Model.attribute('canReport'); app.store.models.posts.prototype.canFlag = Model.attribute('canFlag');
app.store.models.reports = Report; app.store.models.flags = Flag;
app.routes.reports = {path: '/reports', component: <ReportsPage/>}; app.routes.flags = {path: '/flags', component: <FlagsPage/>};
addReportControl(); addFlagControl();
addReportsDropdown(); addFlagsDropdown();
addReportsToPosts(); addFlagsToPosts();
}); });

View File

@@ -1,8 +1,8 @@
import Model from 'flarum/Model'; import Model from 'flarum/Model';
import mixin from 'flarum/utils/mixin'; import mixin from 'flarum/utils/mixin';
export default class Report extends mixin(Model, { export default class Flag extends mixin(Model, {
reporter: Model.attribute('reporter'), type: Model.attribute('type'),
reason: Model.attribute('reason'), reason: Model.attribute('reason'),
reasonDetail: Model.attribute('reasonDetail'), reasonDetail: Model.attribute('reasonDetail'),
time: Model.attribute('time', Model.transformDate), time: Model.attribute('time', Model.transformDate),

View File

@@ -1,34 +1,32 @@
.Post--reported { .Post--flagged {
padding-top: 0 !important; padding-top: 0 !important;
border: 2px solid @primary-color; border: 2px solid @primary-color;
} }
.Post-header .item-reported { .Post-header .item-flagged {
display: block; display: block;
margin: 0; margin: 0;
} }
.Post-reported { .Post-flagged {
background: @primary-color; background: @primary-color;
margin-top: -2px; margin-top: -2px;
margin-bottom: 20px; margin-bottom: 20px;
margin-left: -22px; margin-left: -22px;
margin-right: -22px; margin-right: -22px;
@media @tablet-up {
margin-left: -22px - 85px;
text-align: right;
}
padding: 10px; padding: 10px;
border-radius: @border-radius @border-radius 0 0; border-radius: @border-radius @border-radius 0 0;
overflow: hidden; overflow: hidden;
.light-contents(@color: @body-bg; @control-color: @body-bg); .light-contents(@color: @body-bg; @control-color: @body-bg);
@media @tablet-up {
margin-left: -22px - 85px;
}
&, a { &, a {
color: @body-bg !important; color: @body-bg !important;
} }
} }
.Post-reported-summary { .Post-flagged-flags {
@media @tablet-up { @media @tablet-up {
float: left; float: left;
} }
@@ -38,16 +36,21 @@
text-align: left; text-align: left;
font-weight: bold; font-weight: bold;
} }
.Post-reported-detail { .Post-flagged-detail {
font-size: 12px; font-size: 12px;
margin-top: 5px; margin-left: 10px;
font-weight: normal; font-weight: normal;
} }
.Post-reported-actions .Button { .Post-flagged-actions {
@media @tablet-up {
float: right;
}
}
.Post-flagged-actions .Button {
margin-left: 5px; margin-left: 5px;
} }
.ReportsDropdown .Dropdown-toggle { .FlagsDropdown .Dropdown-toggle {
.Button-label, .Button-label,
.Button-caret { .Button-caret {
display: none; display: none;

View File

@@ -1,8 +1,8 @@
reports: flags:
reason_off_topic: Off-topic reason_off_topic: Off-topic
reason_spam: Spam reason_spam: Spam
reason_inappropriate: Inappropriate reason_inappropriate: Inappropriate
reason_other: Other reason_other: Other
reported_by: "Reported by {users}" flagged_by: "{username} flagged"
reported_by_with_reason: "Reported as {reasons} by {users}" flagged_by_with_reason: "{username} flagged as {reason}"
no_reports: No Reports no_flags: No Flags

View File

@@ -8,22 +8,22 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Migrations\Reports; namespace Flarum\Migrations\Flags;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Flarum\Migrations\Migration; use Flarum\Migrations\Migration;
class AddReportsReadTimeToUsersTable extends Migration class AddFlagsReadTimeToUsersTable extends Migration
{ {
public function up() public function up()
{ {
$this->schema->table('users', function (Blueprint $table) { $this->schema->table('users', function (Blueprint $table) {
$table->dateTime('reports_read_time')->nullable(); $table->dateTime('flags_read_time')->nullable();
}); });
} }
public function down() public function down()
{ {
$this->schema->drop('reports_read_time'); $this->schema->drop('flags_read_time');
} }
} }

View File

@@ -8,20 +8,20 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Migrations\Reports; namespace Flarum\Migrations\Flags;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Flarum\Migrations\Migration; use Flarum\Migrations\Migration;
class CreateReportsTable extends Migration class CreateFlagsTable extends Migration
{ {
public function up() public function up()
{ {
$this->schema->create('reports', function (Blueprint $table) { $this->schema->create('flags', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->integer('post_id')->unsigned(); $table->integer('post_id')->unsigned();
$table->integer('user_id')->unsigned(); $table->string('type');
$table->string('reporter')->nullable(); $table->integer('user_id')->unsigned()->nullable();
$table->string('reason')->nullable(); $table->string('reason')->nullable();
$table->string('reason_detail')->nullable(); $table->string('reason_detail')->nullable();
$table->dateTime('time'); $table->dateTime('time');
@@ -30,6 +30,6 @@ class CreateReportsTable extends Migration
public function down() public function down()
{ {
$this->schema->drop('reports'); $this->schema->drop('flags');
} }
} }

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Api; namespace Flarum\Flags\Api;
use Flarum\Reports\Commands\CreateReport; use Flarum\Flags\Commands\CreateFlag;
use Flarum\Api\Actions\CreateAction as BaseCreateAction; use Flarum\Api\Actions\CreateAction as BaseCreateAction;
use Flarum\Api\JsonApiRequest; use Flarum\Api\JsonApiRequest;
use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Contracts\Bus\Dispatcher;
@@ -25,14 +25,14 @@ class CreateAction extends BaseCreateAction
/** /**
* @inheritdoc * @inheritdoc
*/ */
public $serializer = 'Flarum\Reports\Api\ReportSerializer'; public $serializer = 'Flarum\Flags\Api\FlagSerializer';
/** /**
* @inheritdoc * @inheritdoc
*/ */
public $include = [ public $include = [
'post' => true, 'post' => true,
'post.reports' => true 'post.flags' => true
]; ];
/** /**
@@ -44,15 +44,15 @@ class CreateAction extends BaseCreateAction
} }
/** /**
* Create a report according to input from the API request. * Create a flag according to input from the API request.
* *
* @param JsonApiRequest $request * @param JsonApiRequest $request
* @return \Flarum\Reports\Report * @return \Flarum\Flags\Flag
*/ */
protected function create(JsonApiRequest $request) protected function create(JsonApiRequest $request)
{ {
return $this->bus->dispatch( return $this->bus->dispatch(
new CreateReport($request->actor, $request->get('data')) new CreateFlag($request->actor, $request->get('data'))
); );
} }
} }

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Api; namespace Flarum\Flags\Api;
use Flarum\Reports\Commands\DeleteReports; use Flarum\Flags\Commands\DeleteFlags;
use Flarum\Api\Actions\DeleteAction as BaseDeleteAction; use Flarum\Api\Actions\DeleteAction as BaseDeleteAction;
use Flarum\Api\Request; use Flarum\Api\Request;
use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Contracts\Bus\Dispatcher;
@@ -31,14 +31,14 @@ class DeleteAction extends BaseDeleteAction
} }
/** /**
* Delete reports for a post. * Delete flags for a post.
* *
* @param Request $request * @param Request $request
*/ */
protected function delete(Request $request) protected function delete(Request $request)
{ {
$this->bus->dispatch( $this->bus->dispatch(
new DeleteReports($request->get('id'), $request->actor, $request->all()) new DeleteFlags($request->get('id'), $request->actor, $request->all())
); );
} }
} }

View File

@@ -8,20 +8,20 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Api; namespace Flarum\Flags\Api;
use Flarum\Api\Serializers\Serializer; use Flarum\Api\Serializers\Serializer;
class ReportSerializer extends Serializer class FlagSerializer extends Serializer
{ {
protected $type = 'reports'; protected $type = 'flags';
protected function getDefaultAttributes($report) protected function getDefaultAttributes($flag)
{ {
return [ return [
'reporter' => $report->reporter, 'type' => $flag->type,
'reason' => $report->reason, 'reason' => $flag->reason,
'reasonDetail' => $report->reason_detail, 'reasonDetail' => $flag->reason_detail,
]; ];
} }

View File

@@ -8,11 +8,11 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Api; namespace Flarum\Flags\Api;
use Flarum\Api\Actions\SerializeCollectionAction; use Flarum\Api\Actions\SerializeCollectionAction;
use Flarum\Api\JsonApiRequest; use Flarum\Api\JsonApiRequest;
use Flarum\Reports\Report; use Flarum\Flags\Flag;
use Tobscure\JsonApi\Document; use Tobscure\JsonApi\Document;
class IndexAction extends SerializeCollectionAction class IndexAction extends SerializeCollectionAction
@@ -20,7 +20,7 @@ class IndexAction extends SerializeCollectionAction
/** /**
* @inheritdoc * @inheritdoc
*/ */
public $serializer = 'Flarum\Reports\Api\ReportSerializer'; public $serializer = 'Flarum\Flags\Api\FlagSerializer';
/** /**
* @inheritdoc * @inheritdoc
@@ -41,12 +41,12 @@ class IndexAction extends SerializeCollectionAction
{ {
$actor = $request->actor; $actor = $request->actor;
$actor->reports_read_time = time(); $actor->flags_read_time = time();
$actor->save(); $actor->save();
return Report::whereVisibleTo($actor) return Flag::whereVisibleTo($actor)
->with($request->include) ->with($request->include)
->latest('reports.time') ->latest('flags.time')
->groupBy('post_id') ->groupBy('post_id')
->get(); ->get();
} }

View File

@@ -8,11 +8,11 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Commands; namespace Flarum\Flags\Commands;
use Flarum\Core\Users\User; use Flarum\Core\Users\User;
class CreateReport class CreateFlag
{ {
/** /**
* The user performing the action. * The user performing the action.
@@ -22,7 +22,7 @@ class CreateReport
public $actor; public $actor;
/** /**
* The attributes of the new report. * The attributes of the new flag.
* *
* @var array * @var array
*/ */
@@ -30,7 +30,7 @@ class CreateReport
/** /**
* @param User $actor The user performing the action. * @param User $actor The user performing the action.
* @param array $data The attributes of the new report. * @param array $data The attributes of the new flag.
*/ */
public function __construct(User $actor, array $data) public function __construct(User $actor, array $data)
{ {

View File

@@ -8,14 +8,14 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Commands; namespace Flarum\Flags\Commands;
use Flarum\Reports\Report; use Flarum\Flags\Flag;
use Flarum\Core\Posts\PostRepository; use Flarum\Core\Posts\PostRepository;
use Flarum\Core\Posts\CommentPost; use Flarum\Core\Posts\CommentPost;
use Exception; use Exception;
class CreateReportHandler class CreateFlagHandler
{ {
private $posts; private $posts;
@@ -25,10 +25,10 @@ class CreateReportHandler
} }
/** /**
* @param CreateReport $command * @param CreateFlag $command
* @return Report * @return Flag
*/ */
public function handle(CreateReport $command) public function handle(CreateFlag $command)
{ {
$actor = $command->actor; $actor = $command->actor;
$data = $command->data; $data = $command->data;
@@ -41,23 +41,24 @@ class CreateReportHandler
throw new Exception; throw new Exception;
} }
$post->assertCan($actor, 'report'); $post->assertCan($actor, 'flag');
Report::unguard(); Flag::unguard();
$report = Report::firstOrNew([ $flag = Flag::firstOrNew([
'post_id' => $post->id, 'post_id' => $post->id,
'user_id' => $actor->id 'user_id' => $actor->id
]); ]);
$report->post_id = $post->id; $flag->post_id = $post->id;
$report->user_id = $actor->id; $flag->user_id = $actor->id;
$report->reason = array_get($data, 'attributes.reason'); $flag->type = 'user';
$report->reason_detail = array_get($data, 'attributes.reasonDetail'); $flag->reason = array_get($data, 'attributes.reason');
$report->time = time(); $flag->reason_detail = array_get($data, 'attributes.reasonDetail');
$flag->time = time();
$report->save(); $flag->save();
return $report; return $flag;
} }
} }

View File

@@ -8,15 +8,15 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Commands; namespace Flarum\Flags\Commands;
use Flarum\Reports\Report; use Flarum\Flags\Flag;
use Flarum\Core\Users\User; use Flarum\Core\Users\User;
class DeleteReports class DeleteFlags
{ {
/** /**
* The ID of the post to delete reports for. * The ID of the post to delete flags for.
* *
* @var int * @var int
*/ */
@@ -35,7 +35,7 @@ class DeleteReports
public $data; public $data;
/** /**
* @param int $postId The ID of the post to delete reports for. * @param int $postId The ID of the post to delete flags for.
* @param User $actor The user performing the action. * @param User $actor The user performing the action.
* @param array $data * @param array $data
*/ */

View File

@@ -8,13 +8,13 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Commands; namespace Flarum\Flags\Commands;
use Flarum\Reports\Report; use Flarum\Flags\Flag;
use Flarum\Core\Posts\PostRepository; use Flarum\Core\Posts\PostRepository;
use Flarum\Reports\Events\ReportsWillBeDeleted; use Flarum\Flags\Events\FlagsWillBeDeleted;
class DeleteReportsHandler class DeleteFlagsHandler
{ {
protected $posts; protected $posts;
@@ -24,21 +24,21 @@ class DeleteReportsHandler
} }
/** /**
* @param DeleteReport $command * @param DeleteFlag $command
* @return Report * @return Flag
* @throws \Flarum\Core\Exceptions\PermissionDeniedException * @throws \Flarum\Core\Exceptions\PermissionDeniedException
*/ */
public function handle(DeleteReports $command) public function handle(DeleteFlags $command)
{ {
$actor = $command->actor; $actor = $command->actor;
$post = $this->posts->findOrFail($command->postId, $actor); $post = $this->posts->findOrFail($command->postId, $actor);
$post->discussion->assertCan($actor, 'viewReports'); $post->discussion->assertCan($actor, 'viewFlags');
event(new ReportsWillBeDeleted($post, $actor, $command->data)); event(new FlagsWillBeDeleted($post, $actor, $command->data));
$post->reports()->delete(); $post->flags()->delete();
return $post; return $post;
} }

View File

@@ -9,12 +9,12 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Events; namespace Flarum\Flags\Events;
use Flarum\Core\Posts\Post; use Flarum\Core\Posts\Post;
use Flarum\Core\Users\User; use Flarum\Core\Users\User;
class ReportsWillBeDeleted class FlagsWillBeDeleted
{ {
/** /**
* @var Post * @var Post

View File

@@ -8,17 +8,18 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports; namespace Flarum\Flags;
use Flarum\Support\Extension as BaseExtension; use Flarum\Support\Extension as BaseExtension;
use Illuminate\Events\Dispatcher; use Illuminate\Events\Dispatcher;
use Flarum\Core\Posts\Post;
class Extension extends BaseExtension class Extension extends BaseExtension
{ {
public function listen(Dispatcher $events) public function listen(Dispatcher $events)
{ {
$events->subscribe('Flarum\Reports\Listeners\AddClientAssets'); $events->subscribe('Flarum\Flags\Listeners\AddClientAssets');
$events->subscribe('Flarum\Reports\Listeners\AddApiAttributes'); $events->subscribe('Flarum\Flags\Listeners\AddApiAttributes');
$events->subscribe('Flarum\Reports\Listeners\AddModelRelationship'); $events->subscribe('Flarum\Flags\Listeners\AddModelRelationship');
} }
} }

View File

@@ -8,16 +8,16 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports; namespace Flarum\Flags;
use Flarum\Core\Model; use Flarum\Core\Model;
use Flarum\Core\Support\VisibleScope; use Flarum\Core\Support\VisibleScope;
class Report extends Model class Flag extends Model
{ {
use VisibleScope; use VisibleScope;
protected $table = 'reports'; protected $table = 'flags';
protected $dates = ['time']; protected $dates = ['time'];

View File

@@ -8,7 +8,7 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Listeners; namespace Flarum\Flags\Listeners;
use Flarum\Events\ApiRelationship; use Flarum\Events\ApiRelationship;
use Flarum\Events\WillSerializeData; use Flarum\Events\WillSerializeData;
@@ -19,26 +19,26 @@ use Flarum\Api\Serializers\PostSerializer;
use Flarum\Api\Serializers\ForumSerializer; use Flarum\Api\Serializers\ForumSerializer;
use Flarum\Api\Actions\Posts; use Flarum\Api\Actions\Posts;
use Flarum\Api\Actions\Discussions; use Flarum\Api\Actions\Discussions;
use Flarum\Reports\Report; use Flarum\Flags\Flag;
use Flarum\Reports\Api\CreateAction as ReportsCreateAction; use Flarum\Flags\Api\CreateAction as FlagsCreateAction;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class AddApiAttributes class AddApiAttributes
{ {
public function subscribe($events) public function subscribe($events)
{ {
$events->listen(ApiRelationship::class, [$this, 'addReportsRelationship']); $events->listen(ApiRelationship::class, [$this, 'addFlagsRelationship']);
$events->listen(WillSerializeData::class, [$this, 'loadReportsRelationship']); $events->listen(WillSerializeData::class, [$this, 'loadFlagsRelationship']);
$events->listen(BuildApiAction::class, [$this, 'includeReportsRelationship']); $events->listen(BuildApiAction::class, [$this, 'includeFlagsRelationship']);
$events->listen(ApiAttributes::class, [$this, 'addAttributes']); $events->listen(ApiAttributes::class, [$this, 'addAttributes']);
$events->listen(RegisterApiRoutes::class, [$this, 'addRoutes']); $events->listen(RegisterApiRoutes::class, [$this, 'addRoutes']);
} }
public function loadReportsRelationship(WillSerializeData $event) public function loadFlagsRelationship(WillSerializeData $event)
{ {
// For any API action that allows the 'reports' relationship to be // For any API action that allows the 'flags' relationship to be
// included, we need to preload this relationship onto the data (Post // included, we need to preload this relationship onto the data (Post
// models) so that we can selectively expose only the reports that the // models) so that we can selectively expose only the flags that the
// user has permission to view. // user has permission to view.
if ($event->action instanceof Discussions\ShowAction) { if ($event->action instanceof Discussions\ShowAction) {
$discussion = $event->data; $discussion = $event->data;
@@ -53,9 +53,9 @@ class AddApiAttributes
$posts = [$event->data]; $posts = [$event->data];
} }
if ($event->action instanceof ReportsCreateAction) { if ($event->action instanceof FlagsCreateAction) {
$report = $event->data; $flag = $event->data;
$posts = [$report->post]; $posts = [$flag->post];
} }
if (isset($posts)) { if (isset($posts)) {
@@ -63,67 +63,67 @@ class AddApiAttributes
$postsWithPermission = []; $postsWithPermission = [];
foreach ($posts as $post) { foreach ($posts as $post) {
$post->setRelation('reports', null); $post->setRelation('flags', null);
if ($post->discussion->can($actor, 'viewReports')) { if ($post->discussion->can($actor, 'viewFlags')) {
$postsWithPermission[] = $post; $postsWithPermission[] = $post;
} }
} }
if (count($postsWithPermission)) { if (count($postsWithPermission)) {
(new Collection($postsWithPermission)) (new Collection($postsWithPermission))
->load('reports', 'reports.user'); ->load('flags', 'flags.user');
} }
} }
} }
public function addReportsRelationship(ApiRelationship $event) public function addFlagsRelationship(ApiRelationship $event)
{ {
if ($event->serializer instanceof PostSerializer && if ($event->serializer instanceof PostSerializer &&
$event->relationship === 'reports') { $event->relationship === 'flags') {
return $event->serializer->hasMany('Flarum\Reports\Api\ReportSerializer', 'reports'); return $event->serializer->hasMany('Flarum\Flags\Api\FlagSerializer', 'flags');
} }
} }
public function includeReportsRelationship(BuildApiAction $event) public function includeFlagsRelationship(BuildApiAction $event)
{ {
if ($event->action instanceof Discussions\ShowAction) { if ($event->action instanceof Discussions\ShowAction) {
$event->addInclude('posts.reports'); $event->addInclude('posts.flags');
$event->addInclude('posts.reports.user'); $event->addInclude('posts.flags.user');
} }
if ($event->action instanceof Posts\IndexAction || if ($event->action instanceof Posts\IndexAction ||
$event->action instanceof Posts\ShowAction) { $event->action instanceof Posts\ShowAction) {
$event->addInclude('reports'); $event->addInclude('flags');
$event->addInclude('reports.user'); $event->addInclude('flags.user');
} }
} }
public function addAttributes(ApiAttributes $event) public function addAttributes(ApiAttributes $event)
{ {
if ($event->serializer instanceof ForumSerializer) { if ($event->serializer instanceof ForumSerializer) {
$event->attributes['canViewReports'] = $event->actor->hasPermissionLike('discussion.viewReports'); $event->attributes['canViewFlags'] = $event->actor->hasPermissionLike('discussion.viewFlags');
if ($event->attributes['canViewReports']) { if ($event->attributes['canViewFlags']) {
$query = Report::whereVisibleTo($event->actor); $query = Flag::whereVisibleTo($event->actor);
if ($time = $event->actor->reports_read_time) { if ($time = $event->actor->flags_read_time) {
$query->where('reports.time', '>', $time); $query->where('flags.time', '>', $time);
} }
$event->attributes['unreadReportsCount'] = $query->distinct('reports.post_id')->count(); $event->attributes['unreadFlagsCount'] = $query->distinct('flags.post_id')->count();
} }
} }
if ($event->serializer instanceof PostSerializer) { if ($event->serializer instanceof PostSerializer) {
$event->attributes['canReport'] = $event->model->can($event->actor, 'report'); $event->attributes['canFlag'] = $event->model->can($event->actor, 'flag');
} }
} }
public function addRoutes(RegisterApiRoutes $event) public function addRoutes(RegisterApiRoutes $event)
{ {
$event->get('/reports', 'reports.index', 'Flarum\Reports\Api\IndexAction'); $event->get('/flags', 'flags.index', 'Flarum\Flags\Api\IndexAction');
$event->post('/reports', 'reports.create', 'Flarum\Reports\Api\CreateAction'); $event->post('/flags', 'flags.create', 'Flarum\Flags\Api\CreateAction');
$event->delete('/posts/{id}/reports', 'reports.delete', 'Flarum\Reports\Api\DeleteAction'); $event->delete('/posts/{id}/flags', 'flags.delete', 'Flarum\Flags\Api\DeleteAction');
} }
} }

View File

@@ -8,7 +8,7 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Listeners; namespace Flarum\Flags\Listeners;
use Flarum\Events\RegisterLocales; use Flarum\Events\RegisterLocales;
use Flarum\Events\BuildClientView; use Flarum\Events\BuildClientView;
@@ -34,16 +34,16 @@ class AddClientAssets
__DIR__.'/../../less/forum/extension.less' __DIR__.'/../../less/forum/extension.less'
]); ]);
$event->forumBootstrapper('reports/main'); $event->forumBootstrapper('flags/main');
$event->forumTranslations([ $event->forumTranslations([
'reports.reason_off_topic', 'flags.reason_off_topic',
'reports.reason_spam', 'flags.reason_spam',
'reports.reason_inappropriate', 'flags.reason_inappropriate',
'reports.reason_other', 'flags.reason_other',
'reports.reported_by', 'flags.flagged_by',
'reports.reported_by_with_reason', 'flags.flagged_by_with_reason',
'reports.no_reports' 'flags.no_flags'
]); ]);
$event->adminAssets([ $event->adminAssets([
@@ -51,10 +51,10 @@ class AddClientAssets
__DIR__.'/../../less/admin/extension.less' __DIR__.'/../../less/admin/extension.less'
]); ]);
$event->adminBootstrapper('reports/main'); $event->adminBootstrapper('flags/main');
$event->adminTranslations([ $event->adminTranslations([
// 'report.hello_world' // 'flag.hello_world'
]); ]);
} }
} }

View File

@@ -8,33 +8,40 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Flarum\Reports\Listeners; namespace Flarum\Flags\Listeners;
use Flarum\Events\ModelRelationship; use Flarum\Events\ModelRelationship;
use Flarum\Events\ModelDates; use Flarum\Events\ModelDates;
use Flarum\Events\PostWasDeleted;
use Flarum\Core\Posts\Post; use Flarum\Core\Posts\Post;
use Flarum\Core\Users\User; use Flarum\Core\Users\User;
use Flarum\Reports\Report; use Flarum\Flags\Flag;
class AddModelRelationship class AddModelRelationship
{ {
public function subscribe($events) public function subscribe($events)
{ {
$events->listen(ModelRelationship::class, [$this, 'addReportsRelationship']); $events->listen(ModelRelationship::class, [$this, 'addFlagsRelationship']);
$events->listen(ModelDates::class, [$this, 'modelDates']); $events->listen(ModelDates::class, [$this, 'modelDates']);
$events->listen(PostWasDeleted::class, [$this, 'deleteFlags']);
} }
public function addReportsRelationship(ModelRelationship $event) public function addFlagsRelationship(ModelRelationship $event)
{ {
if ($event->model instanceof Post && $event->relationship === 'reports') { if ($event->model instanceof Post && $event->relationship === 'flags') {
return $event->model->hasMany('Flarum\Reports\Report', 'post_id'); return $event->model->hasMany('Flarum\Flags\Flag', 'post_id');
} }
} }
public function modelDates(ModelDates $event) public function modelDates(ModelDates $event)
{ {
if ($event->model instanceof User) { if ($event->model instanceof User) {
$event->dates[] = 'reports_read_time'; $event->dates[] = 'flags_read_time';
} }
} }
public function deleteFlags(PostWasDeleted $event)
{
$event->post->flags()->delete();
}
} }