diff --git a/src/Access.php b/src/Access.php index 58ea763c..d56c328a 100644 --- a/src/Access.php +++ b/src/Access.php @@ -114,7 +114,7 @@ class Access return array_filter(PostSafety::getAll(), function($safety) { return Access::check(new Privilege(Privilege::ListPosts, $safety->toString())) - and Auth::getCurrentUser()->hasEnabledSafety($safety); + and Auth::getCurrentUser()->getSettings()->hasEnabledSafety($safety); }); } diff --git a/src/Controllers/UserController.php b/src/Controllers/UserController.php index 0858661c..8dca17b7 100644 --- a/src/Controllers/UserController.php +++ b/src/Controllers/UserController.php @@ -81,11 +81,11 @@ class UserController if (!is_array($suppliedSafety)) $suppliedSafety = []; foreach (PostSafety::getAll() as $safety) - $user->enableSafety($safety, in_array($safety->toInteger(), $suppliedSafety)); + $user->getSettings()->enableSafety($safety, in_array($safety->toInteger(), $suppliedSafety)); - $user->enableEndlessScrolling(InputHelper::get('endless-scrolling')); - $user->enablePostTagTitles(InputHelper::get('post-tag-titles')); - $user->enableHidingDislikedPosts(InputHelper::get('hide-disliked-posts')); + $user->getSettings()->enableEndlessScrolling(InputHelper::get('endless-scrolling')); + $user->getSettings()->enablePostTagTitles(InputHelper::get('post-tag-titles')); + $user->getSettings()->enableHidingDislikedPosts(InputHelper::get('hide-disliked-posts')); if ($user->getAccessRank()->toInteger() != AccessRank::Anonymous) UserModel::save($user); @@ -185,7 +185,7 @@ class UserController $safety = new PostSafety($safety); $safety->validate(); - $user->enableSafety($safety, !$user->hasEnabledSafety($safety)); + $user->getSettings()->enableSafety($safety, !$user->getSettings()->hasEnabledSafety($safety)); if ($user->getAccessRank()->toInteger() != AccessRank::Anonymous) UserModel::save($user); diff --git a/src/Helpers/TextHelper.php b/src/Helpers/TextHelper.php index d8a1066b..3c63bd0f 100644 --- a/src/Helpers/TextHelper.php +++ b/src/Helpers/TextHelper.php @@ -7,6 +7,48 @@ class TextHelper return preg_match($emailRegex, $email); } + public static function toIntegerOrNull($x) + { + if ($x === true or $x === false) + return null; + + if ($x === 0 or $x === '0') + return 0; + + $y = intval($x); + + if ($y !== 0) + { + if (!preg_match('/^-?\d+$/', $x)) + return null; + + return $y; + } + + return null; + } + + public static function toBooleanOrNull($x) + { + switch (strtolower($x)) + { + case '1': + case 'true': + case 'on': + case 'yes': + case 'y': + return true; + case '0': + case 'false': + case 'off': + case 'no': + case 'n': + return false; + default: + return null; + } + } + public static function replaceTokens($text, array $tokens) { foreach ($tokens as $key => $value) diff --git a/src/Models/Entities/UserEntity.php b/src/Models/Entities/UserEntity.php index c56da41b..e552314a 100644 --- a/src/Models/Entities/UserEntity.php +++ b/src/Models/Entities/UserEntity.php @@ -13,16 +13,17 @@ final class UserEntity extends AbstractEntity implements IValidatable private $joinDate; private $lastLoginDate; private $accessRank; - public $settings; private $banned = false; - private $__passwordChanged = false; - private $__password; + private $settings; + private $_passwordChanged = false; + private $_password; public function fillNew() { $this->setAccessRank(new AccessRank(AccessRank::Anonymous)); $this->setPasswordSalt(md5(mt_rand() . uniqid())); + $this->settings = new UserSettings(); } public function fillFromDatabase($row) @@ -36,9 +37,9 @@ final class UserEntity extends AbstractEntity implements IValidatable $this->emailConfirmed = $row['email_confirmed']; $this->joinDate = $row['join_date']; $this->lastLoginDate = $row['last_login_date']; - $this->settings = $row['settings']; $this->banned = $row['banned']; $this->setAccessRank(new AccessRank($row['access_rank'])); + $this->settings = new UserSettings($row['settings']); } public function validate() @@ -47,9 +48,7 @@ final class UserEntity extends AbstractEntity implements IValidatable $this->validatePassword(); $this->validateAccessRank(); $this->validateEmails(); - - if (!$this->getSetting(UserModel::SETTING_SAFETY)) - $this->setSetting(UserModel::SETTING_SAFETY, (new PostSafety(PostSafety::Safe))->toFlag()); + $this->settings->validate(); if (empty($this->getAccessRank())) throw new Exception('No access rank detected'); @@ -88,14 +87,14 @@ final class UserEntity extends AbstractEntity implements IValidatable if (empty($this->getPasswordHash())) throw new Exception('Trying to save user with no password into database'); - if (!$this->__passwordChanged) + if (!$this->_passwordChanged) return; $config = getConfig(); $passMinLength = intval($config->registration->passMinLength); $passRegex = $config->registration->passRegex; - $password = $this->__password; + $password = $this->_password; if (strlen($password) < $passMinLength) throw new SimpleException('Password must have at least %d characters', $passMinLength); @@ -249,8 +248,8 @@ final class UserEntity extends AbstractEntity implements IValidatable public function setPassword($password) { - $this->__passwordChanged = true; - $this->__password = $password; + $this->_passwordChanged = true; + $this->_password = $password; $this->passHash = UserModel::hashPassword($password, $this->passSalt); } @@ -275,86 +274,9 @@ final class UserEntity extends AbstractEntity implements IValidatable return $url; } - public function getSetting($key) + public function getSettings() { - $settings = json_decode($this->settings, true); - return isset($settings[$key]) - ? $settings[$key] - : null; - } - - public function setSetting($key, $value) - { - $settings = json_decode($this->settings, true); - $settings[$key] = $value; - $settings = json_encode($settings); - if (strlen($settings) > 200) - throw new SimpleException('Too much data'); - $this->settings = $settings; - } - - public function hasEnabledSafety(PostSafety $safety) - { - $all = $this->getSetting(UserModel::SETTING_SAFETY); - if (!$all) - return $safety->toInteger() == (new PostSafety(PostSafety::Safe))->toInteger(); - return ($all & $safety->toFlag()) == $safety->toFlag(); - } - - public function enableSafety(PostSafety $safety, $enabled) - { - $all = $this->getSetting(UserModel::SETTING_SAFETY); - - $new = $all; - if (!$enabled) - { - $new &= ~$safety->toFlag(); - } - else - { - $new |= $safety->toFlag(); - } - - $this->setSetting(UserModel::SETTING_SAFETY, $new); - } - - public function hasEnabledHidingDislikedPosts() - { - $ret = $this->getSetting(UserModel::SETTING_HIDE_DISLIKED_POSTS); - if ($ret === null) - $ret = !getConfig()->browsing->showDislikedPostsDefault; - return $ret; - } - - public function enableHidingDislikedPosts($enabled) - { - $this->setSetting(UserModel::SETTING_HIDE_DISLIKED_POSTS, $enabled ? 1 : 0); - } - - public function hasEnabledPostTagTitles() - { - $ret = $this->getSetting(UserModel::SETTING_POST_TAG_TITLES); - if ($ret === null) - $ret = getConfig()->browsing->showPostTagTitlesDefault; - return $ret; - } - - public function enablePostTagTitles($enabled) - { - $this->setSetting(UserModel::SETTING_POST_TAG_TITLES, $enabled ? 1 : 0); - } - - public function hasEnabledEndlessScrolling() - { - $ret = $this->getSetting(UserModel::SETTING_ENDLESS_SCROLLING); - if ($ret === null) - $ret = getConfig()->browsing->endlessScrollingDefault; - return $ret; - } - - public function enableEndlessScrolling($enabled) - { - $this->setSetting(UserModel::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0); + return $this->settings; } public function confirmEmail() diff --git a/src/Models/SearchParsers/PostSearchParser.php b/src/Models/SearchParsers/PostSearchParser.php index 26574fb9..fceab77d 100644 --- a/src/Models/SearchParsers/PostSearchParser.php +++ b/src/Models/SearchParsers/PostSearchParser.php @@ -29,7 +29,7 @@ class PostSearchParser extends AbstractSearchParser protected function processTeardown() { - if (Auth::getCurrentUser()->hasEnabledHidingDislikedPosts() and !$this->showDisliked) + if (Auth::getCurrentUser()->getSettings()->hasEnabledHidingDislikedPosts() and !$this->showDisliked) $this->processComplexToken('special', 'disliked', true); if (!Access::check(new Privilege(Privilege::ListPosts, 'hidden')) or !$this->showHidden) diff --git a/src/Models/UserModel.php b/src/Models/UserModel.php index 55363b55..7d8eb137 100644 --- a/src/Models/UserModel.php +++ b/src/Models/UserModel.php @@ -4,11 +4,6 @@ use \Chibi\Database as Database; final class UserModel extends AbstractCrudModel { - const SETTING_SAFETY = 1; - const SETTING_ENDLESS_SCROLLING = 2; - const SETTING_POST_TAG_TITLES = 3; - const SETTING_HIDE_DISLIKED_POSTS = 4; - public static function getTableName() { return 'user'; @@ -32,7 +27,7 @@ final class UserModel extends AbstractCrudModel 'join_date' => $user->getJoinTime(), 'last_login_date' => $user->getLastLoginTime(), 'access_rank' => $user->getAccessRank()->toInteger(), - 'settings' => $user->settings, + 'settings' => $user->getSettings()->getAllAsSerializedString(), 'banned' => $user->isBanned(), ]; diff --git a/src/Models/UserSettings.php b/src/Models/UserSettings.php new file mode 100644 index 00000000..dbdfd044 --- /dev/null +++ b/src/Models/UserSettings.php @@ -0,0 +1,143 @@ +data = []; + if ($serializedString !== null) + $this->fillFromSerializedString($serializedString); + $this->attachDefaultSettings(); + } + + public function validate() + { + $serialized = $this->getAllAsSerializedString(); + if (strlen($serialized) > 200) + throw new SimpleException('Too much data'); + $this->ensureCorrectTypes(); + } + + public function get($key) + { + return isset($this->data[$key]) + ? $this->data[$key] + : null; + } + + public function set($key, $value) + { + $this->data[$key] = $value; + } + + public function getAllAsSerializedString() + { + return json_encode($this->data); + } + + public function getAllAsArray() + { + return $this->data; + } + + + public function hasEnabledSafety(PostSafety $safety) + { + $all = $this->get(self::SETTING_SAFETY); + return ($all & $safety->toFlag()) == $safety->toFlag(); + } + + public function enableSafety(PostSafety $safety, $enabled) + { + $new = $this->get(self::SETTING_SAFETY); + + if (!$enabled) + $new &= ~$safety->toFlag(); + else + $new |= $safety->toFlag(); + + $this->set(self::SETTING_SAFETY, $new); + $this->attachDefaultSettings(); + } + + public function hasEnabledHidingDislikedPosts() + { + return $this->get(self::SETTING_HIDE_DISLIKED_POSTS); + } + + public function enableHidingDislikedPosts($enabled) + { + $this->set(self::SETTING_HIDE_DISLIKED_POSTS, $enabled); + $this->attachDefaultSettings(); + } + + public function hasEnabledPostTagTitles() + { + return $this->get(self::SETTING_POST_TAG_TITLES); + } + + public function enablePostTagTitles($enabled) + { + $this->set(self::SETTING_POST_TAG_TITLES, $enabled); + $this->attachDefaultSettings(); + } + + public function hasEnabledEndlessScrolling() + { + return $this->get(self::SETTING_ENDLESS_SCROLLING); + } + + public function enableEndlessScrolling($enabled) + { + $this->set(self::SETTING_ENDLESS_SCROLLING, $enabled); + $this->attachDefaultSettings(); + } + + private function fillFromSerializedString($string) + { + $this->data = json_decode($string, true); + } + + private function attachDefaultSettings() + { + if ($this->get(self::SETTING_SAFETY) === null or $this->get(self::SETTING_SAFETY) === 0) + $this->set(self::SETTING_SAFETY, (new PostSafety(PostSafety::Safe))->toInteger()); + + if ($this->get(self::SETTING_HIDE_DISLIKED_POSTS) === null) + $this->set(self::SETTING_HIDE_DISLIKED_POSTS, !(bool) getConfig()->browsing->showDislikedPostsDefault); + + if ($this->get(self::SETTING_POST_TAG_TITLES) === null) + $this->set(self::SETTING_POST_TAG_TITLES, (bool) getConfig()->browsing->showPostTagTitlesDefault); + + if ($this->get(self::SETTING_ENDLESS_SCROLLING) === null) + $this->set(self::SETTING_ENDLESS_SCROLLING, (bool) getConfig()->browsing->endlessScrollingDefault); + } + + private function ensureCorrectTypes() + { + $makeInt = ['TextHelper', 'toIntegerOrNull']; + $makeBool = ['TextHelper', 'toBooleanOrNull']; + + $types = + [ + [self::SETTING_SAFETY, $makeInt], + [self::SETTING_HIDE_DISLIKED_POSTS, $makeBool], + [self::SETTING_POST_TAG_TITLES, $makeBool], + [self::SETTING_ENDLESS_SCROLLING, $makeBool], + ]; + + foreach ($types as $item) + { + list ($setting, $func) = $item; + $this->set($setting, $func($this->get($setting))); + } + + $this->attachDefaultSettings(); + } +} diff --git a/src/Views/paginator.phtml b/src/Views/paginator.phtml index 2dfdc3f6..0582293c 100644 --- a/src/Views/paginator.phtml +++ b/src/Views/paginator.phtml @@ -42,7 +42,7 @@ if (!function_exists('pageUrl')) hasEnabledEndlessScrolling()) + if (Auth::getCurrentUser()->getSettings()->hasEnabledEndlessScrolling()) Assets::addScript('paginator-endless.js'); ?> diff --git a/src/Views/post-small.phtml b/src/Views/post-small.phtml index 78a87318..5e8086eb 100644 --- a/src/Views/post-small.phtml +++ b/src/Views/post-small.phtml @@ -41,7 +41,7 @@ if ($masstag) hasEnabledPostTagTitles()): ?> + getSettings()->hasEnabledPostTagTitles()): ?> title="context->post->getTags()) ?>" href=" $this->context->post->getId()]) ?>"> diff --git a/src/Views/top-navigation.phtml b/src/Views/top-navigation.phtml index 1c15ee1e..6b1ce145 100644 --- a/src/Views/top-navigation.phtml +++ b/src/Views/top-navigation.phtml @@ -119,13 +119,15 @@ TextCaseConverter::CAMEL_CASE, TextCaseConverter::SPINAL_CASE) ?>"> - diff --git a/src/Views/user-list.phtml b/src/Views/user-list.phtml index 3fca6dbe..13899dfd 100644 --- a/src/Views/user-list.phtml +++ b/src/Views/user-list.phtml @@ -1,9 +1,6 @@ hasEnabledEndlessScrolling()) - Assets::addScript('paginator-endless.js'); ?>