mirror of
https://github.com/flarum/core.git
synced 2025-07-15 13:56:23 +02:00
Merge remote-tracking branch 'extensions_flags/REWRITE'
This commit is contained in:
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,xml,json}]
|
||||
indent_size = 4
|
18
extensions/flags/.gitattributes
vendored
Normal file
18
extensions/flags/.gitattributes
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.gitmodules export-ignore
|
||||
.github export-ignore
|
||||
.travis export-ignore
|
||||
.travis.yml export-ignore
|
||||
.editorconfig export-ignore
|
||||
.styleci.yml export-ignore
|
||||
|
||||
phpunit.xml export-ignore
|
||||
tests export-ignore
|
||||
|
||||
js/dist/* -diff
|
||||
js/dist/* linguist-generated
|
||||
js/dist-typings/* linguist-generated
|
||||
js/yarn.lock -diff
|
||||
|
||||
* text=auto eol=lf
|
15
extensions/flags/.github/workflows/backend.yml
vendored
Normal file
15
extensions/flags/.github/workflows/backend.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
name: Flags PHP
|
||||
|
||||
on: [workflow_dispatch, push, pull_request]
|
||||
|
||||
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
|
||||
# This will break your current script.
|
||||
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
|
||||
with:
|
||||
enable_backend_testing: true
|
||||
|
||||
backend_directory: .
|
21
extensions/flags/.github/workflows/frontend.yml
vendored
Normal file
21
extensions/flags/.github/workflows/frontend.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Flags JS
|
||||
|
||||
on: [workflow_dispatch, push, pull_request]
|
||||
|
||||
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
|
||||
# This will break your current script.
|
||||
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
|
||||
with:
|
||||
enable_bundlewatch: false
|
||||
enable_prettier: true
|
||||
enable_typescript: true
|
||||
|
||||
frontend_directory: ./js
|
||||
main_git_branch: master
|
||||
|
||||
secrets:
|
||||
bundlewatch_github_token: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}
|
12
extensions/flags/.gitignore
vendored
Normal file
12
extensions/flags/.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/vendor
|
||||
composer.lock
|
||||
composer.phar
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
tests/.phpunit.result.cache
|
||||
/tests/integration/tmp
|
||||
.vagrant
|
||||
.idea/*
|
||||
.vscode
|
||||
js/coverage-ts
|
14
extensions/flags/.styleci.yml
Normal file
14
extensions/flags/.styleci.yml
Normal file
@ -0,0 +1,14 @@
|
||||
preset: recommended
|
||||
|
||||
enabled:
|
||||
- logical_not_operators_with_successor_space
|
||||
|
||||
disabled:
|
||||
- align_double_arrow
|
||||
- blank_line_after_opening_tag
|
||||
- multiline_array_trailing_comma
|
||||
- new_with_braces
|
||||
- phpdoc_align
|
||||
- phpdoc_order
|
||||
- phpdoc_separation
|
||||
- phpdoc_types
|
79
extensions/flags/CHANGELOG.md
Normal file
79
extensions/flags/CHANGELOG.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Changelog
|
||||
|
||||
## [1.2.0](https://github.com/flarum/flags/compare/v1.1.0...v1.2.0)
|
||||
|
||||
### Fixed
|
||||
- Missing translation for the reason flag (https://github.com/flarum/flags/pull/30).
|
||||
- Created flags show user as "deleted" until a page refresh (https://github.com/flarum/flags/pull/42)
|
||||
|
||||
## [1.1.0](https://github.com/flarum/flags/compare/v1.0.0...v1.1.0)
|
||||
|
||||
No changes.
|
||||
|
||||
## [1.0.0](https://github.com/flarum/flags/compare/v0.1.0-beta.16...v1.0.0)
|
||||
|
||||
### Changed
|
||||
- Compatibility with Flarum v1.0.0.
|
||||
- Improvements to performance by eager loading relations (#38)
|
||||
|
||||
### Fixes
|
||||
- Incorrectly esolving the deprecated Symfony translator implementation instead of the contract
|
||||
|
||||
## [0.1.0-beta.16](https://github.com/flarum/flags/compare/v0.1.0-beta.15...v0.1.0-beta.16)
|
||||
|
||||
### Added
|
||||
- Created and Deleting events (https://github.com/flarum/flags/pull/35)
|
||||
|
||||
### Changed
|
||||
- Updated admin category from moderation to feature (https://github.com/flarum/flags/pull/36)
|
||||
- Moved locale files from translation pack to extension (https://github.com/flarum/flags/pull/32)
|
||||
|
||||
## [0.1.0-beta.15](https://github.com/flarum/flags/compare/v0.1.0-beta.14.1...v0.1.0-beta.15)
|
||||
|
||||
### Changed
|
||||
- Updated composer.json and admin javascript for new admin area.
|
||||
- Updated to use newest extenders.
|
||||
|
||||
## [0.1.0-beta.14.1](https://github.com/flarum/flags/compare/v0.1.0-beta.14...v0.1.0-beta.14.1)
|
||||
|
||||
### Fixed
|
||||
- Flags cache was instantiated prematurely causing incorrect flags count (#31)
|
||||
|
||||
## [0.1.0-beta.14](https://github.com/flarum/flags/compare/v0.1.0-beta.13...v0.1.0-beta.14)
|
||||
|
||||
### Changed
|
||||
- Updated mithril to version 2
|
||||
- Load language strings correctly on en-/disable
|
||||
- Updated JS dependencies
|
||||
|
||||
## [0.1.0-beta.13](https://github.com/flarum/flags/compare/v0.1.0-beta.12...v0.1.0-beta.13)
|
||||
|
||||
### Changed
|
||||
- Updated JS dependencies
|
||||
- Stop using deprecated core events, use extenders instead
|
||||
|
||||
## [0.1.0-beta.12](https://github.com/flarum/flags/compare/v0.1.0-beta.10...v0.1.0-beta.12)
|
||||
|
||||
### Changed
|
||||
|
||||
- Larger flag modal, disallow users to flag their own posts, increase flag message size,
|
||||
allow comment on more reasons, disabled submit on other without comment ([5292e6c](https://github.com/flarum/flags/commit/5292e6cf8a3d4610171f44a6feebb7b31794dd11))
|
||||
|
||||
## [0.1.0-beta.10](https://github.com/flarum/flags/compare/v0.1.0-beta.9...v0.1.0-beta.10)
|
||||
|
||||
### Fixed
|
||||
- Guests visiting `api/flags` trigger an exception ([d4680a0](https://github.com/flarum/flags/pull/19/commits/d4680a041afdb286ac85865e5b1f51345a6f9384))
|
||||
|
||||
## [0.1.0-beta.9](https://github.com/flarum/flags/compare/v0.1.0-beta.8.1...v0.1.0-beta.9)
|
||||
|
||||
### Changed
|
||||
- Replace event subscribers (that resolve services too early) with listeners ([2f3417b](https://github.com/flarum/flags/commit/2f3417b863793b918d64c51bcdd65a77e05ffdb9))
|
||||
- Compatibility with Laravel 5.7 ([bd00270](https://github.com/flarum/flags/commit/bd002708c57b5297b1796233d04d18876523ae49))
|
||||
|
||||
### Fixed
|
||||
- API serialization failed when posts for a discussion were not loaded and needed ([9803914](https://github.com/flarum/flags/commit/98039144984eab4e43be7316ecc29fc56959b2c3))
|
||||
|
||||
## [0.1.0-beta.8.1](https://github.com/flarum/flags/compare/v0.1.0-beta.8...v0.1.0-beta.8.1)
|
||||
|
||||
### Fixed
|
||||
- Fix dropping foreign keys in `down` migrations ([e17bd03](https://github.com/flarum/flags/commit/e17bd037b011aac6ef3e38a44ab859a25cd1f763))
|
22
extensions/flags/LICENSE
Normal file
22
extensions/flags/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-2021 Stichting Flarum (Flarum Foundation)
|
||||
Copyright (c) 2014-2019 Toby Zerner (toby.zerner@gmail.com)
|
||||
|
||||
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.
|
80
extensions/flags/composer.json
Normal file
80
extensions/flags/composer.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "flarum/flags",
|
||||
"description": "Allow users to flag posts for moderator review.",
|
||||
"type": "flarum-extension",
|
||||
"keywords": [
|
||||
"moderation"
|
||||
],
|
||||
"license": "MIT",
|
||||
"support": {
|
||||
"issues": "https://github.com/flarum/core/issues",
|
||||
"source": "https://github.com/flarum/flags",
|
||||
"forum": "https://discuss.flarum.org"
|
||||
},
|
||||
"homepage": "https://flarum.org",
|
||||
"funding": [
|
||||
{
|
||||
"type": "website",
|
||||
"url": "https://flarum.org/donate/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Flarum\\Flags\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
},
|
||||
"flarum-extension": {
|
||||
"title": "Flags",
|
||||
"category": "feature",
|
||||
"icon": {
|
||||
"name": "fas fa-flag",
|
||||
"backgroundColor": "#D659B5",
|
||||
"color": "#fff"
|
||||
}
|
||||
},
|
||||
"flarum-cli": {
|
||||
"modules": {
|
||||
"admin": true,
|
||||
"forum": true,
|
||||
"js": true,
|
||||
"jsCommon": false,
|
||||
"css": true,
|
||||
"gitConf": true,
|
||||
"githubActions": true,
|
||||
"prettier": true,
|
||||
"typescript": false,
|
||||
"bundlewatch": false,
|
||||
"backendTesting": true,
|
||||
"editorConfig": true,
|
||||
"styleci": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"@test:unit",
|
||||
"@test:integration"
|
||||
],
|
||||
"test:unit": "phpunit -c tests/phpunit.unit.xml",
|
||||
"test:integration": "phpunit -c tests/phpunit.integration.xml",
|
||||
"test:setup": "@php tests/integration/setup.php"
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"test": "Runs all tests.",
|
||||
"test:unit": "Runs all unit tests.",
|
||||
"test:integration": "Runs all integration tests.",
|
||||
"test:setup": "Sets up a database for use with integration tests. Execute this only once."
|
||||
},
|
||||
"require-dev": {
|
||||
"flarum/core": "*@dev",
|
||||
"flarum/tags": "*@dev",
|
||||
"flarum/testing": "^1.0.0"
|
||||
}
|
||||
}
|
86
extensions/flags/extend.php
Normal file
86
extensions/flags/extend.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Api\Controller\AbstractSerializeController;
|
||||
use Flarum\Api\Controller\ListPostsController;
|
||||
use Flarum\Api\Controller\ShowDiscussionController;
|
||||
use Flarum\Api\Controller\ShowPostController;
|
||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||
use Flarum\Api\Serializer\ForumSerializer;
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Flags\Access\ScopeFlagVisibility;
|
||||
use Flarum\Flags\AddCanFlagAttribute;
|
||||
use Flarum\Flags\AddFlagsApiAttributes;
|
||||
use Flarum\Flags\AddNewFlagCountAttribute;
|
||||
use Flarum\Flags\Api\Controller\CreateFlagController;
|
||||
use Flarum\Flags\Api\Controller\DeleteFlagsController;
|
||||
use Flarum\Flags\Api\Controller\ListFlagsController;
|
||||
use Flarum\Flags\Api\Serializer\FlagSerializer;
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\Flags\Listener;
|
||||
use Flarum\Flags\PrepareFlagsApiData;
|
||||
use Flarum\Forum\Content\AssertRegistered;
|
||||
use Flarum\Post\Event\Deleted;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\User\User;
|
||||
|
||||
return [
|
||||
(new Extend\Frontend('forum'))
|
||||
->js(__DIR__.'/js/dist/forum.js')
|
||||
->css(__DIR__.'/less/forum.less')
|
||||
->route('/flags', 'flags', AssertRegistered::class),
|
||||
|
||||
(new Extend\Frontend('admin'))
|
||||
->js(__DIR__.'/js/dist/admin.js'),
|
||||
|
||||
(new Extend\Routes('api'))
|
||||
->get('/flags', 'flags.index', ListFlagsController::class)
|
||||
->post('/flags', 'flags.create', CreateFlagController::class)
|
||||
->delete('/posts/{id}/flags', 'flags.delete', DeleteFlagsController::class),
|
||||
|
||||
(new Extend\Model(User::class))
|
||||
->dateAttribute('read_flags_at'),
|
||||
|
||||
(new Extend\Model(Post::class))
|
||||
->hasMany('flags', Flag::class, 'post_id'),
|
||||
|
||||
(new Extend\ApiSerializer(PostSerializer::class))
|
||||
->hasMany('flags', FlagSerializer::class)
|
||||
->attribute('canFlag', AddCanFlagAttribute::class),
|
||||
|
||||
(new Extend\ApiSerializer(CurrentUserSerializer::class))
|
||||
->attribute('newFlagCount', AddNewFlagCountAttribute::class),
|
||||
|
||||
(new Extend\ApiSerializer(ForumSerializer::class))
|
||||
->attributes(AddFlagsApiAttributes::class),
|
||||
|
||||
(new Extend\ApiController(ShowDiscussionController::class))
|
||||
->addInclude(['posts.flags', 'posts.flags.user']),
|
||||
|
||||
(new Extend\ApiController(ListPostsController::class))
|
||||
->addInclude(['flags', 'flags.user']),
|
||||
|
||||
(new Extend\ApiController(ShowPostController::class))
|
||||
->addInclude(['flags', 'flags.user']),
|
||||
|
||||
(new Extend\ApiController(AbstractSerializeController::class))
|
||||
->prepareDataForSerialization(PrepareFlagsApiData::class),
|
||||
|
||||
(new Extend\Settings())
|
||||
->serializeToForum('guidelinesUrl', 'flarum-flags.guidelines_url'),
|
||||
|
||||
(new Extend\Event())
|
||||
->listen(Deleted::class, Listener\DeleteFlags::class),
|
||||
|
||||
(new Extend\ModelVisibility(Flag::class))
|
||||
->scope(ScopeFlagVisibility::class),
|
||||
|
||||
new Extend\Locales(__DIR__.'/locale'),
|
||||
];
|
9
extensions/flags/js/.gitignore
vendored
Normal file
9
extensions/flags/js/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
node_modules
|
1
extensions/flags/js/admin.js
Normal file
1
extensions/flags/js/admin.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './src/admin';
|
2
extensions/flags/js/dist/admin.js
generated
vendored
Normal file
2
extensions/flags/js/dist/admin.js
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
module.exports=function(e){var t={};function r(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(n,a,function(t){return e[t]}.bind(null,a));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=24)}({24:function(e,t,r){"use strict";r.r(t);var n=r(5),a=r.n(n);a.a.initializers.add("flarum-flags",(function(){a.a.extensionData.for("flarum-flags").registerSetting({setting:"flarum-flags.guidelines_url",type:"text",label:a.a.translator.trans("flarum-flags.admin.settings.guidelines_url_label")},15).registerSetting({setting:"flarum-flags.can_flag_own",type:"boolean",label:a.a.translator.trans("flarum-flags.admin.settings.flag_own_posts_label")}).registerPermission({icon:"fas fa-flag",label:a.a.translator.trans("flarum-flags.admin.permissions.view_flags_label"),permission:"discussion.viewFlags"},"moderate",65).registerPermission({icon:"fas fa-flag",label:a.a.translator.trans("flarum-flags.admin.permissions.flag_posts_label"),permission:"discussion.flagPosts"},"reply",65)}))},5:function(e,t){e.exports=flarum.core.compat["admin/app"]}});
|
||||
//# sourceMappingURL=admin.js.map
|
1
extensions/flags/js/dist/admin.js.map
generated
vendored
Normal file
1
extensions/flags/js/dist/admin.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
extensions/flags/js/dist/forum.js
generated
vendored
Normal file
2
extensions/flags/js/dist/forum.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
extensions/flags/js/dist/forum.js.map
generated
vendored
Normal file
1
extensions/flags/js/dist/forum.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
extensions/flags/js/forum.js
Normal file
1
extensions/flags/js/forum.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './src/forum';
|
33
extensions/flags/js/package.json
Normal file
33
extensions/flags/js/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@flarum/flags",
|
||||
"prettier": "@flarum/prettier-config",
|
||||
"dependencies": {
|
||||
"@flarum/prettier-config": "^1.0.0",
|
||||
"flarum-webpack-config": "^1.0.0",
|
||||
"flarum-tsconfig": "^1.0.2",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-cli": "^4.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.5.1",
|
||||
"flarum-webpack-config": "^2.0.0",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"@flarum/prettier-config": "^1.0.0",
|
||||
"flarum-tsconfig": "^1.0.2",
|
||||
"typescript": "^4.5.4",
|
||||
"typescript-coverage-report": "^0.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "webpack --mode development --watch",
|
||||
"build": "webpack --mode production",
|
||||
"format": "prettier --write src",
|
||||
"format-check": "prettier --check src",
|
||||
"analyze": "cross-env ANALYZER=true yarn build",
|
||||
"clean-typings": "npx rimraf dist-typings && mkdir dist-typings",
|
||||
"build-typings": "npm run clean-typings && cp -r src/@types dist-typings/@types && tsc",
|
||||
"check-typings": "tsc --noEmit --emitDeclarationOnly false",
|
||||
"check-typings-coverage": "typescript-coverage-report"
|
||||
}
|
||||
}
|
38
extensions/flags/js/src/admin/index.js
Normal file
38
extensions/flags/js/src/admin/index.js
Normal file
@ -0,0 +1,38 @@
|
||||
import app from 'flarum/admin/app';
|
||||
|
||||
app.initializers.add('flarum-flags', () => {
|
||||
app.extensionData
|
||||
.for('flarum-flags')
|
||||
.registerSetting(
|
||||
{
|
||||
setting: 'flarum-flags.guidelines_url',
|
||||
type: 'text',
|
||||
label: app.translator.trans('flarum-flags.admin.settings.guidelines_url_label'),
|
||||
},
|
||||
15
|
||||
)
|
||||
.registerSetting({
|
||||
setting: 'flarum-flags.can_flag_own',
|
||||
type: 'boolean',
|
||||
label: app.translator.trans('flarum-flags.admin.settings.flag_own_posts_label'),
|
||||
})
|
||||
.registerPermission(
|
||||
{
|
||||
icon: 'fas fa-flag',
|
||||
label: app.translator.trans('flarum-flags.admin.permissions.view_flags_label'),
|
||||
permission: 'discussion.viewFlags',
|
||||
},
|
||||
'moderate',
|
||||
65
|
||||
)
|
||||
|
||||
.registerPermission(
|
||||
{
|
||||
icon: 'fas fa-flag',
|
||||
label: app.translator.trans('flarum-flags.admin.permissions.flag_posts_label'),
|
||||
permission: 'discussion.flagPosts',
|
||||
},
|
||||
'reply',
|
||||
65
|
||||
);
|
||||
});
|
19
extensions/flags/js/src/forum/addFlagControl.js
Normal file
19
extensions/flags/js/src/forum/addFlagControl.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { extend } from 'flarum/common/extend';
|
||||
import app from 'flarum/forum/app';
|
||||
import PostControls from 'flarum/forum/utils/PostControls';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
|
||||
import FlagPostModal from './components/FlagPostModal';
|
||||
|
||||
export default function () {
|
||||
extend(PostControls, 'userControls', function (items, post) {
|
||||
if (post.isHidden() || post.contentType() !== 'comment' || !post.canFlag()) return;
|
||||
|
||||
items.add(
|
||||
'flag',
|
||||
<Button icon="fas fa-flag" onclick={() => app.modal.show(FlagPostModal, { post })}>
|
||||
{app.translator.trans('flarum-flags.forum.post_controls.flag_button')}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
}
|
12
extensions/flags/js/src/forum/addFlagsDropdown.js
Normal file
12
extensions/flags/js/src/forum/addFlagsDropdown.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { extend } from 'flarum/common/extend';
|
||||
import app from 'flarum/forum/app';
|
||||
import HeaderSecondary from 'flarum/forum/components/HeaderSecondary';
|
||||
import FlagsDropdown from './components/FlagsDropdown';
|
||||
|
||||
export default function () {
|
||||
extend(HeaderSecondary.prototype, 'items', function (items) {
|
||||
if (app.forum.attribute('canViewFlags')) {
|
||||
items.add('flags', <FlagsDropdown state={app.flags} />, 15);
|
||||
}
|
||||
});
|
||||
}
|
115
extensions/flags/js/src/forum/addFlagsToPosts.js
Normal file
115
extensions/flags/js/src/forum/addFlagsToPosts.js
Normal file
@ -0,0 +1,115 @@
|
||||
import { extend } from 'flarum/common/extend';
|
||||
import app from 'flarum/forum/app';
|
||||
import Post from 'flarum/forum/components/Post';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import PostControls from 'flarum/forum/utils/PostControls';
|
||||
import humanTime from 'flarum/common/utils/humanTime';
|
||||
|
||||
export default function () {
|
||||
extend(Post.prototype, 'elementAttrs', function (attrs) {
|
||||
if (this.attrs.post.flags().length) {
|
||||
attrs.className += ' Post--flagged';
|
||||
}
|
||||
});
|
||||
|
||||
Post.prototype.dismissFlag = function (body) {
|
||||
const post = this.attrs.post;
|
||||
|
||||
delete post.data.relationships.flags;
|
||||
|
||||
this.subtree.invalidate();
|
||||
|
||||
if (app.flags.cache) {
|
||||
app.flags.cache.some((flag, i) => {
|
||||
if (flag.post() === post) {
|
||||
app.flags.cache.splice(i, 1);
|
||||
|
||||
if (app.flags.index === post) {
|
||||
let next = app.flags.cache[i];
|
||||
|
||||
if (!next) next = app.flags.cache[0];
|
||||
|
||||
if (next) {
|
||||
const nextPost = next.post();
|
||||
app.flags.index = nextPost;
|
||||
m.route.set(app.route.post(nextPost));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return app.request({
|
||||
url: app.forum.attribute('apiUrl') + post.apiEndpoint() + '/flags',
|
||||
method: 'DELETE',
|
||||
body,
|
||||
});
|
||||
};
|
||||
|
||||
Post.prototype.flagActionItems = function () {
|
||||
const items = new ItemList();
|
||||
|
||||
const controls = PostControls.destructiveControls(this.attrs.post);
|
||||
|
||||
Object.keys(controls.items).forEach((k) => {
|
||||
const attrs = controls.get(k).attrs;
|
||||
|
||||
attrs.className = 'Button';
|
||||
|
||||
extend(attrs, 'onclick', () => this.dismissFlag());
|
||||
});
|
||||
|
||||
items.add('controls', <div className="ButtonGroup">{controls.toArray()}</div>);
|
||||
|
||||
items.add(
|
||||
'dismiss',
|
||||
<Button className="Button" icon="far fa-eye-slash" onclick={this.dismissFlag.bind(this)}>
|
||||
{app.translator.trans('flarum-flags.forum.post.dismiss_flag_button')}
|
||||
</Button>,
|
||||
-100
|
||||
);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
extend(Post.prototype, 'content', function (vdom) {
|
||||
const post = this.attrs.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>
|
||||
);
|
||||
});
|
||||
|
||||
Post.prototype.flagReason = function (flag) {
|
||||
if (flag.type() === 'user') {
|
||||
const user = flag.user();
|
||||
const reason = flag.reason() ? app.translator.trans(`flarum-flags.forum.flag_post.reason_${flag.reason()}_label`) : null;
|
||||
const detail = flag.reasonDetail();
|
||||
const time = humanTime(flag.createdAt());
|
||||
|
||||
return [
|
||||
app.translator.trans(reason ? 'flarum-flags.forum.post.flagged_by_with_reason_text' : 'flarum-flags.forum.post.flagged_by_text', {
|
||||
time,
|
||||
user,
|
||||
reason,
|
||||
}),
|
||||
detail ? <span className="Post-flagged-detail">{detail}</span> : '',
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
19
extensions/flags/js/src/forum/compat.js
Normal file
19
extensions/flags/js/src/forum/compat.js
Normal file
@ -0,0 +1,19 @@
|
||||
import addFlagsToPosts from './addFlagsToPosts';
|
||||
import addFlagControl from './addFlagControl';
|
||||
import addFlagsDropdown from './addFlagsDropdown';
|
||||
import Flag from './models/Flag';
|
||||
import FlagList from './components/FlagList';
|
||||
import FlagPostModal from './components/FlagPostModal';
|
||||
import FlagsPage from './components/FlagsPage';
|
||||
import FlagsDropdown from './components/FlagsDropdown';
|
||||
|
||||
export default {
|
||||
'flags/addFlagsToPosts': addFlagsToPosts,
|
||||
'flags/addFlagControl': addFlagControl,
|
||||
'flags/addFlagsDropdown': addFlagsDropdown,
|
||||
'flags/models/Flag': Flag,
|
||||
'flags/components/FlagList': FlagList,
|
||||
'flags/components/FlagPostModal': FlagPostModal,
|
||||
'flags/components/FlagsPage': FlagsPage,
|
||||
'flags/components/FlagsDropdown': FlagsDropdown,
|
||||
};
|
65
extensions/flags/js/src/forum/components/FlagList.js
Normal file
65
extensions/flags/js/src/forum/components/FlagList.js
Normal file
@ -0,0 +1,65 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Component from 'flarum/common/Component';
|
||||
import Link from 'flarum/common/components/Link';
|
||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import avatar from 'flarum/common/helpers/avatar';
|
||||
import username from 'flarum/common/helpers/username';
|
||||
import icon from 'flarum/common/helpers/icon';
|
||||
import humanTime from 'flarum/common/helpers/humanTime';
|
||||
|
||||
export default class FlagList extends Component {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
this.state = this.attrs.state;
|
||||
}
|
||||
|
||||
view() {
|
||||
const flags = this.state.cache || [];
|
||||
|
||||
return (
|
||||
<div className="NotificationList FlagList">
|
||||
<div className="NotificationList-header">
|
||||
<h4 className="App-titleControl App-titleControl--text">{app.translator.trans('flarum-flags.forum.flagged_posts.title')}</h4>
|
||||
</div>
|
||||
<div className="NotificationList-content">
|
||||
<ul className="NotificationGroup-content">
|
||||
{flags.length ? (
|
||||
flags.map((flag) => {
|
||||
const post = flag.post();
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
href={app.route.post(post)}
|
||||
className="Notification Flag"
|
||||
onclick={(e) => {
|
||||
app.flags.index = post;
|
||||
e.redraw = false;
|
||||
}}
|
||||
>
|
||||
{avatar(post.user())}
|
||||
{icon('fas fa-flag', { className: 'Notification-icon' })}
|
||||
<span className="Notification-content">
|
||||
{app.translator.trans('flarum-flags.forum.flagged_posts.item_text', {
|
||||
username: username(post.user()),
|
||||
em: <em />,
|
||||
discussion: post.discussion().title(),
|
||||
})}
|
||||
</span>
|
||||
{humanTime(flag.createdAt())}
|
||||
<div className="Notification-excerpt">{post.contentPlain()}</div>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
) : !this.state.loading ? (
|
||||
<div className="NotificationList-empty">{app.translator.trans('flarum-flags.forum.flagged_posts.empty_text')}</div>
|
||||
) : (
|
||||
LoadingIndicator.component({ className: 'LoadingIndicator--block' })
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
171
extensions/flags/js/src/forum/components/FlagPostModal.js
Normal file
171
extensions/flags/js/src/forum/components/FlagPostModal.js
Normal file
@ -0,0 +1,171 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Modal from 'flarum/common/components/Modal';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import withAttr from 'flarum/common/utils/withAttr';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
|
||||
export default class FlagPostModal extends Modal {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.success = false;
|
||||
|
||||
this.reason = Stream('');
|
||||
this.reasonDetail = Stream('');
|
||||
}
|
||||
|
||||
className() {
|
||||
return 'FlagPostModal Modal--medium';
|
||||
}
|
||||
|
||||
title() {
|
||||
return app.translator.trans('flarum-flags.forum.flag_post.title');
|
||||
}
|
||||
|
||||
content() {
|
||||
if (this.success) {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">
|
||||
<p className="helpText">{app.translator.trans('flarum-flags.forum.flag_post.confirmation_message')}</p>
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.dismiss_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">
|
||||
<div className="Form-group">
|
||||
<div>{this.flagReasons().toArray()}</div>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading} disabled={!this.reason()}>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
flagReasons() {
|
||||
const items = new ItemList();
|
||||
const guidelinesUrl = app.forum.attribute('guidelinesUrl');
|
||||
|
||||
items.add(
|
||||
'off-topic',
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'off_topic'} value="off_topic" onclick={withAttr('value', this.reason)} />
|
||||
<strong>{app.translator.trans('flarum-flags.forum.flag_post.reason_off_topic_label')}</strong>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.reason_off_topic_text')}
|
||||
{this.reason() === 'off_topic' ? (
|
||||
<textarea
|
||||
className="FormControl"
|
||||
placeholder={app.translator.trans('flarum-flags.forum.flag_post.reason_details_placeholder')}
|
||||
value={this.reasonDetail()}
|
||||
oninput={withAttr('value', this.reasonDetail)}
|
||||
></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
70
|
||||
);
|
||||
|
||||
items.add(
|
||||
'inappropriate',
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="radio"
|
||||
name="reason"
|
||||
checked={this.reason() === 'inappropriate'}
|
||||
value="inappropriate"
|
||||
onclick={withAttr('value', this.reason)}
|
||||
/>
|
||||
<strong>{app.translator.trans('flarum-flags.forum.flag_post.reason_inappropriate_label')}</strong>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.reason_inappropriate_text', {
|
||||
a: guidelinesUrl ? <a href={guidelinesUrl} target="_blank" /> : undefined,
|
||||
})}
|
||||
{this.reason() === 'inappropriate' ? (
|
||||
<textarea
|
||||
className="FormControl"
|
||||
placeholder={app.translator.trans('flarum-flags.forum.flag_post.reason_details_placeholder')}
|
||||
value={this.reasonDetail()}
|
||||
oninput={withAttr('value', this.reasonDetail)}
|
||||
></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
60
|
||||
);
|
||||
|
||||
items.add(
|
||||
'spam',
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'spam'} value="spam" onclick={withAttr('value', this.reason)} />
|
||||
<strong>{app.translator.trans('flarum-flags.forum.flag_post.reason_spam_label')}</strong>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.reason_spam_text')}
|
||||
{this.reason() === 'spam' ? (
|
||||
<textarea
|
||||
className="FormControl"
|
||||
placeholder={app.translator.trans('flarum-flags.forum.flag_post.reason_details_placeholder')}
|
||||
value={this.reasonDetail()}
|
||||
oninput={withAttr('value', this.reasonDetail)}
|
||||
></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
50
|
||||
);
|
||||
|
||||
items.add(
|
||||
'other',
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'other'} value="other" onclick={withAttr('value', this.reason)} />
|
||||
<strong>{app.translator.trans('flarum-flags.forum.flag_post.reason_other_label')}</strong>
|
||||
{this.reason() === 'other' ? (
|
||||
<textarea className="FormControl" value={this.reasonDetail()} oninput={withAttr('value', this.reasonDetail)}></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
10
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
onsubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.loading = true;
|
||||
|
||||
app.store
|
||||
.createRecord('flags')
|
||||
.save(
|
||||
{
|
||||
reason: this.reason() === 'other' ? null : this.reason(),
|
||||
reasonDetail: this.reasonDetail(),
|
||||
relationships: {
|
||||
user: app.session.user,
|
||||
post: this.attrs.post,
|
||||
},
|
||||
},
|
||||
{ errorHandler: this.onerror.bind(this) }
|
||||
)
|
||||
.then(() => (this.success = true))
|
||||
.catch(() => {})
|
||||
.then(this.loaded.bind(this));
|
||||
}
|
||||
}
|
33
extensions/flags/js/src/forum/components/FlagsDropdown.js
Normal file
33
extensions/flags/js/src/forum/components/FlagsDropdown.js
Normal file
@ -0,0 +1,33 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import NotificationsDropdown from 'flarum/components/NotificationsDropdown';
|
||||
|
||||
import FlagList from './FlagList';
|
||||
|
||||
export default class FlagsDropdown extends NotificationsDropdown {
|
||||
static initAttrs(attrs) {
|
||||
attrs.label = attrs.label || app.translator.trans('flarum-flags.forum.flagged_posts.tooltip');
|
||||
attrs.icon = attrs.icon || 'fas fa-flag';
|
||||
|
||||
super.initAttrs(attrs);
|
||||
}
|
||||
|
||||
getMenu() {
|
||||
return (
|
||||
<div className={'Dropdown-menu ' + this.attrs.menuClassName} onclick={this.menuClick.bind(this)}>
|
||||
{this.showing ? FlagList.component({ state: this.attrs.state }) : ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
goToRoute() {
|
||||
m.route.set(app.route('flags'));
|
||||
}
|
||||
|
||||
getUnreadCount() {
|
||||
return app.flags.cache ? app.flags.cache.length : app.forum.attribute('flagCount');
|
||||
}
|
||||
|
||||
getNewCount() {
|
||||
return app.session.user.attribute('newFlagCount');
|
||||
}
|
||||
}
|
28
extensions/flags/js/src/forum/components/FlagsPage.js
Normal file
28
extensions/flags/js/src/forum/components/FlagsPage.js
Normal file
@ -0,0 +1,28 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Page from 'flarum/components/Page';
|
||||
|
||||
import FlagList from './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 {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.history.push('flags');
|
||||
|
||||
app.flags.load();
|
||||
|
||||
this.bodyClass = 'App--flags';
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="FlagsPage">
|
||||
<FlagList state={app.flags}></FlagList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
30
extensions/flags/js/src/forum/index.js
Normal file
30
extensions/flags/js/src/forum/index.js
Normal file
@ -0,0 +1,30 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Model from 'flarum/common/Model';
|
||||
|
||||
import Flag from './models/Flag';
|
||||
import FlagsPage from './components/FlagsPage';
|
||||
import FlagListState from './states/FlagListState';
|
||||
import addFlagControl from './addFlagControl';
|
||||
import addFlagsDropdown from './addFlagsDropdown';
|
||||
import addFlagsToPosts from './addFlagsToPosts';
|
||||
|
||||
app.initializers.add('flarum-flags', () => {
|
||||
app.store.models.posts.prototype.flags = Model.hasMany('flags');
|
||||
app.store.models.posts.prototype.canFlag = Model.attribute('canFlag');
|
||||
|
||||
app.store.models.flags = Flag;
|
||||
|
||||
app.routes.flags = { path: '/flags', component: FlagsPage };
|
||||
|
||||
app.flags = new FlagListState(app);
|
||||
|
||||
addFlagControl();
|
||||
addFlagsDropdown();
|
||||
addFlagsToPosts();
|
||||
});
|
||||
|
||||
// Expose compat API
|
||||
import flagsCompat from './compat';
|
||||
import { compat } from '@flarum/core/forum';
|
||||
|
||||
Object.assign(compat, flagsCompat);
|
15
extensions/flags/js/src/forum/models/Flag.js
Normal file
15
extensions/flags/js/src/forum/models/Flag.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Model from 'flarum/common/Model';
|
||||
|
||||
class Flag extends Model {}
|
||||
|
||||
Object.assign(Flag.prototype, {
|
||||
type: Model.attribute('type'),
|
||||
reason: Model.attribute('reason'),
|
||||
reasonDetail: Model.attribute('reasonDetail'),
|
||||
createdAt: Model.attribute('createdAt', Model.transformDate),
|
||||
|
||||
post: Model.hasOne('post'),
|
||||
user: Model.hasOne('user'),
|
||||
});
|
||||
|
||||
export default Flag;
|
37
extensions/flags/js/src/forum/states/FlagListState.js
Normal file
37
extensions/flags/js/src/forum/states/FlagListState.js
Normal file
@ -0,0 +1,37 @@
|
||||
export default class FlagListState {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
/**
|
||||
* Whether or not the flags are loading.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load flags into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (this.cache && !this.app.session.user.attribute('newFlagCount')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
this.app.store
|
||||
.find('flags')
|
||||
.then((flags) => {
|
||||
this.app.session.user.pushAttributes({ newFlagCount: 0 });
|
||||
this.cache = flags.sort((a, b) => b.createdAt() - a.createdAt());
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
1
extensions/flags/js/webpack.config.js
Normal file
1
extensions/flags/js/webpack.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('flarum-webpack-config')();
|
4512
extensions/flags/js/yarn.lock
Normal file
4512
extensions/flags/js/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
0
extensions/flags/less/admin.less
Normal file
0
extensions/flags/less/admin.less
Normal file
72
extensions/flags/less/forum.less
Normal file
72
extensions/flags/less/forum.less
Normal file
@ -0,0 +1,72 @@
|
||||
.Post--flagged {
|
||||
padding-top: 0 !important;
|
||||
border: 2px solid @primary-color;
|
||||
}
|
||||
|
||||
.Post-header .item-flagged {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.Post-flagged {
|
||||
background: @primary-color;
|
||||
margin-top: -2px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: -22px;
|
||||
margin-right: -22px;
|
||||
padding: 10px;
|
||||
border-radius: @border-radius @border-radius 0 0;
|
||||
overflow: hidden;
|
||||
.light-contents(@color: @body-bg; @control-color: @body-bg);
|
||||
|
||||
@media @tablet-up {
|
||||
margin-left: -22px - 85px;
|
||||
}
|
||||
|
||||
&, a {
|
||||
color: @body-bg !important;
|
||||
}
|
||||
}
|
||||
.Post-flagged-flags {
|
||||
@media @tablet-up {
|
||||
float: left;
|
||||
}
|
||||
|
||||
font-size: 14px;
|
||||
margin: 7px 10px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
.Post-flagged-detail {
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.Post-flagged-actions {
|
||||
@media @tablet-up {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.Post-flagged-actions .Button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.FlagsDropdown .Dropdown-toggle {
|
||||
.Button-label,
|
||||
.Button-caret {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.FlagPostModal {
|
||||
.Form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.checkbox {
|
||||
margin-bottom: 12px;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
}
|
64
extensions/flags/locale/en.yml
Normal file
64
extensions/flags/locale/en.yml
Normal file
@ -0,0 +1,64 @@
|
||||
flarum-flags:
|
||||
|
||||
##
|
||||
# UNIQUE KEYS - The following keys are used in only one location each.
|
||||
##
|
||||
|
||||
# Translations in this namespace are used by the admin interface.
|
||||
admin:
|
||||
|
||||
# These translations are used in the Permissions page of the admin interface.
|
||||
permissions:
|
||||
flag_posts_label: Flag posts
|
||||
view_flags_label: View flagged posts
|
||||
|
||||
# These translations are used in the Flags Settings modal dialog.
|
||||
settings:
|
||||
flag_own_posts_label: Allow users to flag their own posts
|
||||
guidelines_url_label: Community Guidelines URL
|
||||
title: Flags Settings
|
||||
|
||||
# Translations in this namespace are used by the forum user interface.
|
||||
forum:
|
||||
|
||||
# These translations are used by the Flag Post modal dialog.
|
||||
flag_post:
|
||||
confirmation_message: Thank you for flagging this post. Our moderators will look into it.
|
||||
dismiss_button: => core.ref.okay
|
||||
reason_details_placeholder: Additional details (optional)
|
||||
reason_inappropriate_label: Inappropriate
|
||||
reason_inappropriate_text: "This post is offensive, abusive, or violates our <a>community guidelines</a>."
|
||||
reason_missing_message: Please provide some details for our moderators.
|
||||
reason_off_topic_label: "Off-topic"
|
||||
reason_off_topic_text: This post is not relevant to the current discussion and should be moved elsewhere.
|
||||
reason_other_label: Other (please specify)
|
||||
reason_spam_label: Spam
|
||||
reason_spam_text: This post is an advertisement.
|
||||
submit_button: => flarum-flags.ref.flag_post
|
||||
title: => flarum-flags.ref.flag_post
|
||||
|
||||
# These translations are used by the Flagged Posts dropdown, a.k.a. "the flag".
|
||||
flagged_posts:
|
||||
empty_text: No Flags
|
||||
item_text: "{username} in <em>{discussion}</em>"
|
||||
title: => flarum-flags.ref.flagged_posts
|
||||
tooltip: => flarum-flags.ref.flagged_posts
|
||||
|
||||
# These translations are used by the frame displayed around flagged posts.
|
||||
post:
|
||||
dismiss_flag_button: Dismiss Flag
|
||||
flagged_by_text: "Flagged by {username}"
|
||||
flagged_by_with_reason_text: "Flagged by {username} as {reason}"
|
||||
|
||||
# These translations are used by the post control buttons.
|
||||
post_controls:
|
||||
flag_button: Flag
|
||||
|
||||
##
|
||||
# REUSED TRANSLATIONS - These keys should not be used directly in code!
|
||||
##
|
||||
|
||||
# Translations in this namespace are referenced by two or more unique keys.
|
||||
ref:
|
||||
flag_post: Flag Post
|
||||
flagged_posts: Flagged Posts
|
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Database\Migration;
|
||||
|
||||
return Migration::addColumns('users', [
|
||||
'flags_read_time' => ['dateTime', 'nullable' => true]
|
||||
]);
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Database\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
return Migration::createTable(
|
||||
'flags',
|
||||
function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('post_id')->unsigned();
|
||||
$table->string('type');
|
||||
$table->integer('user_id')->unsigned()->nullable();
|
||||
$table->string('reason')->nullable();
|
||||
$table->string('reason_detail')->nullable();
|
||||
$table->dateTime('time');
|
||||
}
|
||||
);
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Database\Migration;
|
||||
use Flarum\Group\Group;
|
||||
|
||||
return Migration::addPermissions([
|
||||
'discussion.flagPosts' => Group::MEMBER_ID,
|
||||
'discussion.viewFlags' => Group::MODERATOR_ID
|
||||
]);
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Database\Migration;
|
||||
|
||||
return Migration::renameColumn('flags', 'time', 'created_at');
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
// Delete rows with non-existent entities so that we will be able to create
|
||||
// foreign keys without any issues.
|
||||
$schema->getConnection()
|
||||
->table('flags')
|
||||
->whereNotExists(function ($query) {
|
||||
$query->selectRaw(1)->from('posts')->whereColumn('id', 'post_id');
|
||||
})
|
||||
->delete();
|
||||
|
||||
$schema->getConnection()
|
||||
->table('flags')
|
||||
->whereNotExists(function ($query) {
|
||||
$query->selectRaw(1)->from('users')->whereColumn('id', 'user_id');
|
||||
})
|
||||
->update(['user_id' => null]);
|
||||
|
||||
$schema->table('flags', function (Blueprint $table) {
|
||||
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
},
|
||||
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->table('flags', function (Blueprint $table) {
|
||||
$table->dropForeign(['post_id']);
|
||||
$table->dropForeign(['user_id']);
|
||||
});
|
||||
}
|
||||
];
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Database\Migration;
|
||||
|
||||
return Migration::renameColumn('users', 'flags_read_time', 'read_flags_at');
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
$schema->table('flags', function (Blueprint $table) {
|
||||
$table->index('created_at');
|
||||
});
|
||||
},
|
||||
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->table('flags', function (Blueprint $table) {
|
||||
$table->dropIndex(['created_at']);
|
||||
});
|
||||
}
|
||||
];
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
$schema->table('flags', function (Blueprint $table) {
|
||||
$table->text('reason_detail')->change();
|
||||
});
|
||||
},
|
||||
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->table('flags', function (Blueprint $table) {
|
||||
$table->string('reason_detail')->change();
|
||||
});
|
||||
}
|
||||
];
|
58
extensions/flags/src/Access/ScopeFlagVisibility.php
Normal file
58
extensions/flags/src/Access/ScopeFlagVisibility.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Access;
|
||||
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopeFlagVisibility
|
||||
{
|
||||
/**
|
||||
* @var ExtensionManager
|
||||
*/
|
||||
protected $extensions;
|
||||
|
||||
public function __construct(ExtensionManager $extensions)
|
||||
{
|
||||
$this->extensions = $extensions;
|
||||
}
|
||||
|
||||
public function __invoke(User $actor, Builder $query)
|
||||
{
|
||||
if ($this->extensions->isEnabled('flarum-tags')) {
|
||||
$query
|
||||
->select('flags.*')
|
||||
->leftJoin('posts', 'posts.id', '=', 'flags.post_id')
|
||||
->leftJoin('discussions', 'discussions.id', '=', 'posts.discussion_id')
|
||||
->whereNotExists(function ($query) use ($actor) {
|
||||
return $query->selectRaw('1')
|
||||
->from('discussion_tag')
|
||||
->whereNotIn('tag_id', function ($query) use ($actor) {
|
||||
Tag::query()->setQuery($query->from('tags'))->whereHasPermission($actor, 'discussion.viewFlags')->select('tags.id');
|
||||
})
|
||||
->whereColumn('discussions.id', 'discussion_id');
|
||||
});
|
||||
|
||||
if (! $actor->hasPermission('discussion.viewFlags')) {
|
||||
$query->whereExists(function ($query) {
|
||||
return $query->selectRaw('1')
|
||||
->from('discussion_tag')
|
||||
->whereColumn('discussions.id', 'discussion_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (! $actor->hasPermission('discussion.viewFlags')) {
|
||||
$query->orWhere('flags.user_id', $actor->id);
|
||||
}
|
||||
}
|
||||
}
|
46
extensions/flags/src/AddCanFlagAttribute.php
Normal file
46
extensions/flags/src/AddCanFlagAttribute.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags;
|
||||
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\User;
|
||||
|
||||
class AddCanFlagAttribute
|
||||
{
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function __invoke(PostSerializer $serializer, Post $post)
|
||||
{
|
||||
return $serializer->getActor()->can('flag', $post) && $this->checkFlagOwnPostSetting($serializer->getActor(), $post);
|
||||
}
|
||||
|
||||
protected function checkFlagOwnPostSetting(User $actor, Post $post): bool
|
||||
{
|
||||
if ($actor->id === $post->user_id) {
|
||||
// If $actor is the post author, check to see if the setting is enabled
|
||||
return (bool) $this->settings->get('flarum-flags.can_flag_own');
|
||||
}
|
||||
// $actor is not the post author
|
||||
return true;
|
||||
}
|
||||
}
|
52
extensions/flags/src/AddFlagsApiAttributes.php
Executable file
52
extensions/flags/src/AddFlagsApiAttributes.php
Executable file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags;
|
||||
|
||||
use Flarum\Api\Serializer\ForumSerializer;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\User;
|
||||
|
||||
class AddFlagsApiAttributes
|
||||
{
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function __invoke(ForumSerializer $serializer)
|
||||
{
|
||||
$attributes = [
|
||||
'canViewFlags' => $serializer->getActor()->hasPermissionLike('discussion.viewFlags')
|
||||
];
|
||||
|
||||
if ($attributes['canViewFlags']) {
|
||||
$attributes['flagCount'] = (int) $this->getFlagCount($serializer->getActor());
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @return int
|
||||
*/
|
||||
protected function getFlagCount(User $actor)
|
||||
{
|
||||
return Flag::whereVisibleTo($actor)->distinct()->count('flags.post_id');
|
||||
}
|
||||
}
|
36
extensions/flags/src/AddNewFlagCountAttribute.php
Normal file
36
extensions/flags/src/AddNewFlagCountAttribute.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags;
|
||||
|
||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||
use Flarum\User\User;
|
||||
|
||||
class AddNewFlagCountAttribute
|
||||
{
|
||||
public function __invoke(CurrentUserSerializer $serializer, User $user)
|
||||
{
|
||||
return (int) $this->getNewFlagCount($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @return int
|
||||
*/
|
||||
protected function getNewFlagCount(User $actor)
|
||||
{
|
||||
$query = Flag::whereVisibleTo($actor);
|
||||
|
||||
if ($time = $actor->read_flags_at) {
|
||||
$query->where('flags.created_at', '>', $time);
|
||||
}
|
||||
|
||||
return $query->distinct()->count('flags.post_id');
|
||||
}
|
||||
}
|
59
extensions/flags/src/Api/Controller/CreateFlagController.php
Normal file
59
extensions/flags/src/Api/Controller/CreateFlagController.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractCreateController;
|
||||
use Flarum\Flags\Api\Serializer\FlagSerializer;
|
||||
use Flarum\Flags\Command\CreateFlag;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class CreateFlagController extends AbstractCreateController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $serializer = FlagSerializer::class;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $include = [
|
||||
'post',
|
||||
'post.flags',
|
||||
'user'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $bus
|
||||
*/
|
||||
public function __construct(Dispatcher $bus)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
return $this->bus->dispatch(
|
||||
new CreateFlag(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data', []))
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractDeleteController;
|
||||
use Flarum\Flags\Command\DeleteFlags;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class DeleteFlagsController extends AbstractDeleteController
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $bus
|
||||
*/
|
||||
public function __construct(Dispatcher $bus)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$this->bus->dispatch(
|
||||
new DeleteFlags(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request), $request->getParsedBody())
|
||||
);
|
||||
}
|
||||
}
|
62
extensions/flags/src/Api/Controller/ListFlagsController.php
Normal file
62
extensions/flags/src/Api/Controller/ListFlagsController.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\Flags\Api\Serializer\FlagSerializer;
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class ListFlagsController extends AbstractListController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $serializer = FlagSerializer::class;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $include = [
|
||||
'user',
|
||||
'post',
|
||||
'post.user',
|
||||
'post.discussion'
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
$actor->assertRegistered();
|
||||
|
||||
$actor->read_flags_at = time();
|
||||
$actor->save();
|
||||
|
||||
$flags = Flag::whereVisibleTo($actor)
|
||||
->latest('flags.created_at')
|
||||
->groupBy('post_id')
|
||||
->get();
|
||||
|
||||
if (in_array('post.user', $include)) {
|
||||
$include[] = 'post.user.groups';
|
||||
}
|
||||
|
||||
$this->loadRelations($flags, $include);
|
||||
|
||||
return $flags;
|
||||
}
|
||||
}
|
51
extensions/flags/src/Api/Serializer/FlagSerializer.php
Normal file
51
extensions/flags/src/Api/Serializer/FlagSerializer.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Api\Serializer;
|
||||
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use Flarum\Api\Serializer\BasicUserSerializer;
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
|
||||
class FlagSerializer extends AbstractSerializer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $type = 'flags';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDefaultAttributes($flag)
|
||||
{
|
||||
return [
|
||||
'type' => $flag->type,
|
||||
'reason' => $flag->reason,
|
||||
'reasonDetail' => $flag->reason_detail,
|
||||
'createdAt' => $this->formatDate($flag->created_at),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Tobscure\JsonApi\Relationship
|
||||
*/
|
||||
protected function post($flag)
|
||||
{
|
||||
return $this->hasOne($flag, PostSerializer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Tobscure\JsonApi\Relationship
|
||||
*/
|
||||
protected function user($flag)
|
||||
{
|
||||
return $this->hasOne($flag, BasicUserSerializer::class);
|
||||
}
|
||||
}
|
39
extensions/flags/src/Command/CreateFlag.php
Normal file
39
extensions/flags/src/Command/CreateFlag.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Command;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
||||
class CreateFlag
|
||||
{
|
||||
/**
|
||||
* The user performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The attributes of the new flag.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param User $actor The user performing the action.
|
||||
* @param array $data The attributes of the new flag.
|
||||
*/
|
||||
public function __construct(User $actor, array $data)
|
||||
{
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
110
extensions/flags/src/Command/CreateFlagHandler.php
Normal file
110
extensions/flags/src/Command/CreateFlagHandler.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Command;
|
||||
|
||||
use Flarum\Flags\Event\Created;
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\Foundation\ValidationException;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\Exception\PermissionDeniedException;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Tobscure\JsonApi\Exception\InvalidParameterException;
|
||||
|
||||
class CreateFlagHandler
|
||||
{
|
||||
/**
|
||||
* @var PostRepository
|
||||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @param PostRepository $posts
|
||||
* @param TranslatorInterface $translator
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function __construct(PostRepository $posts, TranslatorInterface $translator, SettingsRepositoryInterface $settings, Dispatcher $events)
|
||||
{
|
||||
$this->posts = $posts;
|
||||
$this->translator = $translator;
|
||||
$this->settings = $settings;
|
||||
$this->events = $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CreateFlag $command
|
||||
* @return Flag
|
||||
* @throws InvalidParameterException
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function handle(CreateFlag $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
$data = $command->data;
|
||||
|
||||
$postId = Arr::get($data, 'relationships.post.data.id');
|
||||
$post = $this->posts->findOrFail($postId, $actor);
|
||||
|
||||
if (! ($post instanceof CommentPost)) {
|
||||
throw new InvalidParameterException;
|
||||
}
|
||||
|
||||
$actor->assertCan('flag', $post);
|
||||
|
||||
if ($actor->id === $post->user_id && ! $this->settings->get('flarum-flags.can_flag_own')) {
|
||||
throw new PermissionDeniedException();
|
||||
}
|
||||
|
||||
if (Arr::get($data, 'attributes.reason') === null && Arr::get($data, 'attributes.reasonDetail') === '') {
|
||||
throw new ValidationException([
|
||||
'message' => $this->translator->trans('flarum-flags.forum.flag_post.reason_missing_message')
|
||||
]);
|
||||
}
|
||||
|
||||
Flag::unguard();
|
||||
|
||||
$flag = Flag::firstOrNew([
|
||||
'post_id' => $post->id,
|
||||
'user_id' => $actor->id
|
||||
]);
|
||||
|
||||
$flag->post_id = $post->id;
|
||||
$flag->user_id = $actor->id;
|
||||
$flag->type = 'user';
|
||||
$flag->reason = Arr::get($data, 'attributes.reason');
|
||||
$flag->reason_detail = Arr::get($data, 'attributes.reasonDetail');
|
||||
$flag->created_at = time();
|
||||
|
||||
$flag->save();
|
||||
|
||||
$this->events->dispatch(new Created($flag, $actor, $data));
|
||||
|
||||
return $flag;
|
||||
}
|
||||
}
|
46
extensions/flags/src/Command/DeleteFlags.php
Normal file
46
extensions/flags/src/Command/DeleteFlags.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Command;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
||||
class DeleteFlags
|
||||
{
|
||||
/**
|
||||
* The ID of the post to delete flags 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 flags 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;
|
||||
}
|
||||
}
|
63
extensions/flags/src/Command/DeleteFlagsHandler.php
Normal file
63
extensions/flags/src/Command/DeleteFlagsHandler.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Command;
|
||||
|
||||
use Flarum\Flags\Event\Deleting;
|
||||
use Flarum\Flags\Event\FlagsWillBeDeleted;
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
|
||||
class DeleteFlagsHandler
|
||||
{
|
||||
/**
|
||||
* @var PostRepository
|
||||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @param PostRepository $posts
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function __construct(PostRepository $posts, Dispatcher $events)
|
||||
{
|
||||
$this->posts = $posts;
|
||||
$this->events = $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DeleteFlags $command
|
||||
* @return Flag
|
||||
*/
|
||||
public function handle(DeleteFlags $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
|
||||
$post = $this->posts->findOrFail($command->postId, $actor);
|
||||
|
||||
$actor->assertCan('viewFlags', $post->discussion);
|
||||
|
||||
// Deprecated, removed v2.0
|
||||
$this->events->dispatch(new FlagsWillBeDeleted($post, $actor, $command->data));
|
||||
|
||||
foreach ($post->flags as $flag) {
|
||||
$this->events->dispatch(new Deleting($flag, $actor, $command->data));
|
||||
}
|
||||
|
||||
$post->flags()->delete();
|
||||
|
||||
return $post;
|
||||
}
|
||||
}
|
43
extensions/flags/src/Event/Created.php
Normal file
43
extensions/flags/src/Event/Created.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Event;
|
||||
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Created
|
||||
{
|
||||
/**
|
||||
* @var Flag
|
||||
*/
|
||||
public $flag;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param Flag $flag
|
||||
* @param User $actor
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(Flag $flag, User $actor, array $data = [])
|
||||
{
|
||||
$this->flag = $flag;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
43
extensions/flags/src/Event/Deleting.php
Normal file
43
extensions/flags/src/Event/Deleting.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Event;
|
||||
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Deleting
|
||||
{
|
||||
/**
|
||||
* @var Flag
|
||||
*/
|
||||
public $flag;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param Flag $flag
|
||||
* @param User $actor
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(Flag $flag, User $actor, array $data = [])
|
||||
{
|
||||
$this->flag = $flag;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
47
extensions/flags/src/Event/FlagsWillBeDeleted.php
Normal file
47
extensions/flags/src/Event/FlagsWillBeDeleted.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Event;
|
||||
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\User\User;
|
||||
|
||||
/**
|
||||
* @deprecated v2.0
|
||||
* Listen for Flarum\Flags\Event\Deleting instead
|
||||
*/
|
||||
class FlagsWillBeDeleted
|
||||
{
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
41
extensions/flags/src/Flag.php
Normal file
41
extensions/flags/src/Flag.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Database\ScopeVisibilityTrait;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Flag extends AbstractModel
|
||||
{
|
||||
use ScopeVisibilityTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dates = ['created_at'];
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
return $this->belongsTo(Post::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
23
extensions/flags/src/Listener/DeleteFlags.php
Normal file
23
extensions/flags/src/Listener/DeleteFlags.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Listener;
|
||||
|
||||
use Flarum\Post\Event\Deleted;
|
||||
|
||||
class DeleteFlags
|
||||
{
|
||||
/**
|
||||
* @param Deleted $event
|
||||
*/
|
||||
public function handle(Deleted $event)
|
||||
{
|
||||
$event->post->flags()->delete();
|
||||
}
|
||||
}
|
64
extensions/flags/src/PrepareFlagsApiData.php
Executable file
64
extensions/flags/src/PrepareFlagsApiData.php
Executable file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags;
|
||||
|
||||
use Flarum\Api\Controller;
|
||||
use Flarum\Flags\Api\Controller\CreateFlagController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class PrepareFlagsApiData
|
||||
{
|
||||
public function __invoke(Controller\AbstractSerializeController $controller, $data, ServerRequestInterface $request)
|
||||
{
|
||||
// For any API action that allows the 'flags' relationship to be
|
||||
// included, we need to preload this relationship onto the data (Post
|
||||
// models) so that we can selectively expose only the flags that the
|
||||
// user has permission to view.
|
||||
if ($controller instanceof Controller\ShowDiscussionController) {
|
||||
if ($data->relationLoaded('posts')) {
|
||||
$posts = $data->getRelation('posts');
|
||||
}
|
||||
}
|
||||
|
||||
if ($controller instanceof Controller\ListPostsController) {
|
||||
$posts = $data->all();
|
||||
}
|
||||
|
||||
if ($controller instanceof Controller\ShowPostController) {
|
||||
$posts = [$data];
|
||||
}
|
||||
|
||||
if ($controller instanceof CreateFlagController) {
|
||||
$posts = [$data->post];
|
||||
}
|
||||
|
||||
if (isset($posts)) {
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$postsWithPermission = [];
|
||||
|
||||
foreach ($posts as $post) {
|
||||
if (is_object($post)) {
|
||||
$post->setRelation('flags', null);
|
||||
|
||||
if ($actor->can('viewFlags', $post->discussion)) {
|
||||
$postsWithPermission[] = $post;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($postsWithPermission)) {
|
||||
(new Collection($postsWithPermission))
|
||||
->load('flags', 'flags.user');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
0
extensions/flags/tests/fixtures/.gitkeep
vendored
Normal file
0
extensions/flags/tests/fixtures/.gitkeep
vendored
Normal file
133
extensions/flags/tests/integration/api/flags/ListTest.php
Normal file
133
extensions/flags/tests/integration/api/flags/ListTest.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Tests\integration\api\flags;
|
||||
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->extension('flarum-flags');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
[
|
||||
'id' => 3,
|
||||
'username' => 'mod',
|
||||
'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', // BCrypt hash for "too-obscure"
|
||||
'email' => 'normal2@machine.local',
|
||||
'is_email_confirmed' => 1,
|
||||
]
|
||||
],
|
||||
'group_user' => [
|
||||
['group_id' => Group::MODERATOR_ID, 'user_id' => 3]
|
||||
],
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'discussion.viewFlags'],
|
||||
],
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => '', 'user_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
['id' => 1, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 2, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 3, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
],
|
||||
'flags' => [
|
||||
['id' => 1, 'post_id' => 1, 'user_id' => 1],
|
||||
['id' => 2, 'post_id' => 1, 'user_id' => 2],
|
||||
['id' => 3, 'post_id' => 1, 'user_id' => 3],
|
||||
['id' => 4, 'post_id' => 2, 'user_id' => 2],
|
||||
['id' => 5, 'post_id' => 3, 'user_id' => 1],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function admin_can_see_one_flag_per_post()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
'authenticatedAs' => 1
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
$this->assertEqualsCanonicalizing(['1', '4', '5'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function regular_user_sees_own_flags()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
'authenticatedAs' => 2
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
$this->assertEqualsCanonicalizing(['2', '4'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mod_can_see_one_flag_per_post()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
'authenticatedAs' => 3
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
$this->assertEqualsCanonicalizing(['1', '4', '5'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function guest_cant_see_flags()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags')
|
||||
);
|
||||
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Flags\Tests\integration\api\flags;
|
||||
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListWithTagsTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->extension('flarum-flags');
|
||||
$this->extension('flarum-tags');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'tags' => [
|
||||
['id' => 1, 'name' => 'Unrestricted', 'slug' => '1', 'position' => 0, 'parent_id' => null],
|
||||
['id' => 2, 'name' => 'Mods can view discussions', 'slug' => '2', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
|
||||
['id' => 3, 'name' => 'Mods can view flags', 'slug' => '3', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
|
||||
['id' => 4, 'name' => 'Mods can view discussions and flags', 'slug' => '4', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
|
||||
],
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
[
|
||||
'id' => 3,
|
||||
'username' => 'mod',
|
||||
'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', // BCrypt hash for "too-obscure"
|
||||
'email' => 'normal2@machine.local',
|
||||
'is_email_confirmed' => 1,
|
||||
]
|
||||
],
|
||||
'group_user' => [
|
||||
['group_id' => Group::MODERATOR_ID, 'user_id' => 3]
|
||||
],
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'discussion.viewFlags'],
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag2.viewDiscussions'],
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag3.discussion.viewFlags'],
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.viewDiscussions'],
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.discussion.viewFlags'],
|
||||
],
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => 'no tags', 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 2, 'title' => 'has tags where mods can view discussions but not flags', 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'has tags where mods can view flags but not discussions', 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 4, 'title' => 'has tags where mods can view discussions and flags', 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 5, 'title' => 'has unrestricted tag', 'user_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
'discussion_tag' => [
|
||||
['discussion_id' => 2, 'tag_id' => 2],
|
||||
['discussion_id' => 3, 'tag_id' => 3],
|
||||
['discussion_id' => 4, 'tag_id' => 4],
|
||||
['discussion_id' => 5, 'tag_id' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
// From regular ListTest
|
||||
['id' => 1, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 2, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 3, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
// In tags
|
||||
['id' => 4, 'discussion_id' => 2, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 5, 'discussion_id' => 3, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 6, 'discussion_id' => 4, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 7, 'discussion_id' => 5, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
],
|
||||
'flags' => [
|
||||
// From regular ListTest
|
||||
['id' => 1, 'post_id' => 1, 'user_id' => 1],
|
||||
['id' => 2, 'post_id' => 1, 'user_id' => 2],
|
||||
['id' => 3, 'post_id' => 1, 'user_id' => 3],
|
||||
['id' => 4, 'post_id' => 2, 'user_id' => 2],
|
||||
['id' => 5, 'post_id' => 3, 'user_id' => 1],
|
||||
// In tags
|
||||
['id' => 6, 'post_id' => 4, 'user_id' => 1],
|
||||
['id' => 7, 'post_id' => 5, 'user_id' => 1],
|
||||
['id' => 8, 'post_id' => 6, 'user_id' => 1],
|
||||
['id' => 9, 'post_id' => 7, 'user_id' => 1],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function admin_can_see_one_flag_per_post()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
'authenticatedAs' => 1
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
$this->assertEqualsCanonicalizing(['1', '4', '5', '6', '7', '8', '9'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function regular_user_sees_own_flags()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
'authenticatedAs' => 2
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
$this->assertEqualsCanonicalizing(['2', '4'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mod_can_see_one_flag_per_post()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
'authenticatedAs' => 3
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
// 7 is included, even though mods can't view discussions.
|
||||
// This is because the UI doesnt allow discussions.viewFlags without viewDiscussions.
|
||||
$this->assertEqualsCanonicalizing(['1', '4', '5', '7', '8', '9'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function guest_cant_see_flags()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags')
|
||||
);
|
||||
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
}
|
||||
}
|
16
extensions/flags/tests/integration/setup.php
Normal file
16
extensions/flags/tests/integration/setup.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Testing\integration\Setup\SetupScript;
|
||||
|
||||
require __DIR__.'/../../vendor/autoload.php';
|
||||
|
||||
$setup = new SetupScript();
|
||||
|
||||
$setup->run();
|
24
extensions/flags/tests/phpunit.integration.xml
Normal file
24
extensions/flags/tests/phpunit.integration.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="true"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">../src/</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
<testsuites>
|
||||
<testsuite name="Flarum Integration Tests">
|
||||
<directory suffix="Test.php">./integration</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
27
extensions/flags/tests/phpunit.unit.xml
Normal file
27
extensions/flags/tests/phpunit.unit.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">../src/</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
<testsuites>
|
||||
<testsuite name="Flarum Unit Tests">
|
||||
<directory suffix="Test.php">./unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<listeners>
|
||||
<listener class="\Mockery\Adapter\Phpunit\TestListener" />
|
||||
</listeners>
|
||||
</phpunit>
|
0
extensions/flags/tests/unit/.gitkeep
Normal file
0
extensions/flags/tests/unit/.gitkeep
Normal file
Reference in New Issue
Block a user