mirror of
https://github.com/flarum/framework.git
synced 2025-03-24 15:45:18 +08:00
Initial commit
This commit is contained in:
commit
cb6347ef6a
19
extensions/flags/.editorconfig
Normal file
19
extensions/flags/.editorconfig
Normal file
@ -0,0 +1,19 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{diff,md}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.php]
|
||||
indent_size = 4
|
5
extensions/flags/.eslintignore
Normal file
5
extensions/flags/.eslintignore
Normal file
@ -0,0 +1,5 @@
|
||||
**/bower_components/**/*
|
||||
**/node_modules/**/*
|
||||
vendor/**/*
|
||||
**/Gulpfile.js
|
||||
**/dist/**/*
|
175
extensions/flags/.eslintrc
Normal file
175
extensions/flags/.eslintrc
Normal file
@ -0,0 +1,175 @@
|
||||
{
|
||||
"parser": "babel-eslint", // https://github.com/babel/babel-eslint
|
||||
"env": { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments
|
||||
"browser": true // browser global variables
|
||||
},
|
||||
"ecmaFeatures": {
|
||||
"arrowFunctions": true,
|
||||
"blockBindings": true,
|
||||
"classes": true,
|
||||
"defaultParams": true,
|
||||
"destructuring": true,
|
||||
"forOf": true,
|
||||
"generators": false,
|
||||
"modules": true,
|
||||
"objectLiteralComputedProperties": true,
|
||||
"objectLiteralDuplicateProperties": false,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"spread": true,
|
||||
"superInFunctions": true,
|
||||
"templateStrings": true,
|
||||
"jsx": true
|
||||
},
|
||||
"globals": {
|
||||
"m": true,
|
||||
"app": true,
|
||||
"$": true,
|
||||
"moment": true
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-vars": 1,
|
||||
|
||||
/**
|
||||
* Strict mode
|
||||
*/
|
||||
// babel inserts "use strict"; for us
|
||||
"strict": [2, "never"], // http://eslint.org/docs/rules/strict
|
||||
|
||||
/**
|
||||
* ES6
|
||||
*/
|
||||
"no-var": 2, // http://eslint.org/docs/rules/no-var
|
||||
"prefer-const": 2, // http://eslint.org/docs/rules/prefer-const
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*/
|
||||
"no-shadow": 2, // http://eslint.org/docs/rules/no-shadow
|
||||
"no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
|
||||
"no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars
|
||||
"vars": "local",
|
||||
"args": "after-used"
|
||||
}],
|
||||
"no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
|
||||
|
||||
/**
|
||||
* Possible errors
|
||||
*/
|
||||
"comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle
|
||||
"no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign
|
||||
"no-console": 1, // http://eslint.org/docs/rules/no-console
|
||||
"no-debugger": 1, // http://eslint.org/docs/rules/no-debugger
|
||||
"no-alert": 1, // http://eslint.org/docs/rules/no-alert
|
||||
"no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition
|
||||
"no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
|
||||
"no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
|
||||
"no-empty": 2, // http://eslint.org/docs/rules/no-empty
|
||||
"no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
|
||||
"no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast
|
||||
"no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
|
||||
"no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
|
||||
"no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
|
||||
"no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
|
||||
"no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
|
||||
"no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
|
||||
"no-reserved-keys": 2, // http://eslint.org/docs/rules/no-reserved-keys
|
||||
"no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
|
||||
"no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
|
||||
"use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
|
||||
"block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
|
||||
|
||||
/**
|
||||
* Best practices
|
||||
*/
|
||||
"consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
|
||||
"curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly
|
||||
"default-case": 2, // http://eslint.org/docs/rules/default-case
|
||||
"dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation
|
||||
"allowKeywords": true
|
||||
}],
|
||||
"eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq
|
||||
"no-caller": 2, // http://eslint.org/docs/rules/no-caller
|
||||
"no-else-return": 2, // http://eslint.org/docs/rules/no-else-return
|
||||
"no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
|
||||
"no-eval": 2, // http://eslint.org/docs/rules/no-eval
|
||||
"no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
|
||||
"no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
|
||||
"no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
|
||||
"no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
|
||||
"no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
|
||||
"no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
|
||||
"no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
|
||||
"no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
|
||||
"no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
|
||||
"no-new": 2, // http://eslint.org/docs/rules/no-new
|
||||
"no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
|
||||
"no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
|
||||
"no-octal": 2, // http://eslint.org/docs/rules/no-octal
|
||||
"no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
|
||||
"no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign
|
||||
"no-proto": 2, // http://eslint.org/docs/rules/no-proto
|
||||
"no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
|
||||
"no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
|
||||
"no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
|
||||
"no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
|
||||
"no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
|
||||
"no-with": 2, // http://eslint.org/docs/rules/no-with
|
||||
"radix": 2, // http://eslint.org/docs/rules/radix
|
||||
"vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top
|
||||
"wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife
|
||||
"yoda": 2, // http://eslint.org/docs/rules/yoda
|
||||
|
||||
/**
|
||||
* Style
|
||||
*/
|
||||
"indent": [2, 2], // http://eslint.org/docs/rules/indent
|
||||
"brace-style": [2, // http://eslint.org/docs/rules/brace-style
|
||||
"1tbs", {
|
||||
"allowSingleLine": true
|
||||
}],
|
||||
"quotes": [
|
||||
2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes
|
||||
],
|
||||
"camelcase": [2, { // http://eslint.org/docs/rules/camelcase
|
||||
"properties": "never"
|
||||
}],
|
||||
"comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing
|
||||
"before": false,
|
||||
"after": true
|
||||
}],
|
||||
"comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style
|
||||
"eol-last": 2, // http://eslint.org/docs/rules/eol-last
|
||||
"key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}],
|
||||
"new-cap": [2, { // http://eslint.org/docs/rules/new-cap
|
||||
"newIsCap": true
|
||||
}],
|
||||
"no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines
|
||||
"max": 2
|
||||
}],
|
||||
"no-new-object": 2, // http://eslint.org/docs/rules/no-new-object
|
||||
"no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func
|
||||
"no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces
|
||||
"no-wrap-func": 2, // http://eslint.org/docs/rules/no-wrap-func
|
||||
"no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle
|
||||
"one-var": [2, "never"], // http://eslint.org/docs/rules/one-var
|
||||
"padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks
|
||||
"semi": [2, "always"], // http://eslint.org/docs/rules/semi
|
||||
"semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing
|
||||
"before": false,
|
||||
"after": true
|
||||
}],
|
||||
"space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords
|
||||
"space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks
|
||||
"space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren
|
||||
"space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops
|
||||
"space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case
|
||||
"spaced-line-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment
|
||||
}
|
||||
}
|
4
extensions/flags/.gitignore
vendored
Normal file
4
extensions/flags/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/vendor
|
||||
composer.phar
|
||||
.DS_Store
|
||||
Thumbs.db
|
26
extensions/flags/.php_cs
Executable file
26
extensions/flags/.php_cs
Executable file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
$header = <<<EOF
|
||||
This file is part of Flarum.
|
||||
|
||||
(c) Toby Zerner <toby.zerner@gmail.com>
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
EOF;
|
||||
|
||||
Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($header);
|
||||
|
||||
$finder = Symfony\CS\Finder\DefaultFinder::create()
|
||||
->exclude('js')
|
||||
->exclude('less')
|
||||
->in(__DIR__);
|
||||
|
||||
return Symfony\CS\Config\Config::create()
|
||||
->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
|
||||
->fixers([
|
||||
'short_array_syntax',
|
||||
'header_comment',
|
||||
'-psr0'
|
||||
])
|
||||
->finder($finder);
|
23
extensions/flags/.travis.yml
Normal file
23
extensions/flags/.travis.yml
Normal file
@ -0,0 +1,23 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.5
|
||||
- 5.6
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
fast_finish: true
|
||||
|
||||
before_script:
|
||||
- curl -s http://getcomposer.org/installer | php
|
||||
- php composer.phar install
|
||||
|
||||
script:
|
||||
- php composer.phar style
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_failure: change
|
||||
|
||||
sudo: false
|
21
extensions/flags/LICENSE
Normal file
21
extensions/flags/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2015 Toby Zerner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
14
extensions/flags/bootstrap.php
Normal file
14
extensions/flags/bootstrap.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
return 'Flarum\Reports\Extension';
|
10
extensions/flags/composer.json
Normal file
10
extensions/flags/composer.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Flarum\\Reports\\": "src/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"style": "phpcs --standard=PSR2 -np src"
|
||||
}
|
||||
}
|
25
extensions/flags/flarum.json
Normal file
25
extensions/flags/flarum.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "reports",
|
||||
"title": "Reports",
|
||||
"description": "Allow users to report posts for moderator review.",
|
||||
"keywords": [],
|
||||
"version": "0.1.0-beta.2",
|
||||
"author": {
|
||||
"name": "Toby Zerner",
|
||||
"email": "toby@flarum.org",
|
||||
"homepage": "http://tobyzerner.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"flarum": ">=0.1.0-beta.2"
|
||||
},
|
||||
"support": {
|
||||
"source": "https://github.com/flarum/reports",
|
||||
"issues": "https://github.com/flarum/core/issues"
|
||||
},
|
||||
"icon": {
|
||||
"name": "flag",
|
||||
"backgroundColor": "#e92693",
|
||||
"color": "#fff"
|
||||
}
|
||||
}
|
3
extensions/flags/js/.gitignore
vendored
Normal file
3
extensions/flags/js/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
bower_components
|
||||
node_modules
|
||||
dist
|
7
extensions/flags/js/admin/Gulpfile.js
Normal file
7
extensions/flags/js/admin/Gulpfile.js
Normal file
@ -0,0 +1,7 @@
|
||||
var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'reports': 'src/**/*.js'
|
||||
}
|
||||
});
|
7
extensions/flags/js/admin/package.json
Normal file
7
extensions/flags/js/admin/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"gulp": "^3.8.11",
|
||||
"flarum-gulp": "^0.1.0"
|
||||
}
|
||||
}
|
19
extensions/flags/js/admin/src/main.js
Normal file
19
extensions/flags/js/admin/src/main.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import PermissionGrid from 'flarum/components/PermissionGrid';
|
||||
|
||||
app.initializers.add('reports', () => {
|
||||
extend(PermissionGrid.prototype, 'moderateItems', items => {
|
||||
items.add('viewReports', {
|
||||
label: 'View reported posts',
|
||||
permission: 'discussion.viewReports'
|
||||
});
|
||||
});
|
||||
|
||||
extend(PermissionGrid.prototype, 'replyItems', items => {
|
||||
items.add('reportPosts', {
|
||||
label: 'Report posts',
|
||||
permission: 'discussion.reportPosts'
|
||||
});
|
||||
});
|
||||
});
|
7
extensions/flags/js/forum/Gulpfile.js
Normal file
7
extensions/flags/js/forum/Gulpfile.js
Normal file
@ -0,0 +1,7 @@
|
||||
var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'reports': 'src/**/*.js'
|
||||
}
|
||||
});
|
7
extensions/flags/js/forum/package.json
Normal file
7
extensions/flags/js/forum/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"gulp": "^3.8.11",
|
||||
"flarum-gulp": "^0.1.0"
|
||||
}
|
||||
}
|
16
extensions/flags/js/forum/src/addReportControl.js
Normal file
16
extensions/flags/js/forum/src/addReportControl.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import PostControls from 'flarum/utils/PostControls';
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
import ReportPostModal from 'reports/components/ReportPostModal';
|
||||
|
||||
export default function() {
|
||||
extend(PostControls, 'userControls', function(items, post) {
|
||||
if (post.isHidden() || post.contentType() !== 'comment' || !post.canReport() || post.user() === app.session.user) return;
|
||||
|
||||
items.add('report',
|
||||
<Button icon="flag" onclick={() => app.modal.show(new ReportPostModal({post}))}>Report</Button>
|
||||
);
|
||||
});
|
||||
}
|
12
extensions/flags/js/forum/src/addReportsDropdown.js
Normal file
12
extensions/flags/js/forum/src/addReportsDropdown.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import HeaderSecondary from 'flarum/components/HeaderSecondary';
|
||||
import ReportsDropdown from 'reports/components/ReportsDropdown';
|
||||
|
||||
export default function() {
|
||||
extend(HeaderSecondary.prototype, 'items', function(items) {
|
||||
if (app.forum.attribute('canViewReports')) {
|
||||
items.add('reports', <ReportsDropdown/>, 15);
|
||||
}
|
||||
});
|
||||
}
|
134
extensions/flags/js/forum/src/addReportsToPosts.js
Normal file
134
extensions/flags/js/forum/src/addReportsToPosts.js
Normal file
@ -0,0 +1,134 @@
|
||||
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>
|
||||
);
|
||||
});
|
||||
}
|
83
extensions/flags/js/forum/src/components/ReportList.js
Normal file
83
extensions/flags/js/forum/src/components/ReportList.js
Normal file
@ -0,0 +1,83 @@
|
||||
import Component from 'flarum/Component';
|
||||
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||
import avatar from 'flarum/helpers/avatar';
|
||||
import username from 'flarum/helpers/username';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
import humanTime from 'flarum/helpers/humanTime';
|
||||
|
||||
export default class ReportList extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
/**
|
||||
* Whether or not the notifications are loading.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
view() {
|
||||
const reports = app.cache.reports || [];
|
||||
|
||||
return (
|
||||
<div className="NotificationList ReportList">
|
||||
<div className="NotificationList-header">
|
||||
<h4 className="App-titleControl App-titleControl--text">Reported Posts</h4>
|
||||
</div>
|
||||
<div className="NotificationList-content">
|
||||
<ul className="NotificationGroup-content">
|
||||
{reports.length
|
||||
? reports.map(report => {
|
||||
const post = report.post();
|
||||
|
||||
return (
|
||||
<li>
|
||||
<a href={app.route.post(post)} className="Notification Report" config={function(element, isInitialized) {
|
||||
m.route.apply(this, arguments);
|
||||
|
||||
if (!isInitialized) $(element).on('click', () => app.cache.reportIndex = post);
|
||||
}}>
|
||||
{avatar(post.user())}
|
||||
{icon('flag', {className: 'Notification-icon'})}
|
||||
<span className="Notification-content">
|
||||
{username(post.user())} in <em>{post.discussion().title()}</em>
|
||||
</span>
|
||||
{humanTime(report.time())}
|
||||
<div className="Notification-excerpt">
|
||||
{post.contentPlain()}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
: !this.loading
|
||||
? <div className="NotificationList-empty">{app.trans('reports.no_reports')}</div>
|
||||
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load reports into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (app.cache.reports && !app.forum.attribute('unreadReportsCount')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
app.store.find('reports').then(reports => {
|
||||
app.forum.pushAttributes({unreadReportsCount: 0});
|
||||
app.cache.reports = reports.sort((a, b) => b.time() - a.time());
|
||||
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
85
extensions/flags/js/forum/src/components/ReportPostModal.js
Normal file
85
extensions/flags/js/forum/src/components/ReportPostModal.js
Normal file
@ -0,0 +1,85 @@
|
||||
import Modal from 'flarum/components/Modal';
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
export default class ReportPostModal extends Modal {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.reason = m.prop('');
|
||||
this.reasonDetail = m.prop('');
|
||||
}
|
||||
|
||||
className() {
|
||||
return 'ReportPostModal Modal--small';
|
||||
}
|
||||
|
||||
title() {
|
||||
return 'Report Post';
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form">
|
||||
<div className="Form-group">
|
||||
<label>Choose a Reason</label>
|
||||
<div>
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'off_topic'} value="off_topic" onclick={m.withAttr('value', this.reason)}/>
|
||||
Off-topic
|
||||
</label>
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'inappropriate'} value="inappropriate" onclick={m.withAttr('value', this.reason)}/>
|
||||
Inappropriate
|
||||
</label>
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'spam'} value="spam" onclick={m.withAttr('value', this.reason)}/>
|
||||
Spam
|
||||
</label>
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'other'} value="other" onclick={m.withAttr('value', this.reason)}/>
|
||||
Other
|
||||
{this.reason() === 'other' ? (
|
||||
<textarea className="FormControl" value={this.reasonDetail()} oninput={m.withAttr('value', this.reasonDetail)}></textarea>
|
||||
) : ''}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
{Button.component({
|
||||
children: 'Report Post',
|
||||
className: 'Button Button--primary',
|
||||
loading: this.loading,
|
||||
disabled: !this.reason()
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onsubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.loading = true;
|
||||
|
||||
app.store.createRecord('reports').save({
|
||||
reason: this.reason() === 'other' ? null : this.reason(),
|
||||
reasonDetail: this.reasonDetail(),
|
||||
relationships: {
|
||||
user: app.session.user,
|
||||
post: this.props.post
|
||||
}
|
||||
}).then(
|
||||
() => this.hide(),
|
||||
() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
26
extensions/flags/js/forum/src/components/ReportsDropdown.js
Normal file
26
extensions/flags/js/forum/src/components/ReportsDropdown.js
Normal file
@ -0,0 +1,26 @@
|
||||
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');
|
||||
}
|
||||
}
|
24
extensions/flags/js/forum/src/components/ReportsPage.js
Normal file
24
extensions/flags/js/forum/src/components/ReportsPage.js
Normal file
@ -0,0 +1,24 @@
|
||||
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>;
|
||||
}
|
||||
}
|
21
extensions/flags/js/forum/src/main.js
Normal file
21
extensions/flags/js/forum/src/main.js
Normal file
@ -0,0 +1,21 @@
|
||||
import app from 'flarum/app';
|
||||
import Model from 'flarum/Model';
|
||||
|
||||
import Report from 'reports/models/Report';
|
||||
import ReportsPage from 'reports/components/ReportsPage';
|
||||
import addReportControl from 'reports/addReportControl';
|
||||
import addReportsDropdown from 'reports/addReportsDropdown';
|
||||
import addReportsToPosts from 'reports/addReportsToPosts';
|
||||
|
||||
app.initializers.add('reports', () => {
|
||||
app.store.models.posts.prototype.reports = Model.hasMany('reports');
|
||||
app.store.models.posts.prototype.canReport = Model.attribute('canReport');
|
||||
|
||||
app.store.models.reports = Report;
|
||||
|
||||
app.routes.reports = {path: '/reports', component: <ReportsPage/>};
|
||||
|
||||
addReportControl();
|
||||
addReportsDropdown();
|
||||
addReportsToPosts();
|
||||
});
|
12
extensions/flags/js/forum/src/models/Report.js
Normal file
12
extensions/flags/js/forum/src/models/Report.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Model from 'flarum/Model';
|
||||
import mixin from 'flarum/utils/mixin';
|
||||
|
||||
export default class Report extends mixin(Model, {
|
||||
reporter: Model.attribute('reporter'),
|
||||
reason: Model.attribute('reason'),
|
||||
reasonDetail: Model.attribute('reasonDetail'),
|
||||
time: Model.attribute('time', Model.transformDate),
|
||||
|
||||
post: Model.hasOne('post'),
|
||||
user: Model.hasOne('user')
|
||||
}) {}
|
0
extensions/flags/less/admin/extension.less
Normal file
0
extensions/flags/less/admin/extension.less
Normal file
59
extensions/flags/less/forum/extension.less
Normal file
59
extensions/flags/less/forum/extension.less
Normal file
@ -0,0 +1,59 @@
|
||||
.Post--reported {
|
||||
padding-top: 0 !important;
|
||||
border: 2px solid @primary-color;
|
||||
|
||||
.Post-controls {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.Post-header .item-reported {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.Post-reported {
|
||||
background: @primary-color;
|
||||
margin-top: -2px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: -22px;
|
||||
margin-right: -22px;
|
||||
|
||||
@media @tablet-up {
|
||||
margin-left: -22px - 90px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
padding: 10px;
|
||||
border-radius: @border-radius @border-radius 0 0;
|
||||
overflow: hidden;
|
||||
.light-contents(@color: @body-bg; @control-color: @body-bg);
|
||||
|
||||
&, a {
|
||||
color: @body-bg !important;
|
||||
}
|
||||
}
|
||||
.Post-reported-summary {
|
||||
@media @tablet-up {
|
||||
float: left;
|
||||
}
|
||||
|
||||
font-size: 14px;
|
||||
margin: 7px 10px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
.Post-reported-detail {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.Post-reported-actions .Button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.ReportsDropdown .Dropdown-toggle {
|
||||
.Button-label,
|
||||
.Button-caret {
|
||||
display: none;
|
||||
}
|
||||
}
|
8
extensions/flags/locale/en.yml
Normal file
8
extensions/flags/locale/en.yml
Normal file
@ -0,0 +1,8 @@
|
||||
reports:
|
||||
reason_off_topic: Off-topic
|
||||
reason_spam: Spam
|
||||
reason_inappropriate: Inappropriate
|
||||
reason_other: Other
|
||||
reported_by: "Reported by {users}"
|
||||
reported_by_with_reason: "Reported as {reasons} by {users}"
|
||||
no_reports: No Reports
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Flarum\Migrations\Migration;
|
||||
|
||||
class AddReportsReadTimeToUsersTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$this->schema->table('users', function (Blueprint $table) {
|
||||
$table->dateTime('reports_read_time')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->schema->drop('reports_read_time');
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Flarum\Migrations\Migration;
|
||||
|
||||
class CreateReportsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$this->schema->create('reports', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('post_id')->unsigned();
|
||||
$table->integer('user_id')->unsigned();
|
||||
$table->string('reporter')->nullable();
|
||||
$table->string('reason')->nullable();
|
||||
$table->string('reason_detail')->nullable();
|
||||
$table->dateTime('time');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->schema->drop('reports');
|
||||
}
|
||||
}
|
58
extensions/flags/src/Api/CreateAction.php
Normal file
58
extensions/flags/src/Api/CreateAction.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Api;
|
||||
|
||||
use Flarum\Reports\Commands\CreateReport;
|
||||
use Flarum\Api\Actions\CreateAction as BaseCreateAction;
|
||||
use Flarum\Api\JsonApiRequest;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
|
||||
class CreateAction extends BaseCreateAction
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $serializer = 'Flarum\Reports\Api\ReportSerializer';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $include = [
|
||||
'post' => true,
|
||||
'post.reports' => true
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Dispatcher $bus
|
||||
*/
|
||||
public function __construct(Dispatcher $bus)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a report according to input from the API request.
|
||||
*
|
||||
* @param JsonApiRequest $request
|
||||
* @return \Flarum\Reports\Report
|
||||
*/
|
||||
protected function create(JsonApiRequest $request)
|
||||
{
|
||||
return $this->bus->dispatch(
|
||||
new CreateReport($request->actor, $request->get('data'))
|
||||
);
|
||||
}
|
||||
}
|
44
extensions/flags/src/Api/DeleteAction.php
Normal file
44
extensions/flags/src/Api/DeleteAction.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Api;
|
||||
|
||||
use Flarum\Reports\Commands\DeleteReports;
|
||||
use Flarum\Api\Actions\DeleteAction as BaseDeleteAction;
|
||||
use Flarum\Api\Request;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
|
||||
class DeleteAction extends BaseDeleteAction
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $bus
|
||||
*/
|
||||
public function __construct(Dispatcher $bus)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete reports for a post.
|
||||
*
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function delete(Request $request)
|
||||
{
|
||||
$this->bus->dispatch(
|
||||
new DeleteReports($request->get('id'), $request->actor, $request->all())
|
||||
);
|
||||
}
|
||||
}
|
53
extensions/flags/src/Api/IndexAction.php
Normal file
53
extensions/flags/src/Api/IndexAction.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Api;
|
||||
|
||||
use Flarum\Api\Actions\SerializeCollectionAction;
|
||||
use Flarum\Api\JsonApiRequest;
|
||||
use Flarum\Reports\Report;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class IndexAction extends SerializeCollectionAction
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $serializer = 'Flarum\Reports\Api\ReportSerializer';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $include = [
|
||||
'user' => true,
|
||||
'post' => true,
|
||||
'post.user' => true,
|
||||
'post.discussion' => true
|
||||
];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $link = [];
|
||||
|
||||
protected function data(JsonApiRequest $request, Document $document)
|
||||
{
|
||||
$actor = $request->actor;
|
||||
|
||||
$actor->reports_read_time = time();
|
||||
$actor->save();
|
||||
|
||||
return Report::whereVisibleTo($actor)
|
||||
->with($request->include)
|
||||
->latest('reports.time')
|
||||
->groupBy('post_id')
|
||||
->get();
|
||||
}
|
||||
}
|
37
extensions/flags/src/Api/ReportSerializer.php
Normal file
37
extensions/flags/src/Api/ReportSerializer.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Api;
|
||||
|
||||
use Flarum\Api\Serializers\Serializer;
|
||||
|
||||
class ReportSerializer extends Serializer
|
||||
{
|
||||
protected $type = 'reports';
|
||||
|
||||
protected function getDefaultAttributes($report)
|
||||
{
|
||||
return [
|
||||
'reporter' => $report->reporter,
|
||||
'reason' => $report->reason,
|
||||
'reasonDetail' => $report->reason_detail,
|
||||
];
|
||||
}
|
||||
|
||||
protected function post()
|
||||
{
|
||||
return $this->hasOne('Flarum\Api\Serializers\PostSerializer');
|
||||
}
|
||||
|
||||
protected function user()
|
||||
{
|
||||
return $this->hasOne('Flarum\Api\Serializers\UserBasicSerializer');
|
||||
}
|
||||
}
|
40
extensions/flags/src/Commands/CreateReport.php
Normal file
40
extensions/flags/src/Commands/CreateReport.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Commands;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class CreateReport
|
||||
{
|
||||
/**
|
||||
* The user performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The attributes of the new report.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param User $actor The user performing the action.
|
||||
* @param array $data The attributes of the new report.
|
||||
*/
|
||||
public function __construct(User $actor, array $data)
|
||||
{
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
63
extensions/flags/src/Commands/CreateReportHandler.php
Normal file
63
extensions/flags/src/Commands/CreateReportHandler.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Commands;
|
||||
|
||||
use Flarum\Reports\Report;
|
||||
use Flarum\Core\Posts\PostRepository;
|
||||
use Flarum\Core\Posts\CommentPost;
|
||||
use Exception;
|
||||
|
||||
class CreateReportHandler
|
||||
{
|
||||
private $posts;
|
||||
|
||||
public function __construct(PostRepository $posts)
|
||||
{
|
||||
$this->posts = $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CreateReport $command
|
||||
* @return Report
|
||||
*/
|
||||
public function handle(CreateReport $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
$data = $command->data;
|
||||
|
||||
$postId = array_get($data, 'relationships.post.data.id');
|
||||
$post = $this->posts->findOrFail($postId, $actor);
|
||||
|
||||
if (! ($post instanceof CommentPost)) {
|
||||
// TODO: throw 400(?) error
|
||||
throw new Exception;
|
||||
}
|
||||
|
||||
$post->assertCan($actor, 'report');
|
||||
|
||||
Report::unguard();
|
||||
|
||||
$report = Report::firstOrNew([
|
||||
'post_id' => $post->id,
|
||||
'user_id' => $actor->id
|
||||
]);
|
||||
|
||||
$report->post_id = $post->id;
|
||||
$report->user_id = $actor->id;
|
||||
$report->reason = array_get($data, 'attributes.reason');
|
||||
$report->reason_detail = array_get($data, 'attributes.reasonDetail');
|
||||
$report->time = time();
|
||||
|
||||
$report->save();
|
||||
|
||||
return $report;
|
||||
}
|
||||
}
|
48
extensions/flags/src/Commands/DeleteReports.php
Normal file
48
extensions/flags/src/Commands/DeleteReports.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Commands;
|
||||
|
||||
use Flarum\Reports\Report;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class DeleteReports
|
||||
{
|
||||
/**
|
||||
* The ID of the post to delete reports for.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $postId;
|
||||
|
||||
/**
|
||||
* The user performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param int $postId The ID of the post to delete reports for.
|
||||
* @param User $actor The user performing the action.
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct($postId, User $actor, array $data = [])
|
||||
{
|
||||
$this->postId = $postId;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
45
extensions/flags/src/Commands/DeleteReportsHandler.php
Normal file
45
extensions/flags/src/Commands/DeleteReportsHandler.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Commands;
|
||||
|
||||
use Flarum\Reports\Report;
|
||||
use Flarum\Core\Posts\PostRepository;
|
||||
use Flarum\Reports\Events\ReportsWillBeDeleted;
|
||||
|
||||
class DeleteReportsHandler
|
||||
{
|
||||
protected $posts;
|
||||
|
||||
public function __construct(PostRepository $posts)
|
||||
{
|
||||
$this->posts = $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DeleteReport $command
|
||||
* @return Report
|
||||
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
|
||||
*/
|
||||
public function handle(DeleteReports $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
|
||||
$post = $this->posts->findOrFail($command->postId, $actor);
|
||||
|
||||
$post->discussion->assertCan($actor, 'viewReports');
|
||||
|
||||
event(new ReportsWillBeDeleted($post, $actor, $command->data));
|
||||
|
||||
$post->reports()->delete();
|
||||
|
||||
return $post;
|
||||
}
|
||||
}
|
45
extensions/flags/src/Events/ReportsWillBeDeleted.php
Normal file
45
extensions/flags/src/Events/ReportsWillBeDeleted.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Events;
|
||||
|
||||
use Flarum\Core\Posts\Post;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class ReportsWillBeDeleted
|
||||
{
|
||||
/**
|
||||
* @var Post
|
||||
*/
|
||||
public $post;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param Post $post
|
||||
* @param User $actor
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(Post $post, User $actor, array $data = [])
|
||||
{
|
||||
$this->post = $post;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
24
extensions/flags/src/Extension.php
Normal file
24
extensions/flags/src/Extension.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports;
|
||||
|
||||
use Flarum\Support\Extension as BaseExtension;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
|
||||
class Extension extends BaseExtension
|
||||
{
|
||||
public function listen(Dispatcher $events)
|
||||
{
|
||||
$events->subscribe('Flarum\Reports\Listeners\AddClientAssets');
|
||||
$events->subscribe('Flarum\Reports\Listeners\AddApiAttributes');
|
||||
$events->subscribe('Flarum\Reports\Listeners\AddModelRelationship');
|
||||
}
|
||||
}
|
129
extensions/flags/src/Listeners/AddApiAttributes.php
Executable file
129
extensions/flags/src/Listeners/AddApiAttributes.php
Executable file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Listeners;
|
||||
|
||||
use Flarum\Events\ApiRelationship;
|
||||
use Flarum\Events\WillSerializeData;
|
||||
use Flarum\Events\BuildApiAction;
|
||||
use Flarum\Events\ApiAttributes;
|
||||
use Flarum\Events\RegisterApiRoutes;
|
||||
use Flarum\Api\Serializers\PostSerializer;
|
||||
use Flarum\Api\Serializers\ForumSerializer;
|
||||
use Flarum\Api\Actions\Posts;
|
||||
use Flarum\Api\Actions\Discussions;
|
||||
use Flarum\Reports\Report;
|
||||
use Flarum\Reports\Api\CreateAction as ReportsCreateAction;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class AddApiAttributes
|
||||
{
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen(ApiRelationship::class, [$this, 'addReportsRelationship']);
|
||||
$events->listen(WillSerializeData::class, [$this, 'loadReportsRelationship']);
|
||||
$events->listen(BuildApiAction::class, [$this, 'includeReportsRelationship']);
|
||||
$events->listen(ApiAttributes::class, [$this, 'addAttributes']);
|
||||
$events->listen(RegisterApiRoutes::class, [$this, 'addRoutes']);
|
||||
}
|
||||
|
||||
public function loadReportsRelationship(WillSerializeData $event)
|
||||
{
|
||||
// For any API action that allows the 'reports' relationship to be
|
||||
// included, we need to preload this relationship onto the data (Post
|
||||
// models) so that we can selectively expose only the reports that the
|
||||
// user has permission to view.
|
||||
if ($event->action instanceof Discussions\ShowAction) {
|
||||
$discussion = $event->data;
|
||||
$posts = $discussion->posts->all();
|
||||
}
|
||||
|
||||
if ($event->action instanceof Posts\IndexAction) {
|
||||
$posts = $event->data->all();
|
||||
}
|
||||
|
||||
if ($event->action instanceof Posts\ShowAction) {
|
||||
$posts = [$event->data];
|
||||
}
|
||||
|
||||
if ($event->action instanceof ReportsCreateAction) {
|
||||
$report = $event->data;
|
||||
$posts = [$report->post];
|
||||
}
|
||||
|
||||
if (isset($posts)) {
|
||||
$actor = $event->request->actor;
|
||||
$postsWithPermission = [];
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$post->setRelation('reports', null);
|
||||
|
||||
if ($post->discussion->can($actor, 'viewReports')) {
|
||||
$postsWithPermission[] = $post;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($postsWithPermission)) {
|
||||
(new Collection($postsWithPermission))
|
||||
->load('reports', 'reports.user');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addReportsRelationship(ApiRelationship $event)
|
||||
{
|
||||
if ($event->serializer instanceof PostSerializer &&
|
||||
$event->relationship === 'reports') {
|
||||
return $event->serializer->hasMany('Flarum\Reports\Api\ReportSerializer', 'reports');
|
||||
}
|
||||
}
|
||||
|
||||
public function includeReportsRelationship(BuildApiAction $event)
|
||||
{
|
||||
if ($event->action instanceof Discussions\ShowAction) {
|
||||
$event->addInclude('posts.reports');
|
||||
$event->addInclude('posts.reports.user');
|
||||
}
|
||||
|
||||
if ($event->action instanceof Posts\IndexAction ||
|
||||
$event->action instanceof Posts\ShowAction) {
|
||||
$event->addInclude('reports');
|
||||
$event->addInclude('reports.user');
|
||||
}
|
||||
}
|
||||
|
||||
public function addAttributes(ApiAttributes $event)
|
||||
{
|
||||
if ($event->serializer instanceof ForumSerializer) {
|
||||
$event->attributes['canViewReports'] = $event->actor->hasPermissionLike('discussion.viewReports');
|
||||
|
||||
if ($event->attributes['canViewReports']) {
|
||||
$query = Report::whereVisibleTo($event->actor);
|
||||
|
||||
if ($time = $event->actor->reports_read_time) {
|
||||
$query->where('reports.time', '>', $time);
|
||||
}
|
||||
|
||||
$event->attributes['unreadReportsCount'] = $query->distinct('reports.post_id')->count();
|
||||
}
|
||||
}
|
||||
|
||||
if ($event->serializer instanceof PostSerializer) {
|
||||
$event->attributes['canReport'] = $event->model->can($event->actor, 'report');
|
||||
}
|
||||
}
|
||||
|
||||
public function addRoutes(RegisterApiRoutes $event)
|
||||
{
|
||||
$event->get('/reports', 'reports.index', 'Flarum\Reports\Api\IndexAction');
|
||||
$event->post('/reports', 'reports.create', 'Flarum\Reports\Api\CreateAction');
|
||||
$event->delete('/posts/{id}/reports', 'reports.delete', 'Flarum\Reports\Api\DeleteAction');
|
||||
}
|
||||
}
|
60
extensions/flags/src/Listeners/AddClientAssets.php
Normal file
60
extensions/flags/src/Listeners/AddClientAssets.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Listeners;
|
||||
|
||||
use Flarum\Events\RegisterLocales;
|
||||
use Flarum\Events\BuildClientView;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class AddClientAssets
|
||||
{
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen(RegisterLocales::class, [$this, 'addLocale']);
|
||||
$events->listen(BuildClientView::class, [$this, 'addAssets']);
|
||||
}
|
||||
|
||||
public function addLocale(RegisterLocales $event)
|
||||
{
|
||||
$event->addTranslations('en', __DIR__.'/../../locale/en.yml');
|
||||
}
|
||||
|
||||
public function addAssets(BuildClientView $event)
|
||||
{
|
||||
$event->forumAssets([
|
||||
__DIR__.'/../../js/forum/dist/extension.js',
|
||||
__DIR__.'/../../less/forum/extension.less'
|
||||
]);
|
||||
|
||||
$event->forumBootstrapper('reports/main');
|
||||
|
||||
$event->forumTranslations([
|
||||
'reports.reason_off_topic',
|
||||
'reports.reason_spam',
|
||||
'reports.reason_inappropriate',
|
||||
'reports.reason_other',
|
||||
'reports.reported_by',
|
||||
'reports.reported_by_with_reason',
|
||||
'reports.no_reports'
|
||||
]);
|
||||
|
||||
$event->adminAssets([
|
||||
__DIR__.'/../../js/admin/dist/extension.js',
|
||||
__DIR__.'/../../less/admin/extension.less'
|
||||
]);
|
||||
|
||||
$event->adminBootstrapper('reports/main');
|
||||
|
||||
$event->adminTranslations([
|
||||
// 'report.hello_world'
|
||||
]);
|
||||
}
|
||||
}
|
40
extensions/flags/src/Listeners/AddModelRelationship.php
Executable file
40
extensions/flags/src/Listeners/AddModelRelationship.php
Executable file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports\Listeners;
|
||||
|
||||
use Flarum\Events\ModelRelationship;
|
||||
use Flarum\Events\ModelDates;
|
||||
use Flarum\Core\Posts\Post;
|
||||
use Flarum\Core\Users\User;
|
||||
use Flarum\Reports\Report;
|
||||
|
||||
class AddModelRelationship
|
||||
{
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen(ModelRelationship::class, [$this, 'addReportsRelationship']);
|
||||
$events->listen(ModelDates::class, [$this, 'modelDates']);
|
||||
}
|
||||
|
||||
public function addReportsRelationship(ModelRelationship $event)
|
||||
{
|
||||
if ($event->model instanceof Post && $event->relationship === 'reports') {
|
||||
return $event->model->hasMany('Flarum\Reports\Report', 'post_id');
|
||||
}
|
||||
}
|
||||
|
||||
public function modelDates(ModelDates $event)
|
||||
{
|
||||
if ($event->model instanceof User) {
|
||||
$event->dates[] = 'reports_read_time';
|
||||
}
|
||||
}
|
||||
}
|
33
extensions/flags/src/Report.php
Normal file
33
extensions/flags/src/Report.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Reports;
|
||||
|
||||
use Flarum\Core\Model;
|
||||
use Flarum\Core\Support\VisibleScope;
|
||||
|
||||
class Report extends Model
|
||||
{
|
||||
use VisibleScope;
|
||||
|
||||
protected $table = 'reports';
|
||||
|
||||
protected $dates = ['time'];
|
||||
|
||||
public function post()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Posts\Post');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Users\User');
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user