diff --git a/extend.php b/extend.php index 74d873e..7c9ab5e 100644 --- a/extend.php +++ b/extend.php @@ -77,6 +77,7 @@ }), (new Extend\ApiResource(Resource\PostResource::class)) + ->fields(Api\PostAttributes::class) ->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint) { return $endpoint ->addDefaultInclude(['discussion.bestAnswerPost', 'discussion.bestAnswerUser', 'discussion.bestAnswerPost.user']); // @todo: same diff --git a/js/src/@types/shims.d.ts b/js/src/@types/shims.d.ts index 072f558..b1468dd 100644 --- a/js/src/@types/shims.d.ts +++ b/js/src/@types/shims.d.ts @@ -14,7 +14,6 @@ declare module 'flarum/common/models/Discussion' { hasBestAnswer(): boolean | undefined; bestAnswerPost(): Post | null; bestAnswerUser(): User | null; - canSelectBestAnswer(): boolean; bestAnswerSetAt(): Date | null; } } @@ -37,3 +36,9 @@ declare module 'flarum/common/models/User' { bestAnswerCount(): number; } } + +declare module 'flarum/common/models/Post' { + export default interface Post { + canSelectAsBestAnswer(): boolean; + } +} diff --git a/js/src/admin/components/BestAnswerSettingsPage.tsx b/js/src/admin/components/BestAnswerSettingsPage.tsx index 7e475ee..93a3886 100644 --- a/js/src/admin/components/BestAnswerSettingsPage.tsx +++ b/js/src/admin/components/BestAnswerSettingsPage.tsx @@ -31,12 +31,6 @@ export default class BestAnswerSettingsPage extends ExtensionPage {

{app.translator.trans('fof-best-answer.admin.settings.label.general')}

- {this.buildSettingComponent({ - type: 'boolean', - setting: 'fof-best-answer.allow_select_own_post', - label: app.translator.trans('fof-best-answer.admin.settings.allow_select_own_post'), - help: app.translator.trans('fof-best-answer.admin.settings.allow_select_own_post_help'), - })} {this.buildSettingComponent({ type: 'boolean', setting: 'fof-best-answer.use_alternative_ui', diff --git a/js/src/admin/extend.ts b/js/src/admin/extend.ts index ea9d9dc..0b9b050 100644 --- a/js/src/admin/extend.ts +++ b/js/src/admin/extend.ts @@ -1,3 +1,4 @@ +import app from 'flarum/admin/app'; import Extend from 'flarum/common/extenders'; import BestAnswerSettingsPage from './components/BestAnswerSettingsPage'; import commonExtend from '../common/extend'; @@ -22,5 +23,13 @@ export default [ permission: 'discussion.selectBestAnswerNotOwnDiscussion', }), 'reply' + ) + .permission( + () => ({ + icon: 'fas fa-check', + label: app.translator.trans('fof-best-answer.admin.permissions.allow_select_own_post'), + permission: 'discussion.fof-best-answer.allow_select_own_post', + }), + 'reply' ), ]; diff --git a/js/src/forum/addBestAnswerAction.tsx b/js/src/forum/addBestAnswerAction.tsx index fd476bc..9a83985 100644 --- a/js/src/forum/addBestAnswerAction.tsx +++ b/js/src/forum/addBestAnswerAction.tsx @@ -10,18 +10,13 @@ import extractText from 'flarum/common/utils/extractText'; export default function addBestAnswerAction() { const ineligible = (discussion: Discussion, post: Post) => { - return post.isHidden() || post.number() === 1 || !discussion.canSelectBestAnswer() || !app.session.user; - }; - - const blockSelectOwnPost = (post: Post): boolean => { - const user = post.user(); - return !app.forum.attribute('canSelectBestAnswerOwnPost') && user !== false && user.id() === app.session.user?.id(); + return post.isHidden() || post.number() === 1 || !post.canSelectAsBestAnswer() || !app.session.user; }; const isThisBestAnswer = (discussion: Discussion, post: Post): boolean => { - const bAPost = discussion.bestAnswerPost(); + const bAPost = discussion.bestAnswerPost?.(); const hasBestAnswer = discussion.hasBestAnswer(); - return hasBestAnswer !== undefined && hasBestAnswer && bAPost !== null && bAPost.id() === post.id(); + return hasBestAnswer !== undefined && hasBestAnswer && bAPost !== null && bAPost.id?.() === post.id(); }; const actionLabel = (isBestAnswer: boolean): string => { @@ -72,7 +67,7 @@ export default function addBestAnswerAction() { if (post.contentType() !== 'comment') return; - if (ineligible(discussion, post) || blockSelectOwnPost(post) || !app.current.matches(DiscussionPage)) return; + if (ineligible(discussion, post) || !app.current.matches(DiscussionPage)) return; items.add( 'bestAnswer', @@ -101,7 +96,7 @@ export default function addBestAnswerAction() { post.pushAttributes({ isBestAnswer }); - if (ineligible(discussion, post) || blockSelectOwnPost(post) || !app.current.matches(DiscussionPage)) return; + if (ineligible(discussion, post) || !app.current.matches(DiscussionPage)) return; items.add( 'bestAnswer', diff --git a/js/src/forum/extend.ts b/js/src/forum/extend.ts index 38c5cd3..6df7b8d 100644 --- a/js/src/forum/extend.ts +++ b/js/src/forum/extend.ts @@ -1,7 +1,7 @@ import Discussion from 'flarum/common/models/Discussion'; import commonExtend from '../common/extend'; import Extend from 'flarum/common/extenders'; -import type Post from 'flarum/common/models/Post'; +import Post from 'flarum/common/models/Post'; import User from 'flarum/common/models/User'; import Model from 'flarum/common/Model'; @@ -12,9 +12,11 @@ export default [ .hasOne('bestAnswerPost') .hasOne('bestAnswerUser') .attribute('hasBestAnswer') - .attribute('canSelectBestAnswer') .attribute('bestAnswerSetAt', Model.transformDate), new Extend.Model(User) // .attribute('bestAnswerCount'), + + new Extend.Model(Post) // + .attribute('canSelectAsBestAnswer'), ]; diff --git a/resources/locale/en.yml b/resources/locale/en.yml index 711eb6a..d54fcec 100644 --- a/resources/locale/en.yml +++ b/resources/locale/en.yml @@ -3,6 +3,7 @@ fof-best-answer: permissions: best_answer: Select Best Answer (own Discussion) best_answer_not_own_discussion: Select Best Answer (not own Discussion) + allow_select_own_post: Select own post as Best Answer settings: label: tags: Best Answer Tags @@ -10,8 +11,6 @@ fof-best-answer: reminders: Reminders advanced: Advanced reminders_notice: For reminders to function, you must have set up the Flarum scheduler correctly. - allow_select_own_post: Select own post - allow_select_own_post_help: Allow a user to select their own post as a best answer to a discussion show_max_lines_label: Max lines to show in post preview show_max_lines_help: Set to 0 to disable. If a post is longer than the configured amount of lines, it will be truncated in the post preview with a fade out effect. select_best_answer_reminder_days: Reminder frequency diff --git a/src/Api/ForumAttributes.php b/src/Api/ForumAttributes.php index 13c4dc7..ed05485 100644 --- a/src/Api/ForumAttributes.php +++ b/src/Api/ForumAttributes.php @@ -24,9 +24,6 @@ public function __construct( public function __invoke(): array { return [ - Schema\Boolean::make('canSelectBestAnswerOwnPost') - ->get(fn () => (bool) $this->settings->get('fof-best-answer.allow_select_own_post')), - Schema\Boolean::make('bestAnswerDiscussionSidebarJumpButton') ->get(fn () => (bool) $this->settings->get('fof-best-answer.discussion_sidebar_jump_button')), diff --git a/src/Api/PostAttributes.php b/src/Api/PostAttributes.php new file mode 100644 index 0000000..ff094ab --- /dev/null +++ b/src/Api/PostAttributes.php @@ -0,0 +1,33 @@ +get(fn (Post $post, Context $context) => $this->bestAnswerRepository->canSelectPostAsBestAnswer($context->getActor(), $post)), + ]; + } +} diff --git a/src/Console/NotifyCommand.php b/src/Console/NotifyCommand.php index cc8e33d..43ed413 100644 --- a/src/Console/NotifyCommand.php +++ b/src/Console/NotifyCommand.php @@ -40,7 +40,6 @@ public function __construct(private SettingsRepositoryInterface $settings, priva public function handle() { $days = (int) $this->settings->get('fof-best-answer.select_best_answer_reminder_days'); - $canSelectOwn = (bool) (int) $this->settings->get('fof-best-answer.allow_select_own_post'); $time = Carbon::now()->subDays($days); // set a max time period to go back, so we don't spam really old discussions too. @@ -76,11 +75,12 @@ public function handle() $errors = []; - $query->chunkById(20, function ($discussions) use ($canSelectOwn, &$errors) { + $query->chunkById(20, function ($discussions) use (&$errors) { // Filter out discussions where the user can't select a post as best answer. // - The user must have permission to select a best answer on their own discussion // - The user must be able to select a post, whether they can select any post (including their own) or not. - $discussions = $discussions->filter(function ($d) use ($canSelectOwn) { + $discussions = $discussions->filter(function ($d) { + $canSelectOwn = $d->user->can('fof-best-answer.allow_select_own_post', $d); $hasPermission = $d->user->can('selectBestAnswerOwnDiscussion', $d); $canSelectPosts = $canSelectOwn || $d->posts()->where('user_id', '!=', $d->user_id)->count() != 0; diff --git a/src/Repository/BestAnswerRepository.php b/src/Repository/BestAnswerRepository.php index 01e34c0..d5d70f8 100644 --- a/src/Repository/BestAnswerRepository.php +++ b/src/Repository/BestAnswerRepository.php @@ -50,7 +50,7 @@ public function canSelectPostAsBestAnswer(User $user, Post $post): bool } if ($user->id === $post->user_id) { - return (bool) $this->settings->get('fof-best-answer.allow_select_own_post'); + return $user->can('fof-best-answer.allow_select_own_post', $post->discussion); } return true; diff --git a/tests/integration/api/SetBestAnswerTest.php b/tests/integration/api/SetBestAnswerTest.php index e6defdb..491f27f 100644 --- a/tests/integration/api/SetBestAnswerTest.php +++ b/tests/integration/api/SetBestAnswerTest.php @@ -59,6 +59,7 @@ public function setUp(): void ], 'group_permission' => [ ['group_id' => 4, 'permission' => 'discussion.selectBestAnswerNotOwnDiscussion', 'created_at' => Carbon::now()], + ['group_id' => 4, 'permission' => 'discussion.fof-best-answer.allow_select_own_post', 'created_at' => Carbon::now()], ], 'group_user' => [ ['user_id' => 4, 'group_id' => 4], @@ -166,20 +167,36 @@ public function user_without_permission_cannot_set_best_answer(int $userId) $this->assertEquals(403, $response->getStatusCode()); } + public static function unauthorizedUsersOwnPostProvider(): array + { + return [ + [2], + [3], + ]; + } + #[Test] - public function user_cannot_set_own_post_as_best_answer_if_not_permitted() + #[DataProvider('unauthorizedUsersOwnPostProvider')] + public function user_cannot_set_own_post_as_best_answer_if_not_permitted(int $userId) { - $response = $this->setBestAnswer(3, 5, 2); + $response = $this->setBestAnswer($userId, 5, 2); $this->assertEquals(403, $response->getStatusCode()); } - #[Test] - public function user_can_set_own_post_as_best_answer_if_permitted() + public static function permittedUsersOwnPostProvider(): array { - $this->setting('fof-best-answer.allow_select_own_post', true); + return [ + [1], + [4], + ]; + } - $response = $this->setBestAnswer(3, 5, 2); + #[Test] + #[DataProvider('permittedUsersOwnPostProvider')] + public function user_can_set_own_post_as_best_answer_if_permitted(int $userId) + { + $response = $this->setBestAnswer($userId, 5, 2); $this->assertEquals(200, $response->getStatusCode());