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="= TextHelper::reprTags($this->context->post->getTags()) ?>"
href="= \Chibi\Router::linkTo(['PostController', 'genericView'], ['id' => $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');
?>