Added support for custom avatars
This commit is contained in:
parent
3051f37587
commit
fee19c61bc
27 changed files with 395 additions and 9 deletions
|
@ -7,6 +7,7 @@ filesPath = "./data/files/"
|
||||||
logsPath = "./data/logs/{yyyy}-{mm}.log"
|
logsPath = "./data/logs/{yyyy}-{mm}.log"
|
||||||
mediaPath = "./public_html/media/"
|
mediaPath = "./public_html/media/"
|
||||||
thumbnailsPath = "./public_html/thumbs/"
|
thumbnailsPath = "./public_html/thumbs/"
|
||||||
|
avatarsPath = "./public_html/avatars/"
|
||||||
title = "szurubooru"
|
title = "szurubooru"
|
||||||
salt = "1A2/$_4xVa"
|
salt = "1A2/$_4xVa"
|
||||||
|
|
||||||
|
@ -129,6 +130,8 @@ editUserEmail.all=admin
|
||||||
editUserEmailNoConfirm=admin
|
editUserEmailNoConfirm=admin
|
||||||
editUserAccessRank=admin
|
editUserAccessRank=admin
|
||||||
editUserName=moderator
|
editUserName=moderator
|
||||||
|
editUserAvatar.own=registered
|
||||||
|
editUserAvatar.all=admin
|
||||||
editUserSettings.own=registered
|
editUserSettings.own=registered
|
||||||
editUserSettings.all=nobody
|
editUserSettings.all=nobody
|
||||||
acceptUserRegistration=moderator
|
acceptUserRegistration=moderator
|
||||||
|
|
2
public_html/avatars/.gitignore
vendored
Normal file
2
public_html/avatars/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
11
public_html/media/js/user-edit.js
Normal file
11
public_html/media/js/user-edit.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
$(function()
|
||||||
|
{
|
||||||
|
$('.avatar-content').parents('.form-row').hide();
|
||||||
|
$('.avatar-style').click(function()
|
||||||
|
{
|
||||||
|
if ($(this).val() == '2'/*custom*/)
|
||||||
|
{
|
||||||
|
$('.avatar-content').parents('.form-row').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -39,6 +39,8 @@ class JobArgs
|
||||||
const ARG_NEW_EMAIL = 'new-email';
|
const ARG_NEW_EMAIL = 'new-email';
|
||||||
const ARG_NEW_USER_NAME = 'new-user-name';
|
const ARG_NEW_USER_NAME = 'new-user-name';
|
||||||
const ARG_NEW_PASSWORD = 'new-password';
|
const ARG_NEW_PASSWORD = 'new-password';
|
||||||
|
const ARG_NEW_AVATAR_CONTENT = 'new-avatar-content';
|
||||||
|
const ARG_NEW_AVATAR_STYLE = 'new-avatar-style';
|
||||||
const ARG_NEW_SETTINGS = 'new-settings';
|
const ARG_NEW_SETTINGS = 'new-settings';
|
||||||
|
|
||||||
const ARG_NEW_POST_SCORE = 'new-post-score';
|
const ARG_NEW_POST_SCORE = 'new-post-score';
|
||||||
|
|
64
src/Api/Jobs/UserJobs/EditUserAvatarJob.php
Normal file
64
src/Api/Jobs/UserJobs/EditUserAvatarJob.php
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
class EditUserAvatarJob extends AbstractJob
|
||||||
|
{
|
||||||
|
protected $userRetriever;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->userRetriever = new UserRetriever($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$user = $this->userRetriever->retrieve();
|
||||||
|
$state = $this->getArgument(JobArgs::ARG_NEW_AVATAR_STYLE);
|
||||||
|
|
||||||
|
if ($state == UserAvatarStyle::Custom)
|
||||||
|
{
|
||||||
|
$file = $this->getArgument(JobArgs::ARG_NEW_AVATAR_CONTENT);
|
||||||
|
$user->setCustomAvatarFromPath($file->filePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$user->setAvatarStyle(new UserAvatarStyle($state));
|
||||||
|
|
||||||
|
if ($this->getContext() == self::CONTEXT_NORMAL)
|
||||||
|
UserModel::save($user);
|
||||||
|
|
||||||
|
Logger::log('{user} changed avatar for {subject}', [
|
||||||
|
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
|
||||||
|
'subject' => TextHelper::reprUser($user)]);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredArguments()
|
||||||
|
{
|
||||||
|
return JobArgs::Conjunction(
|
||||||
|
$this->userRetriever->getRequiredArguments(),
|
||||||
|
JobArgs::ARG_NEW_AVATAR_STYLE,
|
||||||
|
JobArgs::Optional(JobArgs::ARG_NEW_AVATAR_CONTENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredMainPrivilege()
|
||||||
|
{
|
||||||
|
return $this->getContext() == self::CONTEXT_BATCH_ADD
|
||||||
|
? Privilege::RegisterAccount
|
||||||
|
: Privilege::EditUserAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredSubPrivileges()
|
||||||
|
{
|
||||||
|
return Access::getIdentity($this->userRetriever->retrieve());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAuthenticationRequired()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isConfirmedEmailRequired()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ class EditUserJob extends AbstractJob
|
||||||
$this->addSubJob(new EditUserNameJob());
|
$this->addSubJob(new EditUserNameJob());
|
||||||
$this->addSubJob(new EditUserPasswordJob());
|
$this->addSubJob(new EditUserPasswordJob());
|
||||||
$this->addSubJob(new EditUserEmailJob());
|
$this->addSubJob(new EditUserEmailJob());
|
||||||
|
$this->addSubJob(new EditUserAvatarJob());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canEditAnything($user)
|
public function canEditAnything($user)
|
||||||
|
|
|
@ -76,12 +76,25 @@ class UserController extends AbstractController
|
||||||
JobArgs::ARG_NEW_PASSWORD => InputHelper::get('password1'),
|
JobArgs::ARG_NEW_PASSWORD => InputHelper::get('password1'),
|
||||||
JobArgs::ARG_NEW_EMAIL => InputHelper::get('email'),
|
JobArgs::ARG_NEW_EMAIL => InputHelper::get('email'),
|
||||||
JobArgs::ARG_NEW_ACCESS_RANK => InputHelper::get('access-rank'),
|
JobArgs::ARG_NEW_ACCESS_RANK => InputHelper::get('access-rank'),
|
||||||
|
Jobargs::ARG_NEW_AVATAR_STYLE => InputHelper::get('avatar-style'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (!empty($_FILES['avatar-content']['name']))
|
||||||
|
{
|
||||||
|
$file = $_FILES['avatar-content'];
|
||||||
|
TransferHelper::handleUploadErrors($file);
|
||||||
|
|
||||||
|
$args[JobArgs::ARG_NEW_AVATAR_CONTENT] = new ApiFileInput(
|
||||||
|
$file['tmp_name'],
|
||||||
|
$file['name']);
|
||||||
|
}
|
||||||
|
|
||||||
$args = $this->appendUserIdentifierArgument($args, $identifier);
|
$args = $this->appendUserIdentifierArgument($args, $identifier);
|
||||||
|
|
||||||
$args = array_filter($args);
|
$args = array_filter($args);
|
||||||
$user = Api::run(new EditUserJob(), $args);
|
$user = Api::run(new EditUserJob(), $args);
|
||||||
|
|
||||||
|
Core::getContext()->transport->user = $user;
|
||||||
if (Auth::getCurrentUser()->getId() == $user->getId())
|
if (Auth::getCurrentUser()->getId() == $user->getId())
|
||||||
Auth::setCurrentUser($user);
|
Auth::setCurrentUser($user);
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ class Privilege extends AbstractEnum implements IEnum
|
||||||
const EditUserEmail = 'editUserEmail';
|
const EditUserEmail = 'editUserEmail';
|
||||||
const EditUserEmailNoConfirm = 'editUserEmailNoConfirm';
|
const EditUserEmailNoConfirm = 'editUserEmailNoConfirm';
|
||||||
const EditUserName = 'editUserName';
|
const EditUserName = 'editUserName';
|
||||||
|
const EditUserAvatar = 'editUserAvatar';
|
||||||
const EditUserSettings = 'editUserSettings';
|
const EditUserSettings = 'editUserSettings';
|
||||||
const DeleteUser = 'deleteUser';
|
const DeleteUser = 'deleteUser';
|
||||||
const FlagUser = 'flagUser';
|
const FlagUser = 'flagUser';
|
||||||
|
|
44
src/Enums/UserAvatarStyle.php
Normal file
44
src/Enums/UserAvatarStyle.php
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
class UserAvatarStyle extends AbstractEnum implements IEnum, IValidatable
|
||||||
|
{
|
||||||
|
const Gravatar = 1;
|
||||||
|
const Custom = 2;
|
||||||
|
const None = 3;
|
||||||
|
|
||||||
|
private $type;
|
||||||
|
|
||||||
|
public function __construct($type)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toInteger()
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString()
|
||||||
|
{
|
||||||
|
switch ($this->type)
|
||||||
|
{
|
||||||
|
case self::None: return 'none';
|
||||||
|
case self::Gravatar: return 'gravatar';
|
||||||
|
case self::Custom: return 'custom';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAll()
|
||||||
|
{
|
||||||
|
return array_map(function($constantName)
|
||||||
|
{
|
||||||
|
return new self($constantName);
|
||||||
|
}, self::getAllConstants());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate()
|
||||||
|
{
|
||||||
|
if (!in_array($this->type, self::getAllConstants()))
|
||||||
|
throw new SimpleException('Invalid user picture type "%s"', $this->type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -391,7 +391,7 @@ final class PostEntity extends AbstractEntity implements IValidatable, ISerializ
|
||||||
{
|
{
|
||||||
$width = Core::getConfig()->browsing->thumbnailWidth;
|
$width = Core::getConfig()->browsing->thumbnailWidth;
|
||||||
$height = Core::getConfig()->browsing->thumbnailHeight;
|
$height = Core::getConfig()->browsing->thumbnailHeight;
|
||||||
$dstPath = $this->getThumbnailPath($width, $height);
|
$dstPath = $this->getThumbnailPath();
|
||||||
|
|
||||||
$thumbnailGenerator = new SmartThumbnailGenerator();
|
$thumbnailGenerator = new SmartThumbnailGenerator();
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ final class UserEntity extends AbstractEntity implements IValidatable, ISerializ
|
||||||
private $lastLoginDate;
|
private $lastLoginDate;
|
||||||
private $accessRank;
|
private $accessRank;
|
||||||
private $banned = false;
|
private $banned = false;
|
||||||
|
private $avatarStyle;
|
||||||
|
|
||||||
private $settings;
|
private $settings;
|
||||||
private $_passwordChanged = false;
|
private $_passwordChanged = false;
|
||||||
|
@ -23,6 +24,7 @@ final class UserEntity extends AbstractEntity implements IValidatable, ISerializ
|
||||||
{
|
{
|
||||||
$this->setAccessRank(new AccessRank(AccessRank::Anonymous));
|
$this->setAccessRank(new AccessRank(AccessRank::Anonymous));
|
||||||
$this->setPasswordSalt(md5(mt_rand() . uniqid()));
|
$this->setPasswordSalt(md5(mt_rand() . uniqid()));
|
||||||
|
$this->avatarStyle = new UserAvatarStyle(UserAvatarStyle::Gravatar);
|
||||||
$this->settings = new UserSettings();
|
$this->settings = new UserSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@ final class UserEntity extends AbstractEntity implements IValidatable, ISerializ
|
||||||
$this->lastLoginDate = TextHelper::toIntegerOrNull($row['last_login_date']);
|
$this->lastLoginDate = TextHelper::toIntegerOrNull($row['last_login_date']);
|
||||||
$this->banned = $row['banned'];
|
$this->banned = $row['banned'];
|
||||||
$this->setAccessRank(new AccessRank($row['access_rank']));
|
$this->setAccessRank(new AccessRank($row['access_rank']));
|
||||||
|
$this->avatarStyle = new UserAvatarStyle($row['avatar_style']);
|
||||||
$this->settings = new UserSettings($row['settings']);
|
$this->settings = new UserSettings($row['settings']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +64,7 @@ final class UserEntity extends AbstractEntity implements IValidatable, ISerializ
|
||||||
$this->validateAccessRank();
|
$this->validateAccessRank();
|
||||||
$this->validateEmails();
|
$this->validateEmails();
|
||||||
$this->settings->validate();
|
$this->settings->validate();
|
||||||
|
$this->avatarStyle->validate();
|
||||||
|
|
||||||
if (empty($this->getAccessRank()))
|
if (empty($this->getAccessRank()))
|
||||||
throw new Exception('No access rank detected');
|
throw new Exception('No access rank detected');
|
||||||
|
@ -276,14 +280,44 @@ final class UserEntity extends AbstractEntity implements IValidatable, ISerializ
|
||||||
$this->accessRank = $accessRank;
|
$this->accessRank = $accessRank;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAvatarStyle()
|
||||||
|
{
|
||||||
|
return $this->avatarStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAvatarStyle(UserAvatarStyle $userAvatarStyle)
|
||||||
|
{
|
||||||
|
$this->avatarStyle = $userAvatarStyle;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAvatarUrl($size = 32)
|
public function getAvatarUrl($size = 32)
|
||||||
{
|
{
|
||||||
$subject = !empty($this->getConfirmedEmail())
|
switch ($this->avatarStyle->toInteger())
|
||||||
? $this->getConfirmedEmail()
|
{
|
||||||
: $this->passSalt . $this->getName();
|
case UserAvatarStyle::None:
|
||||||
$hash = md5(strtolower(trim($subject)));
|
return $this->getBlankAvatarUrl($size);
|
||||||
$url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
|
|
||||||
return $url;
|
case UserAvatarStyle::Gravatar:
|
||||||
|
return $this->getGravatarAvatarUrl($size);
|
||||||
|
|
||||||
|
case UserAvatarStyle::Custom:
|
||||||
|
return $this->getCustomAvatarUrl($size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCustomAvatarFromPath($srcPath)
|
||||||
|
{
|
||||||
|
$config = Core::getConfig();
|
||||||
|
|
||||||
|
$mimeType = mime_content_type($srcPath);
|
||||||
|
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
|
||||||
|
throw new SimpleException('Invalid file type "%s"', $mimeType);
|
||||||
|
|
||||||
|
$dstPath = $this->getCustomAvatarSourcePath();
|
||||||
|
|
||||||
|
TransferHelper::copy($srcPath, $dstPath);
|
||||||
|
$this->removeOldCustomAvatar();
|
||||||
|
$this->setAvatarStyle(new UserAvatarStyle(UserAvatarStyle::Custom));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSettings()
|
public function getSettings()
|
||||||
|
@ -351,4 +385,67 @@ final class UserEntity extends AbstractEntity implements IValidatable, ISerializ
|
||||||
$stmt->setCriterion(new Sql\EqualsFunctor('uploader_id', new Sql\Binding($this->getId())));
|
$stmt->setCriterion(new Sql\EqualsFunctor('uploader_id', new Sql\Binding($this->getId())));
|
||||||
return (int) Database::fetchOne($stmt)['count'];
|
return (int) Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getBlankAvatarUrl($size)
|
||||||
|
{
|
||||||
|
return 'http://www.gravatar.com/avatar/?s=' . $size . '&d=mm';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getGravatarAvatarUrl($size)
|
||||||
|
{
|
||||||
|
$subject = !empty($this->getConfirmedEmail())
|
||||||
|
? $this->getConfirmedEmail()
|
||||||
|
: $this->passSalt . $this->getName();
|
||||||
|
$hash = md5(strtolower(trim($subject)));
|
||||||
|
return 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomAvatarUrl($size)
|
||||||
|
{
|
||||||
|
$fileName = md5($this->getName()) . '-' . $size . '.avatar';
|
||||||
|
$path = $this->getCustomAvatarPath($size);
|
||||||
|
if (!file_exists($path))
|
||||||
|
$this->generateCustomAvatar($size);
|
||||||
|
if (file_exists($path))
|
||||||
|
return \Chibi\Util\Url::makeAbsolute('/avatars/' . $fileName);
|
||||||
|
return $this->getBlankAvatarUrl($size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomAvatarSourcePath()
|
||||||
|
{
|
||||||
|
$fileName = md5($this->getName()) . '.avatar_source';
|
||||||
|
return Core::getConfig()->main->avatarsPath . DS . $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomAvatarPath($size)
|
||||||
|
{
|
||||||
|
$fileName = md5($this->getName()) . '-' . $size . '.avatar';
|
||||||
|
return Core::getConfig()->main->avatarsPath . DS . $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomAvatarPaths()
|
||||||
|
{
|
||||||
|
$hash = md5($this->getName());
|
||||||
|
return glob(Core::getConfig()->main->avatarsPath . DS . $hash . '*.avatar');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeOldCustomAvatar()
|
||||||
|
{
|
||||||
|
foreach ($this->getCustomAvatarPaths() as $path)
|
||||||
|
TransferHelper::remove($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateCustomAvatar($size)
|
||||||
|
{
|
||||||
|
$srcPath = $this->getCustomAvatarSourcePath($size);
|
||||||
|
$dstPath = $this->getCustomAvatarPath($size);
|
||||||
|
|
||||||
|
$thumbnailGenerator = new ImageThumbnailGenerator();
|
||||||
|
return $thumbnailGenerator->generateFromFile(
|
||||||
|
$srcPath,
|
||||||
|
$dstPath,
|
||||||
|
$size,
|
||||||
|
$size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ final class UserModel extends AbstractCrudModel
|
||||||
'access_rank' => $user->getAccessRank()->toInteger(),
|
'access_rank' => $user->getAccessRank()->toInteger(),
|
||||||
'settings' => $user->getSettings()->getAllAsSerializedString(),
|
'settings' => $user->getSettings()->getAllAsSerializedString(),
|
||||||
'banned' => $user->isBanned() ? 1 : 0,
|
'banned' => $user->isBanned() ? 1 : 0,
|
||||||
|
'avatar_style' => $user->getAvatarStyle()->toInteger(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$stmt = (new Sql\UpdateStatement)
|
$stmt = (new Sql\UpdateStatement)
|
||||||
|
|
1
src/Upgrades/mysql/Upgrade15.sql
Normal file
1
src/Upgrades/mysql/Upgrade15.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE user ADD COLUMN avatar_style INTEGER DEFAULT 1;
|
1
src/Upgrades/sqlite/Upgrade15.sql
Normal file
1
src/Upgrades/sqlite/Upgrade15.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE user ADD COLUMN avatar_style INTEGER DEFAULT 1;
|
|
@ -19,7 +19,7 @@ $noAutocomplete = isset($this->context->noAutocomplete) ? true : false;
|
||||||
<?php if ($noAutocomplete): ?>
|
<?php if ($noAutocomplete): ?>
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php if ($inputClass != ''): ?>
|
<?php if ($inputClass): ?>
|
||||||
class="<?= $inputClass ?>"
|
class="<?= $inputClass ?>"
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
type="<?= $type ?>"
|
type="<?= $type ?>"
|
||||||
|
|
|
@ -7,6 +7,7 @@ $optionLabels = $this->context->optionLabels;
|
||||||
$optionNames = $this->context->optionNames;
|
$optionNames = $this->context->optionNames;
|
||||||
$optionStates = $this->context->optionStates;
|
$optionStates = $this->context->optionStates;
|
||||||
$keys = array_keys($optionNames);
|
$keys = array_keys($optionNames);
|
||||||
|
$inputClass = isset($this->context->inputClass) ? $this->context->inputClass : '';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -17,6 +18,9 @@ $keys = array_keys($optionNames);
|
||||||
|
|
||||||
<?php foreach ($keys as $key): ?>
|
<?php foreach ($keys as $key): ?>
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
|
<?php if ($inputClass): ?>
|
||||||
|
class="<?= $inputclass ?>"
|
||||||
|
<?php endif ?>
|
||||||
name="<?= $optionNames[$key] ?>"
|
name="<?= $optionNames[$key] ?>"
|
||||||
value="<?= $optionValuesDisabled[$key] ?>"/>
|
value="<?= $optionValuesDisabled[$key] ?>"/>
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ $optionValues = $this->context->optionValues;
|
||||||
$optionLabels = $this->context->optionLabels;
|
$optionLabels = $this->context->optionLabels;
|
||||||
$activeOptionValue = $this->context->activeOptionValue;
|
$activeOptionValue = $this->context->activeOptionValue;
|
||||||
$keys = array_keys($optionValues);
|
$keys = array_keys($optionValues);
|
||||||
|
$inputClass = isset($this->context->inputClass) ? $this->context->inputClass : '';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -18,6 +19,9 @@ $keys = array_keys($optionValues);
|
||||||
<label>
|
<label>
|
||||||
<input type="radio"
|
<input type="radio"
|
||||||
name="<?= $name ?>"
|
name="<?= $name ?>"
|
||||||
|
<?php if ($inputClass): ?>
|
||||||
|
class="<?= $inputClass ?>"
|
||||||
|
<?php endif ?>
|
||||||
<?php if (count($keys) == 1): ?>
|
<?php if (count($keys) == 1): ?>
|
||||||
id="<?= $id ?>"
|
id="<?= $id ?>"
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
|
@ -6,6 +6,7 @@ $optionValues = $this->context->optionValues;
|
||||||
$optionLabels = $this->context->optionLabels;
|
$optionLabels = $this->context->optionLabels;
|
||||||
$activeOptionValue = isset($this->context->activeOptionValue) ? $this->context->activeOptionValue : null;
|
$activeOptionValue = isset($this->context->activeOptionValue) ? $this->context->activeOptionValue : null;
|
||||||
$keys = array_keys($optionValues);
|
$keys = array_keys($optionValues);
|
||||||
|
$inputClass = isset($this->context->inputClass) ? $this->context->inputClass : '';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -13,7 +14,13 @@ $keys = array_keys($optionValues);
|
||||||
<?= $label ?>
|
<?= $label ?>
|
||||||
</label>
|
</label>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<select name="<?= $name ?>" id="<?= $id ?>">
|
<select
|
||||||
|
<?php if ($inputClass): ?>
|
||||||
|
class="<?= $inputclass ?>"
|
||||||
|
<?php endif ?>
|
||||||
|
name="<?= $name ?>"
|
||||||
|
id="<?= $id ?>">
|
||||||
|
|
||||||
<?php foreach ($keys as $key): ?>
|
<?php foreach ($keys as $key): ?>
|
||||||
<?php if ($activeOptionValue == $optionValues[$key]): ?>
|
<?php if ($activeOptionValue == $optionValues[$key]): ?>
|
||||||
<option value="<?= $optionValues[$key] ?>" selected="selected">
|
<option value="<?= $optionValues[$key] ?>" selected="selected">
|
||||||
|
@ -23,6 +30,7 @@ $keys = array_keys($optionValues);
|
||||||
<?= $optionLabels[$key] ?>
|
<?= $optionLabels[$key] ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
<?php
|
||||||
|
$this->assets->addScript('user-edit.js');
|
||||||
|
?>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
action="<?= \Chibi\Router::linkTo(
|
action="<?= \Chibi\Router::linkTo(
|
||||||
['UserController', 'editAction'],
|
['UserController', 'editAction'],
|
||||||
['identifier' => $this->context->transport->user->getName()]) ?>"
|
['identifier' => $this->context->transport->user->getName()]) ?>"
|
||||||
|
enctype="multipart/form-data"
|
||||||
method="post"
|
method="post"
|
||||||
class="edit"
|
class="edit"
|
||||||
autocomplete="off">
|
autocomplete="off">
|
||||||
|
@ -17,6 +22,26 @@
|
||||||
echo '<hr/>';
|
echo '<hr/>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Access::check(new Privilege(
|
||||||
|
Privilege::EditUserAvatar,
|
||||||
|
Access::getIdentity($this->context->transport->user))))
|
||||||
|
{
|
||||||
|
$styles = UserAvatarStyle::getAll();
|
||||||
|
$context = new StdClass;
|
||||||
|
$context->name = 'avatar-style';
|
||||||
|
$context->label = 'User picture';
|
||||||
|
$context->optionValues = array_map(function($s) { return $s->toInteger(); }, $styles);
|
||||||
|
$context->optionLabels = array_map(function($s) { return ucfirst($s->toDisplayString()); }, $styles);
|
||||||
|
$context->activeOptionValue = $this->context->transport->user->getAvatarStyle()->toInteger();
|
||||||
|
$context->inputClass = 'avatar-style';
|
||||||
|
$this->renderExternal('input-radioboxes', $context);
|
||||||
|
|
||||||
|
$context = new StdClass;
|
||||||
|
$context->name = 'avatar-content';
|
||||||
|
$context->inputClass = 'avatar-content';
|
||||||
|
$this->renderExternal('input-file', $context);
|
||||||
|
}
|
||||||
|
|
||||||
if (Access::check(new Privilege(
|
if (Access::check(new Privilege(
|
||||||
Privilege::EditUserName,
|
Privilege::EditUserName,
|
||||||
Access::getIdentity($this->context->transport->user))))
|
Access::getIdentity($this->context->transport->user))))
|
||||||
|
|
|
@ -68,6 +68,7 @@ final class Core
|
||||||
|
|
||||||
TransferHelper::createDirectory($config->main->filesPath);
|
TransferHelper::createDirectory($config->main->filesPath);
|
||||||
TransferHelper::createDirectory($config->main->thumbnailsPath);
|
TransferHelper::createDirectory($config->main->thumbnailsPath);
|
||||||
|
TransferHelper::createDirectory($config->main->avatarsPath);
|
||||||
|
|
||||||
//extension sanity checks
|
//extension sanity checks
|
||||||
$requiredExtensions = ['pdo', 'pdo_' . $config->main->dbDriver, 'openssl', 'fileinfo'];
|
$requiredExtensions = ['pdo', 'pdo_' . $config->main->dbDriver, 'openssl', 'fileinfo'];
|
||||||
|
|
|
@ -113,6 +113,7 @@ class SzurubooruTestRunner implements ITestRunner
|
||||||
[
|
[
|
||||||
realpath(Core::getConfig()->main->filesPath),
|
realpath(Core::getConfig()->main->filesPath),
|
||||||
realpath(Core::getConfig()->main->thumbnailsPath),
|
realpath(Core::getConfig()->main->thumbnailsPath),
|
||||||
|
realpath(Core::getConfig()->main->avatarsPath),
|
||||||
realpath(dirname(Core::getConfig()->main->logsPath)),
|
realpath(dirname(Core::getConfig()->main->logsPath)),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,15 @@ class ApiArgumentTest extends AbstractFullApiTest
|
||||||
JobArgs::ARG_NEW_USER_NAME));
|
JobArgs::ARG_NEW_USER_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testEditUserAvatarJob()
|
||||||
|
{
|
||||||
|
$this->testArguments(new EditUserAvatarJob(),
|
||||||
|
JobArgs::Conjunction(
|
||||||
|
$this->getUserSelector(),
|
||||||
|
JobArgs::ARG_NEW_AVATAR_STYLE,
|
||||||
|
JobArgs::Optional(JobArgs::ARG_NEW_AVATAR_CONTENT)));
|
||||||
|
}
|
||||||
|
|
||||||
public function testEditUserPasswordJob()
|
public function testEditUserPasswordJob()
|
||||||
{
|
{
|
||||||
$this->testArguments(new EditUserPasswordJob(),
|
$this->testArguments(new EditUserPasswordJob(),
|
||||||
|
|
|
@ -24,6 +24,7 @@ class ApiAuthTest extends AbstractFullApiTest
|
||||||
$this->testAuth(new EditUserEmailJob(), false);
|
$this->testAuth(new EditUserEmailJob(), false);
|
||||||
$this->testAuth(new EditUserNameJob(), false);
|
$this->testAuth(new EditUserNameJob(), false);
|
||||||
$this->testAuth(new EditUserPasswordJob(), false);
|
$this->testAuth(new EditUserPasswordJob(), false);
|
||||||
|
$this->testAuth(new EditUserAvatarJob(), false);
|
||||||
$this->testAuth(new EditUserSettingsJob(), false);
|
$this->testAuth(new EditUserSettingsJob(), false);
|
||||||
$this->testAuth(new FeaturePostJob(), true);
|
$this->testAuth(new FeaturePostJob(), true);
|
||||||
$this->testAuth(new FlagPostJob(), false);
|
$this->testAuth(new FlagPostJob(), false);
|
||||||
|
|
|
@ -25,6 +25,7 @@ class ApiEmailRequirementsTest extends AbstractFullApiTest
|
||||||
$this->testRegularEmailRequirement(new EditUserEmailJob());
|
$this->testRegularEmailRequirement(new EditUserEmailJob());
|
||||||
$this->testRegularEmailRequirement(new EditUserNameJob());
|
$this->testRegularEmailRequirement(new EditUserNameJob());
|
||||||
$this->testRegularEmailRequirement(new EditUserPasswordJob());
|
$this->testRegularEmailRequirement(new EditUserPasswordJob());
|
||||||
|
$this->testRegularEmailRequirement(new EditUserAvatarJob());
|
||||||
$this->testRegularEmailRequirement(new EditUserSettingsJob());
|
$this->testRegularEmailRequirement(new EditUserSettingsJob());
|
||||||
$this->testRegularEmailRequirement(new FeaturePostJob());
|
$this->testRegularEmailRequirement(new FeaturePostJob());
|
||||||
$this->testRegularEmailRequirement(new FlagPostJob());
|
$this->testRegularEmailRequirement(new FlagPostJob());
|
||||||
|
|
|
@ -134,6 +134,7 @@ class ApiPrivilegeTest extends AbstractFullApiTest
|
||||||
$this->testDynamicUserPrivilege(new EditUserEmailJob(), Privilege::EditUserEmail);
|
$this->testDynamicUserPrivilege(new EditUserEmailJob(), Privilege::EditUserEmail);
|
||||||
$this->testDynamicUserPrivilege(new EditUserNameJob(), Privilege::EditUserName);
|
$this->testDynamicUserPrivilege(new EditUserNameJob(), Privilege::EditUserName);
|
||||||
$this->testDynamicUserPrivilege(new EditUserPasswordJob(), Privilege::EditUserPassword);
|
$this->testDynamicUserPrivilege(new EditUserPasswordJob(), Privilege::EditUserPassword);
|
||||||
|
$this->testDynamicUserPrivilege(new EditUserAvatarJob(), Privilege::EditUserAvatar);
|
||||||
$this->testDynamicUserPrivilege(new EditUserSettingsJob(), Privilege::EditUserSettings);
|
$this->testDynamicUserPrivilege(new EditUserSettingsJob(), Privilege::EditUserSettings);
|
||||||
|
|
||||||
$ctx = function($job)
|
$ctx = function($job)
|
||||||
|
@ -145,6 +146,7 @@ class ApiPrivilegeTest extends AbstractFullApiTest
|
||||||
$this->testDynamicUserPrivilege($ctx(new EditUserEmailJob()), Privilege::RegisterAccount);
|
$this->testDynamicUserPrivilege($ctx(new EditUserEmailJob()), Privilege::RegisterAccount);
|
||||||
$this->testDynamicUserPrivilege($ctx(new EditUserNameJob()), Privilege::RegisterAccount);
|
$this->testDynamicUserPrivilege($ctx(new EditUserNameJob()), Privilege::RegisterAccount);
|
||||||
$this->testDynamicUserPrivilege($ctx(new EditUserPasswordJob()), Privilege::RegisterAccount);
|
$this->testDynamicUserPrivilege($ctx(new EditUserPasswordJob()), Privilege::RegisterAccount);
|
||||||
|
$this->testDynamicUserPrivilege($ctx(new EditUserAvatarJob()), Privilege::RegisterAccount);
|
||||||
$this->testDynamicUserPrivilege($ctx(new EditUserSettingsJob()), Privilege::EditUserSettings);
|
$this->testDynamicUserPrivilege($ctx(new EditUserSettingsJob()), Privilege::EditUserSettings);
|
||||||
|
|
||||||
$this->testDynamicUserPrivilege(new FlagUserJob(), Privilege::FlagUser);
|
$this->testDynamicUserPrivilege(new FlagUserJob(), Privilege::FlagUser);
|
||||||
|
|
88
tests/Tests/JobTests/EditUserAvatarJobTest.php
Normal file
88
tests/Tests/JobTests/EditUserAvatarJobTest.php
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
class EditUserAvatarJobTest extends AbstractTest
|
||||||
|
{
|
||||||
|
public function testGravatar()
|
||||||
|
{
|
||||||
|
$this->grantAccess('editUserAvatar');
|
||||||
|
$user = $this->userMocker->mockSingle();
|
||||||
|
|
||||||
|
$this->assert->areEqual(UserAvatarStyle::Gravatar, $user->getAvatarStyle()->toInteger());
|
||||||
|
|
||||||
|
$user = $this->assert->doesNotThrow(function() use ($user)
|
||||||
|
{
|
||||||
|
return Api::run(
|
||||||
|
new EditUserAvatarJob(),
|
||||||
|
[
|
||||||
|
JobArgs::ARG_USER_NAME => $user->getName(),
|
||||||
|
JobArgs::ARG_NEW_AVATAR_STYLE => UserAvatarStyle::Gravatar,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assert->areEqual(UserAvatarStyle::Gravatar, $user->getAvatarStyle()->toInteger());
|
||||||
|
|
||||||
|
$hash = md5($user->getPasswordSalt() . $user->getName());
|
||||||
|
$this->assert->isTrue(strpos($user->getAvatarUrl(), $hash) !== false);
|
||||||
|
|
||||||
|
$mail = 'postmaster@mordor.cx';
|
||||||
|
$user->setConfirmedEmail($mail);
|
||||||
|
UserModel::save($user);
|
||||||
|
|
||||||
|
$hash = md5($mail);
|
||||||
|
$this->assert->isTrue(strpos($user->getAvatarUrl(), $hash) !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmpty()
|
||||||
|
{
|
||||||
|
$this->grantAccess('editUserAvatar');
|
||||||
|
$user = $this->userMocker->mockSingle();
|
||||||
|
|
||||||
|
$this->assert->areEqual(UserAvatarStyle::Gravatar, $user->getAvatarStyle()->toInteger());
|
||||||
|
|
||||||
|
$user = $this->assert->doesNotThrow(function() use ($user)
|
||||||
|
{
|
||||||
|
return Api::run(
|
||||||
|
new EditUserAvatarJob(),
|
||||||
|
[
|
||||||
|
JobArgs::ARG_USER_NAME => $user->getName(),
|
||||||
|
JobArgs::ARG_NEW_AVATAR_STYLE => UserAvatarStyle::None,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assert->areEqual(UserAvatarStyle::None, $user->getAvatarStyle()->toInteger());
|
||||||
|
|
||||||
|
$hash = md5($user->getPasswordSalt() . $user->getName());
|
||||||
|
$this->assert->isTrue(strpos($user->getAvatarUrl(), $hash) === false);
|
||||||
|
|
||||||
|
$mail = 'postmaster@mordor.cx';
|
||||||
|
$user->setConfirmedEmail($mail);
|
||||||
|
UserModel::save($user);
|
||||||
|
|
||||||
|
$hash = md5($mail);
|
||||||
|
$this->assert->isTrue(strpos($user->getAvatarUrl(), $hash) === false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustom()
|
||||||
|
{
|
||||||
|
$this->grantAccess('editUserAvatar');
|
||||||
|
$user = $this->userMocker->mockSingle();
|
||||||
|
|
||||||
|
$this->assert->areEqual(UserAvatarStyle::Gravatar, $user->getAvatarStyle()->toInteger());
|
||||||
|
|
||||||
|
$user = $this->assert->doesNotThrow(function() use ($user)
|
||||||
|
{
|
||||||
|
return Api::run(
|
||||||
|
new EditUserAvatarJob(),
|
||||||
|
[
|
||||||
|
JobArgs::ARG_USER_NAME => $user->getName(),
|
||||||
|
JobArgs::ARG_NEW_AVATAR_STYLE => UserAvatarStyle::Custom,
|
||||||
|
JobArgs::ARG_NEW_AVATAR_CONTENT => new ApiFileInput($this->testSupport->getPath('image.jpg'), 'image.jpg')
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assert->areEqual(UserAvatarStyle::Custom, $user->getAvatarStyle()->toInteger());
|
||||||
|
|
||||||
|
$hash = md5($user->getPasswordSalt() . $user->getName());
|
||||||
|
$this->assert->isTrue(strpos($user->getAvatarUrl(), $hash) === false);
|
||||||
|
$this->assert->isTrue(strpos($user->getAvatarUrl(32), '32') !== false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
[main]
|
[main]
|
||||||
filesPath = "./tests/files/"
|
filesPath = "./tests/files/"
|
||||||
thumbnailsPath = "./tests/thumbs/"
|
thumbnailsPath = "./tests/thumbs/"
|
||||||
|
avatarsPath = "./tests/avatars/"
|
||||||
logsPath = "./tests/logs/{yyyy}-{mm}.log"
|
logsPath = "./tests/logs/{yyyy}-{mm}.log"
|
||||||
mediaPath = "./public_html/media/"
|
mediaPath = "./public_html/media/"
|
||||||
title = "szurubooru/tests"
|
title = "szurubooru/tests"
|
||||||
|
|
Loading…
Reference in a new issue