diff --git a/src/Api/JobArgs/JobArgs.php b/src/Api/JobArgs/JobArgs.php index 436cee4d..98c41c86 100644 --- a/src/Api/JobArgs/JobArgs.php +++ b/src/Api/JobArgs/JobArgs.php @@ -38,14 +38,15 @@ class JobArgs const ARG_NEW_THUMB_CONTENT = 'new-thumb-content'; const ARG_NEW_TAG_NAMES = 'new-tag-names'; - const ARG_NEW_POST_SCORE = 'new-post-score'; - const ARG_SOURCE_TAG_NAME = 'source-tag-name'; - const ARG_TARGET_TAG_NAME = 'target-tag-name'; - const ARG_NEW_ACCESS_RANK = 'new-access-rank'; const ARG_NEW_EMAIL = 'new-email'; const ARG_NEW_USER_NAME = 'new-user-name'; const ARG_NEW_PASSWORD = 'new-password'; + const ARG_NEW_SETTINGS = 'new-settings'; + + const ARG_NEW_POST_SCORE = 'new-post-score'; + const ARG_SOURCE_TAG_NAME = 'source-tag-name'; + const ARG_TARGET_TAG_NAME = 'target-tag-name'; public static function Alternative() { diff --git a/src/Api/Jobs/UserJobs/EditUserSettingsJob.php b/src/Api/Jobs/UserJobs/EditUserSettingsJob.php new file mode 100644 index 00000000..293e187c --- /dev/null +++ b/src/Api/Jobs/UserJobs/EditUserSettingsJob.php @@ -0,0 +1,44 @@ +userRetriever = new UserRetriever($this); + } + + public function execute() + { + $newSettings = $this->getArgument(JobArgs::ARG_NEW_SETTINGS); + + if (!is_array($newSettings)) + throw new SimpleException('Expected array'); + + $user = $this->userRetriever->retrieve(); + foreach ($newSettings as $key => $value) + { + $user->getSettings()->set($key, $value); + } + return UserModel::save($user); + } + + public function getRequiredArguments() + { + return JobArgs::Conjunction( + $this->userRetriever->getRequiredArguments(), + JobArgs::ARG_NEW_SETTINGS); + } + + public function getRequiredPrivileges() + { + return new Privilege( + Privilege::ChangeUserSettings, + Access::getIdentity($this->userRetriever->retrieve())); + } + + public function isAuthenticationRequired() + { + return true; + } +} diff --git a/src/Api/Jobs/UserJobs/GetUserSettingsJob.php b/src/Api/Jobs/UserJobs/GetUserSettingsJob.php new file mode 100644 index 00000000..be2cb1a5 --- /dev/null +++ b/src/Api/Jobs/UserJobs/GetUserSettingsJob.php @@ -0,0 +1,33 @@ +userRetriever = new UserRetriever($this); + } + + public function execute() + { + $user = $this->userRetriever->retrieve(); + return $user->getSettings()->getAllAsArray(); + } + + public function getRequiredArguments() + { + return $this->userRetriever->getRequiredArguments(); + } + + public function getRequiredPrivileges() + { + return new Privilege( + Privilege::ChangeUserSettings, + Access::getIdentity($this->userRetriever->retrieve())); + } + + public function isAuthenticationRequired() + { + return true; + } +} diff --git a/src/Controllers/UserController.php b/src/Controllers/UserController.php index 8dca17b7..8df275d2 100644 --- a/src/Controllers/UserController.php +++ b/src/Controllers/UserController.php @@ -71,30 +71,48 @@ class UserController { $this->genericView($identifier, 'settings'); - $user = getContext()->transport->user; - - Access::assert(new Privilege( - Privilege::ChangeUserSettings, - Access::getIdentity($user))); - $suppliedSafety = InputHelper::get('safety'); - if (!is_array($suppliedSafety)) - $suppliedSafety = []; - foreach (PostSafety::getAll() as $safety) - $user->getSettings()->enableSafety($safety, in_array($safety->toInteger(), $suppliedSafety)); + $desiredSafety = PostSafety::makeFlags($suppliedSafety); - $user->getSettings()->enableEndlessScrolling(InputHelper::get('endless-scrolling')); - $user->getSettings()->enablePostTagTitles(InputHelper::get('post-tag-titles')); - $user->getSettings()->enableHidingDislikedPosts(InputHelper::get('hide-disliked-posts')); + $user = Api::run( + new EditUserSettingsJob(), + $this->appendUserIdentifierArgument( + [ + JobArgs::ARG_NEW_SETTINGS => + [ + UserSettings::SETTING_SAFETY => $desiredSafety, + UserSettings::SETTING_ENDLESS_SCROLLING => InputHelper::get('endless-scrolling'), + UserSettings::SETTING_POST_TAG_TITLES => InputHelper::get('post-tag-titles'), + UserSettings::SETTING_HIDE_DISLIKED_POSTS => InputHelper::get('hide-disliked-posts'), + ] + ], $identifier)); - if ($user->getAccessRank()->toInteger() != AccessRank::Anonymous) - UserModel::save($user); + getContext()->transport->user = $user; if ($user->getId() == Auth::getCurrentUser()->getId()) Auth::setCurrentUser($user); Messenger::message('Browsing settings updated!'); } + public function toggleSafetyAction($safety) + { + $safety = new PostSafety($safety); + $safety->validate(); + + $user = Auth::getCurrentUser(); + $user->getSettings()->enableSafety($safety, !$user->getSettings()->hasEnabledSafety($safety)); + $desiredSafety = $user->getSettings()->get(UserSettings::SETTING_SAFETY); + + $user = Api::run( + new EditUserSettingsJob(), + [ + JobArgs::ARG_USER_ENTITY => Auth::getCurrentUser(), + JobArgs::ARG_NEW_SETTINGS => [UserSettings::SETTING_SAFETY => $desiredSafety], + ]); + + Auth::setCurrentUser($user); + } + public function editAction($identifier) { $this->genericView($identifier, 'edit'); @@ -174,24 +192,6 @@ class UserController $this->appendUserIdentifierArgument([], $identifier)); } - public function toggleSafetyAction($safety) - { - $user = Auth::getCurrentUser(); - - Access::assert(new Privilege( - Privilege::ChangeUserSettings, - Access::getIdentity($user))); - - $safety = new PostSafety($safety); - $safety->validate(); - - $user->getSettings()->enableSafety($safety, !$user->getSettings()->hasEnabledSafety($safety)); - - if ($user->getAccessRank()->toInteger() != AccessRank::Anonymous) - UserModel::save($user); - Auth::setCurrentUser($user); - } - public function registrationView() { $context = getContext(); diff --git a/src/Models/Enums/PostSafety.php b/src/Models/Enums/PostSafety.php index 3014ac63..008a7414 100644 --- a/src/Models/Enums/PostSafety.php +++ b/src/Models/Enums/PostSafety.php @@ -27,6 +27,18 @@ class PostSafety extends Enum implements IValidatable return self::_toString($this->safety); } + public static function makeFlags($safetyCodes) + { + if (!is_array($safetyCodes)) + return 0; + + $flags = 0; + foreach (self::getAll() as $safety) + if (in_array($safety->toInteger(), $safetyCodes)) + $flags |= $safety->toFlag(); + return $flags; + } + public static function getAll() { return array_map(function($constantName) diff --git a/src/Models/UserSettings.php b/src/Models/UserSettings.php index dbdfd044..462f279c 100644 --- a/src/Models/UserSettings.php +++ b/src/Models/UserSettings.php @@ -21,6 +21,7 @@ class UserSettings implements IValidatable $serialized = $this->getAllAsSerializedString(); if (strlen($serialized) > 200) throw new SimpleException('Too much data'); + $this->ensureCorrectTypes(); } diff --git a/tests/Tests/ApiTests/ApiArgumentTest.php b/tests/Tests/ApiTests/ApiArgumentTest.php index b4fb8ed0..d673837f 100644 --- a/tests/Tests/ApiTests/ApiArgumentTest.php +++ b/tests/Tests/ApiTests/ApiArgumentTest.php @@ -211,6 +211,12 @@ class ApiArgumentTest extends AbstractFullApiTest $this->getUserSelector()); } + public function testGetUserSettingsJob() + { + $this->testArguments(new GetUserSettingsJob(), + $this->getUserSelector()); + } + public function testListCommentsJob() { $this->testArguments(new ListCommentsJob(), @@ -280,6 +286,14 @@ class ApiArgumentTest extends AbstractFullApiTest $this->getUserSelector())); } + public function testEditUserSettingsJob() + { + $this->testArguments(new EditUserSettingsJob(), + JobArgs::Conjunction( + $this->getUserSelector(), + JobArgs::ARG_NEW_SETTINGS)); + } + public function testPreviewCommentJob() { $this->testArguments(new PreviewCommentJob(), diff --git a/tests/Tests/ApiTests/ApiAuthTest.php b/tests/Tests/ApiTests/ApiAuthTest.php index b65d9d61..f319cf44 100644 --- a/tests/Tests/ApiTests/ApiAuthTest.php +++ b/tests/Tests/ApiTests/ApiAuthTest.php @@ -24,6 +24,7 @@ class ApiAuthTest extends AbstractFullApiTest $this->testAuth(new EditUserJob(), false); $this->testAuth(new EditUserNameJob(), false); $this->testAuth(new EditUserPasswordJob(), false); + $this->testAuth(new EditUserSettingsJob(), true); $this->testAuth(new FeaturePostJob(), true); $this->testAuth(new FlagPostJob(), false); $this->testAuth(new FlagUserJob(), false); @@ -32,6 +33,7 @@ class ApiAuthTest extends AbstractFullApiTest $this->testAuth(new GetPostJob(), false); $this->testAuth(new GetPostThumbJob(), false); $this->testAuth(new GetUserJob(), false); + $this->testAuth(new GetUserSettingsJob(), true); $this->testAuth(new ListCommentsJob(), false); $this->testAuth(new ListLogsJob(), false); $this->testAuth(new ListPostsJob(), false); diff --git a/tests/Tests/ApiTests/ApiEmailRequirementsTest.php b/tests/Tests/ApiTests/ApiEmailRequirementsTest.php index 569e3144..6cf63868 100644 --- a/tests/Tests/ApiTests/ApiEmailRequirementsTest.php +++ b/tests/Tests/ApiTests/ApiEmailRequirementsTest.php @@ -25,6 +25,7 @@ class ApiEmailRequirementsTest extends AbstractFullApiTest $this->testRegularEmailRequirement(new EditUserJob()); $this->testRegularEmailRequirement(new EditUserNameJob()); $this->testRegularEmailRequirement(new EditUserPasswordJob()); + $this->testRegularEmailRequirement(new EditUserSettingsJob()); $this->testRegularEmailRequirement(new FeaturePostJob()); $this->testRegularEmailRequirement(new FlagPostJob()); $this->testRegularEmailRequirement(new FlagUserJob()); @@ -33,6 +34,7 @@ class ApiEmailRequirementsTest extends AbstractFullApiTest $this->testRegularEmailRequirement(new GetPostJob()); $this->testRegularEmailRequirement(new GetPostThumbJob()); $this->testRegularEmailRequirement(new GetUserJob()); + $this->testRegularEmailRequirement(new GetUserSettingsJob()); $this->testRegularEmailRequirement(new ListCommentsJob()); $this->testRegularEmailRequirement(new ListLogsJob()); $this->testRegularEmailRequirement(new ListPostsJob()); diff --git a/tests/Tests/ApiTests/ApiPrivilegeTest.php b/tests/Tests/ApiTests/ApiPrivilegeTest.php index 58e31ef6..f5159c26 100644 --- a/tests/Tests/ApiTests/ApiPrivilegeTest.php +++ b/tests/Tests/ApiTests/ApiPrivilegeTest.php @@ -133,8 +133,10 @@ class ApiPrivilegeTest extends AbstractFullApiTest $this->testDynamicUserPrivilege(new EditUserEmailJob(), new Privilege(Privilege::ChangeUserEmail)); $this->testDynamicUserPrivilege(new EditUserNameJob(), new Privilege(Privilege::ChangeUserName)); $this->testDynamicUserPrivilege(new EditUserPasswordJob(), new Privilege(Privilege::ChangeUserPassword)); + $this->testDynamicUserPrivilege(new EditUserSettingsJob(), new Privilege(Privilege::ChangeUserSettings)); $this->testDynamicUserPrivilege(new FlagUserJob(), new Privilege(Privilege::FlagUser)); $this->testDynamicUserPrivilege(new GetUserJob(), new Privilege(Privilege::ViewUser)); + $this->testDynamicUserPrivilege(new GetUserSettingsJob(), new Privilege(Privilege::ChangeUserSettings)); $this->testDynamicUserPrivilege(new ToggleUserBanJob(), new Privilege(Privilege::BanUser)); } diff --git a/tests/Tests/JobTests/EditUserSettingsJobTest.php b/tests/Tests/JobTests/EditUserSettingsJobTest.php new file mode 100644 index 00000000..2083c203 --- /dev/null +++ b/tests/Tests/JobTests/EditUserSettingsJobTest.php @@ -0,0 +1,112 @@ +grantAccess('changeUserSettings'); + $user = $this->userMocker->mockSingle(); + $this->login($user); + + $expectedSafety = (new PostSafety(PostSafety::Sketchy))->toFlag(); + $user = $this->assert->doesNotThrow(function() use ($user, $expectedSafety) + { + return Api::run( + new EditUserSettingsJob(), + [ + JobArgs::ARG_USER_NAME => $user->getName(), + JobArgs::ARG_NEW_SETTINGS => + [ + UserSettings::SETTING_SAFETY => $expectedSafety, + UserSettings::SETTING_ENDLESS_SCROLLING => true, + UserSettings::SETTING_POST_TAG_TITLES => true, + UserSettings::SETTING_HIDE_DISLIKED_POSTS => true, + ] + ]); + }); + + $settings = $user->getSettings(); + + $this->assert->areEqual($expectedSafety, $settings->get(UserSettings::SETTING_SAFETY)); + $this->assert->isTrue($settings->get(UserSettings::SETTING_ENDLESS_SCROLLING)); + $this->assert->isTrue($settings->get(UserSettings::SETTING_POST_TAG_TITLES)); + $this->assert->isTrue($settings->get(UserSettings::SETTING_HIDE_DISLIKED_POSTS)); + } + + public function testSettingAdditional() + { + $this->grantAccess('changeUserSettings'); + $user = $this->userMocker->mockSingle(); + $this->login($user); + + $user = $this->assert->doesNotThrow(function() use ($user) + { + return Api::run( + new EditUserSettingsJob(), + [ + JobArgs::ARG_USER_NAME => $user->getName(), + JobArgs::ARG_NEW_SETTINGS => + [ + 'additional' => 'rubbish', + ] + ]); + }); + + $settings = $user->getSettings(); + + $expectedSafety = (new PostSafety(PostSafety::Safe))->toFlag(); + $this->assert->areEqual($expectedSafety, $settings->get(UserSettings::SETTING_SAFETY)); + $this->assert->isTrue($settings->get(UserSettings::SETTING_ENDLESS_SCROLLING)); + $this->assert->isFalse($settings->get(UserSettings::SETTING_POST_TAG_TITLES)); + $this->assert->isFalse($settings->get(UserSettings::SETTING_HIDE_DISLIKED_POSTS)); + $this->assert->areEqual('rubbish', $settings->get('additional')); + } + + public function testSettingBadValues() + { + $this->grantAccess('changeUserSettings'); + $user = $this->userMocker->mockSingle(); + $this->login($user); + + $user = $this->assert->doesNotThrow(function() use ($user) + { + return Api::run( + new EditUserSettingsJob(), + [ + JobArgs::ARG_USER_NAME => $user->getName(), + JobArgs::ARG_NEW_SETTINGS => + [ + UserSettings::SETTING_SAFETY => 'rubbish', + UserSettings::SETTING_ENDLESS_SCROLLING => 'rubbish', + UserSettings::SETTING_POST_TAG_TITLES => 'rubbish', + UserSettings::SETTING_HIDE_DISLIKED_POSTS => 'rubbish', + ] + ]); + }); + + $settings = $user->getSettings(); + $expectedSafety = (new PostSafety(PostSafety::Safe))->toFlag(); + $this->assert->areEqual($expectedSafety, $settings->get(UserSettings::SETTING_SAFETY)); + $this->assert->isTrue($settings->get(UserSettings::SETTING_ENDLESS_SCROLLING)); + $this->assert->isFalse($settings->get(UserSettings::SETTING_POST_TAG_TITLES)); + $this->assert->isFalse($settings->get(UserSettings::SETTING_HIDE_DISLIKED_POSTS)); + } + + public function testSettingTooLongData() + { + $this->grantAccess('changeUserSettings'); + $user = $this->userMocker->mockSingle(); + $this->login($user); + + $this->assert->throws(function() use ($user) + { + return Api::run( + new EditUserSettingsJob(), + [ + JobArgs::ARG_USER_NAME => $user->getName(), + JobArgs::ARG_NEW_SETTINGS => + [ + 'additional' => str_repeat('rubbish', 50), + ]]); + }, 'Too much data'); + } +} diff --git a/tests/Tests/JobTests/GetUserSettingsJobTest.php b/tests/Tests/JobTests/GetUserSettingsJobTest.php new file mode 100644 index 00000000..aa6959ae --- /dev/null +++ b/tests/Tests/JobTests/GetUserSettingsJobTest.php @@ -0,0 +1,82 @@ +grantAccess('changeUserSettings'); + $user = $this->userMocker->mockSingle(); + $this->login($user); + + $settings = $this->assert->doesNotThrow(function() use ($user) + { + return Api::run( + new GetUserSettingsJob(), + [ + JobArgs::ARG_USER_NAME => $user->getName(), + ]); + }); + + $expectedSafety = (new PostSafety(PostSafety::Safe))->toFlag(); + $this->assert->areEqual($expectedSafety, $settings[UserSettings::SETTING_SAFETY]); + + $this->assert->isTrue($settings[UserSettings::SETTING_ENDLESS_SCROLLING]); + $this->assert->isFalse($settings[UserSettings::SETTING_POST_TAG_TITLES]); + $this->assert->isFalse($settings[UserSettings::SETTING_HIDE_DISLIKED_POSTS]); + } + + public function testSwitchingSafety() + { + $this->grantAccess('changeUserSettings'); + $user = $this->userMocker->mockSingle(); + $this->login($user); + + $user->getSettings()->enableSafety(new PostSafety(PostSafety::Sketchy), true); + UserModel::save($user); + + $settings = $this->assert->doesNotThrow(function() use ($user) + { + return Api::run( + new GetUserSettingsJob(), + [ + JobArgs::ARG_USER_NAME => $user->getName(), + ]); + }); + + $expectedSafety = + ((new PostSafety(PostSafety::Safe))->toFlag() + | (new PostSafety(PostSafety::Sketchy))->toFlag()); + + $this->assert->areEqual($expectedSafety, $settings[UserSettings::SETTING_SAFETY]); + + $this->assert->isTrue($settings[UserSettings::SETTING_ENDLESS_SCROLLING]); + $this->assert->isFalse($settings[UserSettings::SETTING_POST_TAG_TITLES]); + $this->assert->isFalse($settings[UserSettings::SETTING_HIDE_DISLIKED_POSTS]); + } + + public function testSwitchingSafety2() + { + $this->grantAccess('changeUserSettings'); + $user = $this->userMocker->mockSingle(); + $this->login($user); + + $user->getSettings()->enableSafety(new PostSafety(PostSafety::Sketchy), true); + $user->getSettings()->enableSafety(new PostSafety(PostSafety::Safe), false); + UserModel::save($user); + + $settings = $this->assert->doesNotThrow(function() use ($user) + { + return Api::run( + new GetUserSettingsJob(), + [ + JobArgs::ARG_USER_NAME => $user->getName(), + ]); + }); + + $expectedSafety = (new PostSafety(PostSafety::Sketchy))->toFlag(); + $this->assert->areEqual($expectedSafety, $settings[UserSettings::SETTING_SAFETY]); + + $this->assert->isTrue($settings[UserSettings::SETTING_ENDLESS_SCROLLING]); + $this->assert->isFalse($settings[UserSettings::SETTING_POST_TAG_TITLES]); + $this->assert->isFalse($settings[UserSettings::SETTING_HIDE_DISLIKED_POSTS]); + } +}