From 937b70bdf860f893e855f42734db6119624cf941 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 22 Sep 2015 18:14:56 +0930 Subject: [PATCH] Initial commit --- extensions/approval/.editorconfig | 19 ++ extensions/approval/.eslintignore | 5 + extensions/approval/.eslintrc | 175 ++++++++++++++++++ extensions/approval/.gitignore | 4 + extensions/approval/bootstrap.php | 5 + extensions/approval/build.sh | 65 +++++++ extensions/approval/composer.json | 7 + extensions/approval/flarum.json | 22 +++ extensions/approval/js/.gitignore | 3 + extensions/approval/js/admin/Gulpfile.js | 7 + extensions/approval/js/admin/package.json | 7 + extensions/approval/js/admin/src/main.js | 21 +++ extensions/approval/js/forum/Gulpfile.js | 7 + extensions/approval/js/forum/package.json | 7 + extensions/approval/js/forum/src/main.js | 56 ++++++ extensions/approval/less/admin/extension.less | 0 extensions/approval/less/forum/extension.less | 7 + extensions/approval/locale/en.yml | 2 + ..._011527_add_is_approved_to_discussions.php | 33 ++++ ..._09_21_011706_add_is_approved_to_posts.php | 33 ++++ .../approval/src/Events/PostWasApproved.php | 31 ++++ extensions/approval/src/Extension.php | 16 ++ .../src/Listeners/AddApiAttributes.php | 26 +++ .../src/Listeners/AddClientAssets.php | 44 +++++ .../approval/src/Listeners/ApproveContent.php | 47 +++++ .../src/Listeners/HideUnapprovedContent.php | 44 +++++ .../src/Listeners/UnapproveNewContent.php | 47 +++++ 27 files changed, 740 insertions(+) create mode 100644 extensions/approval/.editorconfig create mode 100644 extensions/approval/.eslintignore create mode 100644 extensions/approval/.eslintrc create mode 100644 extensions/approval/.gitignore create mode 100644 extensions/approval/bootstrap.php create mode 100644 extensions/approval/build.sh create mode 100644 extensions/approval/composer.json create mode 100644 extensions/approval/flarum.json create mode 100644 extensions/approval/js/.gitignore create mode 100644 extensions/approval/js/admin/Gulpfile.js create mode 100644 extensions/approval/js/admin/package.json create mode 100644 extensions/approval/js/admin/src/main.js create mode 100644 extensions/approval/js/forum/Gulpfile.js create mode 100644 extensions/approval/js/forum/package.json create mode 100644 extensions/approval/js/forum/src/main.js create mode 100644 extensions/approval/less/admin/extension.less create mode 100644 extensions/approval/less/forum/extension.less create mode 100644 extensions/approval/locale/en.yml create mode 100644 extensions/approval/migrations/2015_09_21_011527_add_is_approved_to_discussions.php create mode 100644 extensions/approval/migrations/2015_09_21_011706_add_is_approved_to_posts.php create mode 100644 extensions/approval/src/Events/PostWasApproved.php create mode 100644 extensions/approval/src/Extension.php create mode 100644 extensions/approval/src/Listeners/AddApiAttributes.php create mode 100644 extensions/approval/src/Listeners/AddClientAssets.php create mode 100644 extensions/approval/src/Listeners/ApproveContent.php create mode 100644 extensions/approval/src/Listeners/HideUnapprovedContent.php create mode 100644 extensions/approval/src/Listeners/UnapproveNewContent.php diff --git a/extensions/approval/.editorconfig b/extensions/approval/.editorconfig new file mode 100644 index 000000000..87694ddab --- /dev/null +++ b/extensions/approval/.editorconfig @@ -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 diff --git a/extensions/approval/.eslintignore b/extensions/approval/.eslintignore new file mode 100644 index 000000000..86b7c8854 --- /dev/null +++ b/extensions/approval/.eslintignore @@ -0,0 +1,5 @@ +**/bower_components/**/* +**/node_modules/**/* +vendor/**/* +**/Gulpfile.js +**/dist/**/* diff --git a/extensions/approval/.eslintrc b/extensions/approval/.eslintrc new file mode 100644 index 000000000..9e89e6ba6 --- /dev/null +++ b/extensions/approval/.eslintrc @@ -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 + } +} diff --git a/extensions/approval/.gitignore b/extensions/approval/.gitignore new file mode 100644 index 000000000..a4f3b125e --- /dev/null +++ b/extensions/approval/.gitignore @@ -0,0 +1,4 @@ +/vendor +composer.phar +.DS_Store +Thumbs.db diff --git a/extensions/approval/bootstrap.php b/extensions/approval/bootstrap.php new file mode 100644 index 000000000..6c3da11ab --- /dev/null +++ b/extensions/approval/bootstrap.php @@ -0,0 +1,5 @@ + ${release}/release.zip + +cd ${release} +unzip release.zip -d ./ +rm release.zip + +# Delete files +rm -rf ${release}/build.sh + +# Install all Composer dependencies +composer install --prefer-dist --optimize-autoloader --ignore-platform-reqs --no-dev + +cd "${release}/js" +if [ -f bower.json ]; then +bower install +fi + +for app in forum admin; do + cd "${release}/js" + + if [ -d $app ]; then + cd $app + + if [ -f bower.json ]; then + bower install + fi + + npm install + gulp --production + rm -rf node_modules bower_components + fi +done + +rm -rf "${release}/extensions/${extension}/js/bower_components" +wait + +# Finally, create the release archive +cd ${release} +find . -type d -exec chmod 0750 {} + +find . -type f -exec chmod 0644 {} + +chmod 0775 . +zip -r ${extension}.zip ./ +mv ${extension}.zip ${base}/${extension}.zip diff --git a/extensions/approval/composer.json b/extensions/approval/composer.json new file mode 100644 index 000000000..c663295f7 --- /dev/null +++ b/extensions/approval/composer.json @@ -0,0 +1,7 @@ +{ + "autoload": { + "psr-4": { + "Flarum\\Approval\\": "src/" + } + } +} diff --git a/extensions/approval/flarum.json b/extensions/approval/flarum.json new file mode 100644 index 000000000..8bde180d1 --- /dev/null +++ b/extensions/approval/flarum.json @@ -0,0 +1,22 @@ +{ + "name": "approval", + "title": "Approval", + "description": "Make discussions and posts require moderator approval.", + "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", + "reports": ">=0.1.0-beta.2" + }, + "icon": { + "name": "check", + "backgroundColor": "green", + "color": "#fff" + } +} diff --git a/extensions/approval/js/.gitignore b/extensions/approval/js/.gitignore new file mode 100644 index 000000000..372e20a51 --- /dev/null +++ b/extensions/approval/js/.gitignore @@ -0,0 +1,3 @@ +bower_components +node_modules +dist diff --git a/extensions/approval/js/admin/Gulpfile.js b/extensions/approval/js/admin/Gulpfile.js new file mode 100644 index 000000000..2b986bd70 --- /dev/null +++ b/extensions/approval/js/admin/Gulpfile.js @@ -0,0 +1,7 @@ +var gulp = require('flarum-gulp'); + +gulp({ + modules: { + 'approval': 'src/**/*.js' + } +}); diff --git a/extensions/approval/js/admin/package.json b/extensions/approval/js/admin/package.json new file mode 100644 index 000000000..62ea6c691 --- /dev/null +++ b/extensions/approval/js/admin/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "devDependencies": { + "gulp": "^3.8.11", + "flarum-gulp": "^0.1.0" + } +} diff --git a/extensions/approval/js/admin/src/main.js b/extensions/approval/js/admin/src/main.js new file mode 100644 index 000000000..8b3ff7863 --- /dev/null +++ b/extensions/approval/js/admin/src/main.js @@ -0,0 +1,21 @@ +import { extend } from 'flarum/extend'; +import app from 'flarum/app'; +import PermissionGrid from 'flarum/components/PermissionGrid'; + +app.initializers.add('approval', () => { + extend(PermissionGrid.prototype, 'replyItems', items => { + items.add('replyWithoutApproval', { + icon: 'check', + label: 'Reply without approval', + permission: 'discussion.replyWithoutApproval' + }, 95); + }); + + extend(PermissionGrid.prototype, 'moderateItems', items => { + items.add('approvePosts', { + icon: 'check', + label: 'Approve posts', + permission: 'discussion.approvePosts' + }, 65); + }); +}); diff --git a/extensions/approval/js/forum/Gulpfile.js b/extensions/approval/js/forum/Gulpfile.js new file mode 100644 index 000000000..2b986bd70 --- /dev/null +++ b/extensions/approval/js/forum/Gulpfile.js @@ -0,0 +1,7 @@ +var gulp = require('flarum-gulp'); + +gulp({ + modules: { + 'approval': 'src/**/*.js' + } +}); diff --git a/extensions/approval/js/forum/package.json b/extensions/approval/js/forum/package.json new file mode 100644 index 000000000..62ea6c691 --- /dev/null +++ b/extensions/approval/js/forum/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "devDependencies": { + "gulp": "^3.8.11", + "flarum-gulp": "^0.1.0" + } +} diff --git a/extensions/approval/js/forum/src/main.js b/extensions/approval/js/forum/src/main.js new file mode 100644 index 000000000..23cb23551 --- /dev/null +++ b/extensions/approval/js/forum/src/main.js @@ -0,0 +1,56 @@ +import { extend, override } from 'flarum/extend'; +import app from 'flarum/app'; +import Discussion from 'flarum/models/Discussion'; +import Post from 'flarum/models/Post'; +import DiscussionListItem from 'flarum/components/DiscussionListItem'; +import CommentPost from 'flarum/components/CommentPost'; +import Button from 'flarum/components/Button'; +import PostControls from 'flarum/utils/PostControls'; + +app.initializers.add('approval', () => { + Discussion.prototype.isApproved = Discussion.attribute('isApproved'); + + Post.prototype.isApproved = Post.attribute('isApproved'); + Post.prototype.canApprove = Post.attribute('canApprove'); + + extend(DiscussionListItem.prototype, 'attrs', function(attrs) { + if (!this.props.discussion.isApproved()) { + attrs.className += ' DiscussionListItem--unapproved'; + } + }); + + extend(CommentPost.prototype, 'attrs', function(attrs) { + if (!this.props.post.isApproved() && !this.props.post.isHidden()) { + attrs.className += ' CommentPost--unapproved'; + } + }); + + extend(CommentPost.prototype, 'headerItems', function(items) { + if (!this.props.post.isApproved() && !this.props.post.isHidden()) { + items.add('unapproved', 'Awaiting Approval'); + } + }); + + override(CommentPost.prototype, 'flagReason', function(original, flag) { + if (flag.type() === 'approval') { + return 'Awaiting approval'; + } + + return original(flag); + }); + + extend(PostControls, 'destructiveControls', function(items, post) { + if (!post.isApproved() && post.canApprove()) { + items.add('approve', + , + 10 + ); + } + }); + + PostControls.approveAction = function() { + this.save({isApproved: true}); + }; +}, -10); // set initializer priority to run after reports diff --git a/extensions/approval/less/admin/extension.less b/extensions/approval/less/admin/extension.less new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/approval/less/forum/extension.less b/extensions/approval/less/forum/extension.less new file mode 100644 index 000000000..7e4d43e7d --- /dev/null +++ b/extensions/approval/less/forum/extension.less @@ -0,0 +1,7 @@ +.CommentPost--unapproved { + .Post-header, + .Post-body, + .Post-footer { + opacity: 0.5; + } +} diff --git a/extensions/approval/locale/en.yml b/extensions/approval/locale/en.yml new file mode 100644 index 000000000..f9584dd98 --- /dev/null +++ b/extensions/approval/locale/en.yml @@ -0,0 +1,2 @@ +approval: + # hello_world: "Hello, world!" diff --git a/extensions/approval/migrations/2015_09_21_011527_add_is_approved_to_discussions.php b/extensions/approval/migrations/2015_09_21_011527_add_is_approved_to_discussions.php new file mode 100644 index 000000000..9ad21fdf2 --- /dev/null +++ b/extensions/approval/migrations/2015_09_21_011527_add_is_approved_to_discussions.php @@ -0,0 +1,33 @@ +schema->table('discussions', function (Blueprint $table) { + $table->boolean('is_approved')->default(1); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $this->schema->table('discussions', function (Blueprint $table) { + $table->dropColumn('is_approved'); + }); + } +} diff --git a/extensions/approval/migrations/2015_09_21_011706_add_is_approved_to_posts.php b/extensions/approval/migrations/2015_09_21_011706_add_is_approved_to_posts.php new file mode 100644 index 000000000..cf44672eb --- /dev/null +++ b/extensions/approval/migrations/2015_09_21_011706_add_is_approved_to_posts.php @@ -0,0 +1,33 @@ +schema->table('posts', function (Blueprint $table) { + $table->boolean('is_approved')->default(1); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $this->schema->table('posts', function (Blueprint $table) { + $table->dropColumn('is_approved'); + }); + } +} diff --git a/extensions/approval/src/Events/PostWasApproved.php b/extensions/approval/src/Events/PostWasApproved.php new file mode 100644 index 000000000..165d716a6 --- /dev/null +++ b/extensions/approval/src/Events/PostWasApproved.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Approval\Events; + +use Flarum\Core\Posts\Post; + +class PostWasApproved +{ + /** + * The post that was approved. + * + * @var Post + */ + public $post; + + /** + * @param Post $post + */ + public function __construct(Post $post) + { + $this->post = $post; + } +} diff --git a/extensions/approval/src/Extension.php b/extensions/approval/src/Extension.php new file mode 100644 index 000000000..abb61f68b --- /dev/null +++ b/extensions/approval/src/Extension.php @@ -0,0 +1,16 @@ +subscribe('Flarum\Approval\Listeners\AddClientAssets'); + $events->subscribe('Flarum\Approval\Listeners\AddApiAttributes'); + $events->subscribe('Flarum\Approval\Listeners\HideUnapprovedContent'); + $events->subscribe('Flarum\Approval\Listeners\UnapproveNewContent'); + $events->subscribe('Flarum\Approval\Listeners\ApproveContent'); + } +} diff --git a/extensions/approval/src/Listeners/AddApiAttributes.php b/extensions/approval/src/Listeners/AddApiAttributes.php new file mode 100644 index 000000000..684af8c07 --- /dev/null +++ b/extensions/approval/src/Listeners/AddApiAttributes.php @@ -0,0 +1,26 @@ +listen(ApiAttributes::class, [$this, 'addApiAttributes']); + } + + public function addApiAttributes(ApiAttributes $event) + { + if ($event->serializer instanceof DiscussionSerializer || + $event->serializer instanceof PostSerializer) { + $event->attributes['isApproved'] = (bool) $event->model->is_approved; + } + + if ($event->serializer instanceof PostSerializer) { + $event->attributes['canApprove'] = (bool) $event->model->discussion->can($event->actor, 'approvePosts'); + } + } +} diff --git a/extensions/approval/src/Listeners/AddClientAssets.php b/extensions/approval/src/Listeners/AddClientAssets.php new file mode 100644 index 000000000..18bae640b --- /dev/null +++ b/extensions/approval/src/Listeners/AddClientAssets.php @@ -0,0 +1,44 @@ +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('approval/main'); + + $event->forumTranslations([ + // 'approval.hello_world' + ]); + + $event->adminAssets([ + __DIR__.'/../../js/admin/dist/extension.js', + __DIR__.'/../../less/admin/extension.less' + ]); + + $event->adminBootstrapper('approval/main'); + + $event->adminTranslations([ + // 'approval.hello_world' + ]); + } +} diff --git a/extensions/approval/src/Listeners/ApproveContent.php b/extensions/approval/src/Listeners/ApproveContent.php new file mode 100644 index 000000000..a3cca9637 --- /dev/null +++ b/extensions/approval/src/Listeners/ApproveContent.php @@ -0,0 +1,47 @@ +listen(PostWillBeSaved::class, [$this, 'approvePost']); + $events->listen(PostWasApproved::class, [$this, 'approveDiscussion']); + } + + public function approvePost(PostWillBeSaved $event) + { + $attributes = $event->data['attributes']; + $post = $event->post; + + if (isset($attributes['isApproved'])) { + $post->assertCan($event->actor, 'approve'); + + $isApproved = (bool) $attributes['isApproved']; + } elseif (! empty($attributes['isHidden']) && $post->can($event->actor, 'approve')) { + $isApproved = true; + } + + if (! empty($isApproved)) { + $post->is_approved = true; + + $post->raise(new PostWasApproved($post)); + } + } + + public function approveDiscussion(PostWasApproved $event) + { + $post = $event->post; + + $post->discussion->refreshCommentsCount(); + $post->discussion->refreshLastPost(); + + if ($post->number == 1) { + $post->discussion->is_approved = true; + $post->discussion->save(); + } + } +} diff --git a/extensions/approval/src/Listeners/HideUnapprovedContent.php b/extensions/approval/src/Listeners/HideUnapprovedContent.php new file mode 100644 index 000000000..ee975cb71 --- /dev/null +++ b/extensions/approval/src/Listeners/HideUnapprovedContent.php @@ -0,0 +1,44 @@ +listen(ScopeModelVisibility::class, [$this, 'hideUnapprovedDiscussions']); + $events->listen(ScopePostVisibility::class, [$this, 'hideUnapprovedPosts']); + } + + public function hideUnapprovedDiscussions(ScopeModelVisibility $event) + { + if ($event->model instanceof Discussion) { + $user = $event->actor; + + if (! $user->hasPermission('discussion.editPosts')) { + $event->query->where(function ($query) use ($user) { + $query->where('discussions.is_approved', 1) + ->orWhere('start_user_id', $user->id); + + event(new ScopeHiddenDiscussionVisibility($query, $user, 'discussion.editPosts')); + }); + } + } + } + + public function hideUnapprovedPosts(ScopePostVisibility $event) + { + if ($event->discussion->can($event->actor, 'editPosts')) { + return; + } + + $event->query->where(function ($query) use ($event) { + $query->where('posts.is_approved', 1) + ->orWhere('user_id', $event->actor->id); + }); + } +} diff --git a/extensions/approval/src/Listeners/UnapproveNewContent.php b/extensions/approval/src/Listeners/UnapproveNewContent.php new file mode 100644 index 000000000..d0d9c49ba --- /dev/null +++ b/extensions/approval/src/Listeners/UnapproveNewContent.php @@ -0,0 +1,47 @@ +listen(PostWillBeSaved::class, [$this, 'unapproveNewPosts']); + } + + public function unapproveNewPosts(PostWillBeSaved $event) + { + $post = $event->post; + + if (! $post->exists) { + if ($post->discussion->can($event->actor, 'replyWithoutApproval')) { + if ($post->is_approved === null) { + $post->is_approved = true; + } + + return; + } + + $post->is_approved = false; + + $post->afterSave(function ($post) { + if ($post->number == 1) { + $post->discussion->is_approved = false; + $post->discussion->save(); + } + + $flag = new Flag; + + $flag->post_id = $post->id; + $flag->type = 'approval'; + $flag->time = time(); + + $flag->save(); + }); + } + } +}