Added account settings management and avatars
This commit is contained in:
parent
bfee96c59e
commit
ee2ca7fbaf
40 changed files with 1178 additions and 175 deletions
3
data/avatars/.gitignore
vendored
Normal file
3
data/avatars/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
*
|
||||||
|
!blank.png
|
||||||
|
!.gitignore
|
BIN
data/avatars/blank.png
Normal file
BIN
data/avatars/blank.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
|
@ -8,12 +8,25 @@ secret = change
|
||||||
minPasswordLength = 5
|
minPasswordLength = 5
|
||||||
|
|
||||||
[security.privileges]
|
[security.privileges]
|
||||||
register = anonymous
|
register = anonymous
|
||||||
listUsers = regularUser, powerUser, moderator, administrator
|
listUsers = regularUser, powerUser, moderator, administrator
|
||||||
deleteOwnAccount = regularUser, powerUser, moderator, administrator
|
deleteOwnAccount = regularUser, powerUser, moderator, administrator
|
||||||
deleteAllAccounts = administrator
|
deleteAllAccounts = administrator
|
||||||
|
changeOwnName = regularUser, powerUser, moderator, administrator
|
||||||
|
changeOwnAvatarStyle = regularUser, powerUser, moderator, administrator
|
||||||
|
changeOwnEmailAddress = regularUser, powerUser, moderator, administrator
|
||||||
|
changeOwnName = regularUser, powerUser, moderator, administrator
|
||||||
|
changeOwnPassword = regularUser, powerUser, moderator, administrator
|
||||||
|
changeAllAvatarStyles = moderator, administrator
|
||||||
|
changeAllEmailAddresses = moderator, administrator
|
||||||
|
changeAllNames = moderator, administrator
|
||||||
|
changeAllPasswords = moderator, administrator
|
||||||
|
changeAccessRank = administrator
|
||||||
|
|
||||||
[users]
|
[users]
|
||||||
minUserNameLength = 1
|
minUserNameLength = 1
|
||||||
maxUserNameLength = 32
|
maxUserNameLength = 32
|
||||||
usersPerPage = 20
|
usersPerPage = 20
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
thumbnailCropStyle = outside
|
||||||
|
|
|
@ -44,3 +44,15 @@ input[type=button] {
|
||||||
font-family: 'Droid Sans', sans-serif;
|
font-family: 'Droid Sans', sans-serif;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-handler {
|
||||||
|
border: 3px dashed #eee;
|
||||||
|
padding: 0.3em 0.5em;
|
||||||
|
line-height: 140% !important;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.file-handler.active {
|
||||||
|
border-color: #6a2;
|
||||||
|
background-color: #eeffcc;
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<script type="text/javascript" src="/js/Api.js"></script>
|
<script type="text/javascript" src="/js/Api.js"></script>
|
||||||
<script type="text/javascript" src="/js/Auth.js"></script>
|
<script type="text/javascript" src="/js/Auth.js"></script>
|
||||||
<script type="text/javascript" src="/js/Util.js"></script>
|
<script type="text/javascript" src="/js/Util.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/Controls/FileDropper.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/TopNavigationPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/TopNavigationPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/PagedCollectionPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/PagedCollectionPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/LoginPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/LoginPresenter.js"></script>
|
||||||
|
@ -40,6 +41,9 @@
|
||||||
<script type="text/javascript" src="/js/Presenters/MessagePresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/MessagePresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/RegistrationPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/RegistrationPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/UserListPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/UserListPresenter.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/Presenters/UserBrowsingSettingsPresenter.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/Presenters/UserAccountSettingsPresenter.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/Presenters/UserAccountRemovalPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/UserPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/UserPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Router.js"></script>
|
<script type="text/javascript" src="/js/Router.js"></script>
|
||||||
<script type="text/javascript" src="/js/Bootstrap.js"></script>
|
<script type="text/javascript" src="/js/Bootstrap.js"></script>
|
||||||
|
|
|
@ -5,6 +5,16 @@ App.Auth = function(jQuery, util, api, appState, promise) {
|
||||||
var privileges = {
|
var privileges = {
|
||||||
register: 'register',
|
register: 'register',
|
||||||
listUsers: 'listUsers',
|
listUsers: 'listUsers',
|
||||||
|
viewAllEmailAddresses: 'viewAllEmailAddresses',
|
||||||
|
changeAccessRank: 'changeAccessRank',
|
||||||
|
changeOwnAvatarStyle: 'changeOwnAvatarStyle',
|
||||||
|
changeOwnEmailAddress: 'changeOwnEmailAddress',
|
||||||
|
changeOwnName: 'changeOwnName',
|
||||||
|
changeOwnPassword: 'changeOwnPassword',
|
||||||
|
changeAllAvatarStyles: 'changeAllAvatarStyles',
|
||||||
|
changeAllEmailAddresses: 'changeAllEmailAddresses',
|
||||||
|
changeAllNames: 'changeAllNames',
|
||||||
|
changeAllPasswords: 'changeAllPasswords',
|
||||||
deleteOwnAccount: 'deleteOwnAccount',
|
deleteOwnAccount: 'deleteOwnAccount',
|
||||||
deleteAllAccounts: 'deleteAllAccounts',
|
deleteAllAccounts: 'deleteAllAccounts',
|
||||||
};
|
};
|
||||||
|
|
54
public_html/js/Controls/FileDropper.js
Normal file
54
public_html/js/Controls/FileDropper.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
var App = App || {};
|
||||||
|
App.Controls = App.Controls || {};
|
||||||
|
|
||||||
|
App.Controls.FileDropper = function(
|
||||||
|
$fileInput,
|
||||||
|
allowMultiple,
|
||||||
|
onChange,
|
||||||
|
jQuery) {
|
||||||
|
|
||||||
|
var $dropDiv = jQuery('<div class="file-handler"></div>');
|
||||||
|
$dropDiv.html((allowMultiple ? 'Drop files here!' : 'Drop file here!') + '<br/>Or just click on this box.');
|
||||||
|
$dropDiv.insertBefore($fileInput);
|
||||||
|
$fileInput.attr('multiple', allowMultiple);
|
||||||
|
$fileInput.hide();
|
||||||
|
|
||||||
|
$fileInput.change(function(e)
|
||||||
|
{
|
||||||
|
addFiles(this.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
$dropDiv.on('dragenter', function(e)
|
||||||
|
{
|
||||||
|
$dropDiv.addClass('active');
|
||||||
|
}).on('dragleave', function(e)
|
||||||
|
{
|
||||||
|
$dropDiv.removeClass('active');
|
||||||
|
}).on('dragover', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
}).on('drop', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
addFiles(e.originalEvent.dataTransfer.files);
|
||||||
|
}).on('click', function(e)
|
||||||
|
{
|
||||||
|
$fileInput.show().focus().trigger('click').hide();
|
||||||
|
$dropDiv.addClass('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
function getFiles() {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFiles(files) {
|
||||||
|
$dropDiv.removeClass('active');
|
||||||
|
if (!allowMultiple && files.length > 1) {
|
||||||
|
alert('Cannot select multiple files.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.DI.register('fileDropper', App.Controls.FileDropper);
|
|
@ -31,20 +31,20 @@ App.Presenters.RegistrationPresenter = function(
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
messagePresenter.hideMessages($messages);
|
messagePresenter.hideMessages($messages);
|
||||||
|
|
||||||
registrationData = {
|
formData = {
|
||||||
userName: $el.find('[name=user]').val(),
|
userName: $el.find('[name=userName]').val(),
|
||||||
password: $el.find('[name=password1]').val(),
|
password: $el.find('[name=password]').val(),
|
||||||
passwordConfirmation: $el.find('[name=password2]').val(),
|
passwordConfirmation: $el.find('[name=passwordConfirmation]').val(),
|
||||||
email: $el.find('[name=email]').val(),
|
email: $el.find('[name=email]').val(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!validateRegistrationData(registrationData))
|
if (!validateRegistrationFormData(formData))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
api.post('/users', registrationData)
|
api.post('/users', formData)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
registrationSuccess(response);
|
registrationSuccess(response);
|
||||||
}).catch(function(response) {
|
}).fail(function(response) {
|
||||||
registrationFailure(response);
|
registrationFailure(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -62,18 +62,18 @@ App.Presenters.RegistrationPresenter = function(
|
||||||
messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse);
|
messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateRegistrationData(registrationData) {
|
function validateRegistrationFormData(formData) {
|
||||||
if (registrationData.userName.length == 0) {
|
if (formData.userName.length == 0) {
|
||||||
messagePresenter.showError($messages, 'User name cannot be empty.');
|
messagePresenter.showError($messages, 'User name cannot be empty.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registrationData.password.length == 0) {
|
if (formData.password.length == 0) {
|
||||||
messagePresenter.showError($messages, 'Password cannot be empty.');
|
messagePresenter.showError($messages, 'Password cannot be empty.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registrationData.password != registrationData.passwordConfirmation) {
|
if (formData.password != formData.passwordConfirmation) {
|
||||||
messagePresenter.showError($messages, 'Passwords must be the same.');
|
messagePresenter.showError($messages, 'Passwords must be the same.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
78
public_html/js/Presenters/UserAccountRemovalPresenter.js
Normal file
78
public_html/js/Presenters/UserAccountRemovalPresenter.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
var App = App || {};
|
||||||
|
App.Presenters = App.Presenters || {};
|
||||||
|
|
||||||
|
App.Presenters.UserAccountRemovalPresenter = function(
|
||||||
|
jQuery,
|
||||||
|
util,
|
||||||
|
promise,
|
||||||
|
api,
|
||||||
|
auth,
|
||||||
|
router,
|
||||||
|
messagePresenter) {
|
||||||
|
|
||||||
|
var target;
|
||||||
|
var template;
|
||||||
|
var user;
|
||||||
|
var privileges = {};
|
||||||
|
|
||||||
|
function init(args) {
|
||||||
|
return promise.make(function(resolve, reject) {
|
||||||
|
user = args.user;
|
||||||
|
target = args.target;
|
||||||
|
|
||||||
|
privileges.canDeleteAccount =
|
||||||
|
auth.hasPrivilege(auth.privileges.deleteAllAccounts) ||
|
||||||
|
(auth.hasPrivilege(auth.privileges.deleteOwnAccount) && auth.isLoggedIn(user.name));
|
||||||
|
|
||||||
|
promise.wait(util.promiseTemplate('account-removal')).then(function(html) {
|
||||||
|
template = _.template(html);
|
||||||
|
render();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
$el = jQuery(target);
|
||||||
|
$el.html(template({
|
||||||
|
user: user,
|
||||||
|
canDeleteAccount: privileges.canDeleteAccount}));
|
||||||
|
|
||||||
|
$el.find('form').submit(accountRemovalFormSubmitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrivileges() {
|
||||||
|
return privileges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function accountRemovalFormSubmitted(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$messages = jQuery(target).find('.messages');
|
||||||
|
messagePresenter.hideMessages($messages);
|
||||||
|
if (!$el.find('input[name=confirmation]:visible').prop('checked')) {
|
||||||
|
messagePresenter.showError($messages, 'Must confirm to proceed.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
api.delete('/users/' + user.name)
|
||||||
|
.then(function() {
|
||||||
|
auth.logout();
|
||||||
|
var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>');
|
||||||
|
$messageDiv.find('a').click(mainPageLinkClicked);
|
||||||
|
}).fail(function(response) {
|
||||||
|
messagePresenter.showError($messages, response.json && response.json.error || response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mainPageLinkClicked(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
router.navigateToMainPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
render: render,
|
||||||
|
getPrivileges: getPrivileges
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
App.DI.register('userAccountRemovalPresenter', App.Presenters.UserAccountRemovalPresenter);
|
154
public_html/js/Presenters/UserAccountSettingsPresenter.js
Normal file
154
public_html/js/Presenters/UserAccountSettingsPresenter.js
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
var App = App || {};
|
||||||
|
App.Presenters = App.Presenters || {};
|
||||||
|
|
||||||
|
App.Presenters.UserAccountSettingsPresenter = function(
|
||||||
|
jQuery,
|
||||||
|
util,
|
||||||
|
promise,
|
||||||
|
api,
|
||||||
|
auth,
|
||||||
|
messagePresenter) {
|
||||||
|
|
||||||
|
var $messages;
|
||||||
|
var target;
|
||||||
|
var template;
|
||||||
|
var user;
|
||||||
|
var privileges;
|
||||||
|
var avatarContent;
|
||||||
|
|
||||||
|
function init(args) {
|
||||||
|
return promise.make(function(resolve, reject) {
|
||||||
|
user = args.user;
|
||||||
|
target = args.target;
|
||||||
|
|
||||||
|
privileges = {
|
||||||
|
canChangeAccessRank:
|
||||||
|
auth.hasPrivilege(auth.privileges.changeAccessRank),
|
||||||
|
canChangeAvatarStyle:
|
||||||
|
auth.hasPrivilege(auth.privileges.changeAllAvatarStyles) ||
|
||||||
|
(auth.hasPrivilege(auth.privileges.changeOwnAvatarStyle) && auth.isLoggedIn(user.name)),
|
||||||
|
canChangeName:
|
||||||
|
auth.hasPrivilege(auth.privileges.changeAllNames) ||
|
||||||
|
(auth.hasPrivilege(auth.privileges.changeOwnName) && auth.isLoggedIn(user.name)),
|
||||||
|
canChangeEmailAddress:
|
||||||
|
auth.hasPrivilege(auth.privileges.changeAllEmailAddresses) ||
|
||||||
|
(auth.hasPrivilege(auth.privileges.changeOwnEmailAddress) && auth.isLoggedIn(user.name)),
|
||||||
|
canChangePassword:
|
||||||
|
auth.hasPrivilege(auth.privileges.changeAllPasswords) ||
|
||||||
|
(auth.hasPrivilege(auth.privileges.changeOwnPassword) && auth.isLoggedIn(user.name)),
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.wait(util.promiseTemplate('account-settings')).then(function(html) {
|
||||||
|
template = _.template(html);
|
||||||
|
render();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
var $el = jQuery(target);
|
||||||
|
$el.html(template(_.extend({user: user}, privileges)));
|
||||||
|
$el.find('form').submit(accountSettingsFormSubmitted);
|
||||||
|
$el.find('form [name=avatar-style]').change(avatarStyleChanged);
|
||||||
|
avatarStyleChanged();
|
||||||
|
new App.Controls.FileDropper($el.find('[name=avatar-content]'), false, avatarContentChanged, jQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrivileges() {
|
||||||
|
return privileges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function avatarStyleChanged(e) {
|
||||||
|
var $el = jQuery(target);
|
||||||
|
var $target = $el.find('.avatar-content .file-handler');
|
||||||
|
if ($el.find('[name=avatar-style]:checked').val() == 'manual') {
|
||||||
|
$target.show();
|
||||||
|
} else {
|
||||||
|
$target.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function avatarContentChanged(files) {
|
||||||
|
if (files.length == 1) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onloadend = function() {
|
||||||
|
avatarContent = reader.result;
|
||||||
|
var $el = jQuery(target);
|
||||||
|
var $target = $el.find('.avatar-content .file-handler');
|
||||||
|
$target.html(files[0].name);
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function accountSettingsFormSubmitted(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $el = jQuery(target);
|
||||||
|
var $messages = jQuery(target).find('.messages');
|
||||||
|
messagePresenter.hideMessages($messages);
|
||||||
|
var formData = {};
|
||||||
|
|
||||||
|
if (privileges.canChangeAvatarStyle) {
|
||||||
|
formData.avatarStyle = $el.find('[name=avatar-style]:checked').val();
|
||||||
|
if (avatarContent)
|
||||||
|
formData.avatarContent = avatarContent;
|
||||||
|
}
|
||||||
|
if (privileges.canChangeName) {
|
||||||
|
formData.userName = $el.find('[name=userName]').val();
|
||||||
|
}
|
||||||
|
if (privileges.canChangeEmailAddress) {
|
||||||
|
formData.email = $el.find('[name=email]').val();
|
||||||
|
}
|
||||||
|
if (privileges.canChangePassword) {
|
||||||
|
formData.password = $el.find('[name=password]').val();
|
||||||
|
formData.passwordConfirmation = $el.find('[name=passwordConfirmation]').val();
|
||||||
|
}
|
||||||
|
if (privileges.canChangeAccessRank) {
|
||||||
|
formData.accessRank = $el.find('[name=access-rank]').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateAccountSettingsFormData($messages, formData)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.password) {
|
||||||
|
delete formData.password;
|
||||||
|
delete formData.passwordConfirmation;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.put('/users/' + user.name, formData)
|
||||||
|
.then(function(response) {
|
||||||
|
editSuccess($messages, response);
|
||||||
|
}).fail(function(response) {
|
||||||
|
editFailure($messages, response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editSuccess($messages, apiResponse) {
|
||||||
|
//todo: tell user if it turned out that he needs to confirm his e-mail
|
||||||
|
var message = 'Account settings updated!';
|
||||||
|
messagePresenter.showInfo($messages, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function editFailure($messages, apiResponse) {
|
||||||
|
messagePresenter.showError($messages, apiResponse.json && apiResponse.json.error || apiResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAccountSettingsFormData($messages, formData) {
|
||||||
|
if (formData.password != formData.passwordConfirmation) {
|
||||||
|
messagePresenter.showError($messages, 'Passwords must be the same.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
render: render,
|
||||||
|
getPrivileges: getPrivileges,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
App.DI.register('userAccountSettingsPresenter', App.Presenters.UserAccountSettingsPresenter);
|
46
public_html/js/Presenters/UserBrowsingSettingsPresenter.js
Normal file
46
public_html/js/Presenters/UserBrowsingSettingsPresenter.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
var App = App || {};
|
||||||
|
App.Presenters = App.Presenters || {};
|
||||||
|
|
||||||
|
App.Presenters.UserBrowsingSettingsPresenter = function(
|
||||||
|
jQuery,
|
||||||
|
util,
|
||||||
|
promise,
|
||||||
|
auth) {
|
||||||
|
|
||||||
|
var target;
|
||||||
|
var template;
|
||||||
|
var user;
|
||||||
|
var privileges = {};
|
||||||
|
|
||||||
|
function init(args) {
|
||||||
|
return promise.make(function(resolve, reject) {
|
||||||
|
user = args.user;
|
||||||
|
target = args.target;
|
||||||
|
|
||||||
|
privileges.canChangeBrowsingSettings = auth.isLoggedIn(user.name) && user.name == auth.getCurrentUser().name;
|
||||||
|
|
||||||
|
promise.wait(util.promiseTemplate('browsing-settings')).then(function(html) {
|
||||||
|
template = _.template(html);
|
||||||
|
render();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
$el = jQuery(target);
|
||||||
|
$el.html(template({user: user}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrivileges() {
|
||||||
|
return privileges;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
render: render,
|
||||||
|
getPrivileges: getPrivileges,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
App.DI.register('userBrowsingSettingsPresenter', App.Presenters.UserBrowsingSettingsPresenter);
|
|
@ -8,14 +8,14 @@ App.Presenters.UserPresenter = function(
|
||||||
api,
|
api,
|
||||||
auth,
|
auth,
|
||||||
topNavigationPresenter,
|
topNavigationPresenter,
|
||||||
|
userBrowsingSettingsPresenter,
|
||||||
|
userAccountSettingsPresenter,
|
||||||
|
userAccountRemovalPresenter,
|
||||||
messagePresenter) {
|
messagePresenter) {
|
||||||
|
|
||||||
var $el = jQuery('#content');
|
var $el = jQuery('#content');
|
||||||
var $messages = $el;
|
var $messages = $el;
|
||||||
var template;
|
var template;
|
||||||
var accountSettingsTemplate;
|
|
||||||
var accountRemovalTemplate;
|
|
||||||
var browsingSettingsTemplate;
|
|
||||||
var user;
|
var user;
|
||||||
var userName;
|
var userName;
|
||||||
|
|
||||||
|
@ -25,23 +25,22 @@ App.Presenters.UserPresenter = function(
|
||||||
|
|
||||||
promise.waitAll(
|
promise.waitAll(
|
||||||
util.promiseTemplate('user'),
|
util.promiseTemplate('user'),
|
||||||
util.promiseTemplate('account-settings'),
|
|
||||||
util.promiseTemplate('account-removal'),
|
|
||||||
util.promiseTemplate('browsing-settings'),
|
|
||||||
api.get('/users/' + userName))
|
api.get('/users/' + userName))
|
||||||
.then(function(
|
.then(function(
|
||||||
userHtml,
|
userHtml,
|
||||||
accountSettingsHtml,
|
|
||||||
accountRemovalHtml,
|
|
||||||
browsingSettingsHtml,
|
|
||||||
response) {
|
response) {
|
||||||
|
$messages = $el.find('.messages');
|
||||||
template = _.template(userHtml);
|
template = _.template(userHtml);
|
||||||
accountSettingsTemplate = _.template(accountSettingsHtml);
|
|
||||||
accountRemovalTemplate = _.template(accountRemovalHtml);
|
|
||||||
browsingSettingsTemplate = _.template(browsingSettingsHtml);
|
|
||||||
|
|
||||||
user = response.json;
|
user = response.json;
|
||||||
render();
|
var extendedContext = _.extend(args, {user: user});
|
||||||
|
|
||||||
|
promise.waitAll(
|
||||||
|
userBrowsingSettingsPresenter.init(_.extend(extendedContext, {target: '#browsing-settings-target'})),
|
||||||
|
userAccountSettingsPresenter.init(_.extend(extendedContext, {target: '#account-settings-target'})),
|
||||||
|
userAccountRemovalPresenter.init(_.extend(extendedContext, {target: '#account-removal-target'})))
|
||||||
|
.then(render);
|
||||||
|
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
$el.empty();
|
$el.empty();
|
||||||
messagePresenter.showError($messages, response.json && response.json.error || response);
|
messagePresenter.showError($messages, response.json && response.json.error || response);
|
||||||
|
@ -49,37 +48,16 @@ App.Presenters.UserPresenter = function(
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
var context = {
|
$el.html(template({
|
||||||
user: user,
|
user: user,
|
||||||
canDeleteAccount: auth.hasPrivilege(auth.privileges.deleteAllAccounts) ||
|
canChangeBrowsingSettings: userBrowsingSettingsPresenter.getPrivileges().canChangeBrowsingSettings,
|
||||||
(auth.isLoggedIn(userName) && auth.hasPrivilege(auth.privileges.deleteOwnAccount)),
|
canChangeAccountSettings: _.any(userAccountSettingsPresenter.getPrivileges()),
|
||||||
};
|
canDeleteAccount: userAccountRemovalPresenter.getPrivileges().canDeleteAccount}));
|
||||||
$el.html(template(context));
|
userBrowsingSettingsPresenter.render();
|
||||||
$el.find('.browsing-settings').html(browsingSettingsTemplate(context));
|
userAccountSettingsPresenter.render();
|
||||||
$el.find('.account-settings').html(accountSettingsTemplate(context));
|
userAccountRemovalPresenter.render();
|
||||||
$el.find('.account-removal').html(accountRemovalTemplate(context));
|
|
||||||
$el.find('.account-removal form').submit(accountRemovalFormSubmitted);
|
|
||||||
$messages = $el.find('.messages');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function accountRemovalFormSubmitted(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$messages = $el.find('.account-removal .messages');
|
|
||||||
messagePresenter.hideMessages($messages);
|
|
||||||
if (!$el.find('.account-removal input[name=confirmation]:visible').prop('checked')) {
|
|
||||||
messagePresenter.showError($messages, 'Must confirm to proceed.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api.delete('/users/' + user.name)
|
|
||||||
.then(function() {
|
|
||||||
auth.logout();
|
|
||||||
var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>');
|
|
||||||
$messageDiv.find('a').click(mainPageLinkClicked);
|
|
||||||
}).fail(function(response) {
|
|
||||||
messagePresenter.showError($messages, response.json && response.json.error || response);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
render: render
|
render: render
|
||||||
|
|
|
@ -44,7 +44,7 @@ App.Util = (function(jQuery, promise) {
|
||||||
} else {
|
} else {
|
||||||
lastContentPresenter.reinit.call(presenter, args);
|
lastContentPresenter.reinit.call(presenter, args);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function promiseTemplate(templateName) {
|
function promiseTemplate(templateName) {
|
||||||
return promiseTemplateFromCache(templateName)
|
return promiseTemplateFromCache(templateName)
|
||||||
|
@ -83,7 +83,7 @@ App.Util = (function(jQuery, promise) {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
},
|
},
|
||||||
error: function(xhr, textStatus, errorThrown) {
|
error: function(xhr, textStatus, errorThrown) {
|
||||||
console.log(Error('Error while loading template ' + templateName + ': ' + errorThrown));
|
console.log(Error('Error while loading template ' + templateName + ': ' + errorThrown));
|
||||||
reject();
|
reject();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form class="account-settings">
|
<form class="account-removal">
|
||||||
<div class="messages"></div>
|
<div class="messages"></div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label"></label>
|
<label class="form-label"></label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<button class="submit" type="submit">Delete account</button>
|
<button type="submit">Delete account</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,74 +1,94 @@
|
||||||
<form class="account-settings">
|
<form class="account-settings">
|
||||||
<div class="form-row">
|
<div class="messages"></div>
|
||||||
<label class="form-label">User picture:</label>
|
|
||||||
<div class="form-input">
|
|
||||||
<label for="account-settings-avatar-gravatar">
|
|
||||||
<input type="radio" name="avatar-style" id="account-settings-avatar-gravatar" class="avatar-style" value="gravatar"/>
|
|
||||||
Gravatar
|
|
||||||
</label>
|
|
||||||
<label for="account-settings-avatar-manual">
|
|
||||||
<input type="radio" name="avatar-style" id="account-settings-avatar-manual" class="avatar-style" value="manual"/>
|
|
||||||
Custom
|
|
||||||
</label>
|
|
||||||
<label for="account-settings-avatar-none">
|
|
||||||
<input type="radio" name="avatar-style" id="account-settings-avatar-none" class="avatar-style" value="none"/>
|
|
||||||
None
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
<% if (canChangeAvatarStyle) { %>
|
||||||
<label class="form-label" for="account-settings-avatar-content"></label>
|
<div class="form-row">
|
||||||
<div class="form-input">
|
<label class="form-label">User picture:</label>
|
||||||
<input class="avatar-content" type="file" name="avatar-content" id="account-settings-avatar-content"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label class="form-label" for="account-settings-name">Name:</label>
|
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input type="text" name="name" id="account-settings-name" placeholder="New name…" value=""/>
|
<%
|
||||||
|
var avatarStyles = {
|
||||||
|
gravatar: 'Gravatar',
|
||||||
|
manual: 'Custom',
|
||||||
|
blank: 'Blank',
|
||||||
|
};
|
||||||
|
%>
|
||||||
|
<% _.each(avatarStyles, function(v, k) { %>
|
||||||
|
<label for="account-settings-avatar-<%= k %>">
|
||||||
|
<input <% print(user.avatarStyle == k ? 'checked="checked"' : '') %> type="radio" name="avatar-style" id="account-settings-avatar-<%= k %>" value="<%= k %>"/>
|
||||||
|
<%= v %>
|
||||||
|
</label>
|
||||||
|
<% }) %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row avatar-content">
|
||||||
<label class="form-label" for="account-settings-email">E-mail:</label>
|
<label class="form-label"></label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input type="text" name="email" id="account-settings-email" placeholder="New e-mail…" value=""/>
|
<input type="file" name="avatar-content" id="account-settings-avatar-content"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% } %>
|
||||||
|
|
||||||
<div class="form-row">
|
<% if (canChangeName) { %>
|
||||||
<label class="form-label" for="account-settings-password1">New password:</label>
|
<div class="form-row">
|
||||||
<div class="form-input">
|
<label class="form-label" for="account-settings-name">Name:</label>
|
||||||
<input type="password" name="password1" id="account-settings-password1" placeholder="New password…" value=""/>
|
<div class="form-input">
|
||||||
|
<input type="text" name="userName" id="account-settings-name" placeholder="New name…" value="<%= user.name %>"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% } %>
|
||||||
|
|
||||||
<div class="form-row">
|
<% if (canChangeEmailAddress) { %>
|
||||||
<label class="form-label" for="account-settings-password2"></label>
|
<div class="form-row">
|
||||||
<div class="form-input">
|
<label class="form-label" for="account-settings-email">E-mail:</label>
|
||||||
<input type="password" name="password2" id="account-settings-password2" placeholder="New password… (repeat)" value=""/>
|
<div class="form-input">
|
||||||
|
<input type="text" name="email" id="account-settings-email" placeholder="New e-mail…" value="<%= user.email %>"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% } %>
|
||||||
|
|
||||||
<div class="form-row">
|
<% if (canChangePassword) { %>
|
||||||
<label class="form-label" for="account-settings-access-rank">Access rank:</label>
|
<div class="form-row">
|
||||||
<div class="form-input">
|
<label class="form-label" for="account-settings-password">New password:</label>
|
||||||
<select name="access-rank" id="account-settings-access-rank">
|
<div class="form-input">
|
||||||
<option value="anonymous">anonymous</option>
|
<input type="password" name="password" id="account-settings-password" placeholder="New password…" value=""/>
|
||||||
<option value="regular-user">registered</option>
|
</div>
|
||||||
<option value="power-user">power user</option>
|
|
||||||
<option value="moderator">moderator</option>
|
|
||||||
<option value="administrator" selected="selected">admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="form-label" for="account-settings-password-confirmation"></label>
|
||||||
|
<div class="form-input">
|
||||||
|
<input type="password" name="passwordConfirmation" id="account-settings-password-confirmation" placeholder="New password… (repeat)" value=""/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<% if (canChangeAccessRank) { %>
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="form-label" for="account-settings-access-rank">Access rank:</label>
|
||||||
|
<div class="form-input">
|
||||||
|
<select name="access-rank" id="account-settings-access-rank">
|
||||||
|
<%
|
||||||
|
var accessRanks = {
|
||||||
|
anonymous: 'Anonymous',
|
||||||
|
regularUser: 'Regular user',
|
||||||
|
powerUser: 'Power user',
|
||||||
|
moderator: 'Moderator',
|
||||||
|
administrator: 'Administrator'
|
||||||
|
};
|
||||||
|
%>
|
||||||
|
<% _.each(accessRanks, function(v, k) { %>
|
||||||
|
<option <% print(user.accessRank == k ? 'selected="selected"' : '') %> value="<%= k %>"><%= v %></option>
|
||||||
|
<% }) %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label"></label>
|
<label class="form-label"></label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<button class="submit" type="submit">Update settings</button>
|
<button type="submit">Update settings</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<form class="browsing-settings">
|
<form class="browsing-settings">
|
||||||
|
<div class="messages"></div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label">Safety:</label>
|
<label class="form-label">Safety:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
|
@ -47,7 +49,7 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label"></label>
|
<label class="form-label"></label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<button class="submit" type="submit">Update settings</button>
|
<button type="submit">Update settings</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
|
|
||||||
<form method="post" class="form-wrapper">
|
<form method="post" class="form-wrapper">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="login-user" class="form-label">User name:</label>
|
<label class="form-label" for="login-user">User name:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input autocomplete="off" type="text" name="user" id="login-user"/>
|
<input autocomplete="off" type="text" name="user" id="login-user"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="login-password" class="form-label">Password:</label>
|
<label class="form-label" for="login-password">Password:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input autocomplete="off" type="password" name="password" id="login-password"/>
|
<input autocomplete="off" type="password" name="password" id="login-password"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,12 +24,11 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label"></label>
|
<label class="form-label"></label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<button class="submit" type="submit">Log in</button>
|
<button type="submit">Log in</button>
|
||||||
|
|
||||||
<input type="hidden" name="remember" value="0"/>
|
<input type="hidden" name="remember" value="0"/>
|
||||||
<label class="checkbox-wrapper">
|
<label>
|
||||||
<input type="checkbox" name="remember" value="1"/>
|
<input type="checkbox" name="remember" value="1"/>
|
||||||
<span></span>
|
|
||||||
Remember me
|
Remember me
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,28 +8,28 @@
|
||||||
|
|
||||||
<form method="post" class="form-wrapper">
|
<form method="post" class="form-wrapper">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="registration-user" class="form-label">User name:</label>
|
<label class="form-label" for="registration-user">User name:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input autocomplete="off" type="text" name="user" id="registration-user" placeholder="e.g. darth_vader" value=""/>
|
<input autocomplete="off" type="text" name="userName" id="registration-user" placeholder="e.g. darth_vader" value=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="registration-password" class="form-label">Password:</label>
|
<label class="form-label" for="registration-password">Password:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input autocomplete="off" type="password" name="password1" id="registration-password" placeholder="e.g. ●●●●●●●●" value=""/>
|
<input autocomplete="off" type="password" name="password" id="registration-password" placeholder="e.g. ●●●●●●●●" value=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="registration-password-confirm" class="form-label">Password (repeat):</label>
|
<label class="form-label" for="registration-password-confirmation">Password (repeat):</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input autocomplete="off" type="password" name="password2" id="registration-password-confirm" placeholder="e.g. ●●●●●●●●" value=""/>
|
<input autocomplete="off" type="password" name="passwordConfirmation" id="registration-password-confirmation" placeholder="e.g. ●●●●●●●●" value=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="registration-email" class="form-label">E-mail address:</label>
|
<label class="form-label" for="registration-email">E-mail address:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input autocomplete="off" type="text" name="email" id="registration-email" placeholder="e.g. vader@empire.gov" value=""/>
|
<input autocomplete="off" type="text" name="email" id="registration-email" placeholder="e.g. vader@empire.gov" value=""/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label"></label>
|
<label class="form-label"></label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<button class="submit" type="submit">Register</button>
|
<button type="submit">Register</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
<div id="user-view">
|
<div id="user-view">
|
||||||
<div class="messages"></div>
|
<div class="messages"></div>
|
||||||
|
|
||||||
|
<img src="/api/users/<%= user.name %>/avatar/50" alt="Avatar"/>
|
||||||
<%= user.name %>
|
<%= user.name %>
|
||||||
|
|
||||||
<h2>Browsing settings</h2>
|
<% if (canChangeBrowsingSettings) { %>
|
||||||
<div class="browsing-settings"></div>
|
<h2>Browsing settings</h2>
|
||||||
|
<div id="browsing-settings-target"></div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<h2>Account settings</h2>
|
<% if (canChangeAccountSettings) { %>
|
||||||
<div class="account-settings"></div>
|
<h2>Account settings</h2>
|
||||||
|
<div id="account-settings-target"></div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<% if (canDeleteAccount) { %>
|
<% if (canDeleteAccount) { %>
|
||||||
<h2>Account removal</h2>
|
<h2>Account removal</h2>
|
||||||
<div class="account-removal"></div>
|
<div id="account-removal-target"></div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
69
src/Controllers/UserAvatarController.php
Normal file
69
src/Controllers/UserAvatarController.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Controllers;
|
||||||
|
|
||||||
|
final class UserAvatarController extends AbstractController
|
||||||
|
{
|
||||||
|
private $userService;
|
||||||
|
private $fileService;
|
||||||
|
private $httpHelper;
|
||||||
|
private $thumbnailService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\Szurubooru\Services\UserService $userService,
|
||||||
|
\Szurubooru\Services\FileService $fileService,
|
||||||
|
\Szurubooru\Helpers\HttpHelper $httpHelper,
|
||||||
|
\Szurubooru\Services\ThumbnailService $thumbnailService)
|
||||||
|
{
|
||||||
|
$this->userService = $userService;
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->httpHelper = $httpHelper;
|
||||||
|
$this->thumbnailService = $thumbnailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerRoutes(\Szurubooru\Router $router)
|
||||||
|
{
|
||||||
|
$router->get('/api/users/:userName/avatar/:size', [$this, 'getAvatarByName']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvatarByName($userName, $size)
|
||||||
|
{
|
||||||
|
$user = $this->userService->getByName($userName);
|
||||||
|
if (!$user)
|
||||||
|
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
|
||||||
|
|
||||||
|
switch ($user->avatarStyle)
|
||||||
|
{
|
||||||
|
case \Szurubooru\Entities\User::AVATAR_STYLE_GRAVATAR:
|
||||||
|
$hash = md5(strtolower(trim($user->email ? $user->email : $user->id . $user->name)));
|
||||||
|
$url = 'https://www.gravatar.com/avatar/' . $hash . '?d=retro&s=' . $size;
|
||||||
|
$this->serveFromUrl($url);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case \Szurubooru\Entities\User::AVATAR_STYLE_BLANK:
|
||||||
|
$this->serveFromFile($this->userService->getBlankAvatarSourcePath(), $size);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case \Szurubooru\Entities\User::AVATAR_STYLE_MANUAL:
|
||||||
|
$this->serveFromFile($this->userService->getCustomAvatarSourcePath($user), $size);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->serveFromFile($this->userService->getBlankAvatarSourcePath(), $size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serveFromUrl($url)
|
||||||
|
{
|
||||||
|
$this->httpHelper->nonCachedRedirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serveFromFile($file, $size)
|
||||||
|
{
|
||||||
|
if (!$this->fileService->exists($file))
|
||||||
|
$file = $this->userService->getBlankAvatarSourcePath();
|
||||||
|
|
||||||
|
$sizedFile = $this->thumbnailService->generateFromFile($file, $size, $size);
|
||||||
|
$this->fileService->serve($sizedFile);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,18 +22,18 @@ final class UserController extends AbstractController
|
||||||
|
|
||||||
public function registerRoutes(\Szurubooru\Router $router)
|
public function registerRoutes(\Szurubooru\Router $router)
|
||||||
{
|
{
|
||||||
$router->post('/api/users', [$this, 'register']);
|
$router->post('/api/users', [$this, 'createUser']);
|
||||||
$router->get('/api/users', [$this, 'getFiltered']);
|
$router->get('/api/users', [$this, 'getFiltered']);
|
||||||
$router->get('/api/users/:name', [$this, 'getByName']);
|
$router->get('/api/users/:userName', [$this, 'getByName']);
|
||||||
$router->put('/api/users/:name', [$this, 'update']);
|
$router->put('/api/users/:userName', [$this, 'updateUser']);
|
||||||
$router->delete('/api/users/:name', [$this, 'delete']);
|
$router->delete('/api/users/:userName', [$this, 'deleteUser']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByName($name)
|
public function getByName($userName)
|
||||||
{
|
{
|
||||||
$user = $this->userService->getByName($name);
|
$user = $this->userService->getByName($userName);
|
||||||
if (!$user)
|
if (!$user)
|
||||||
throw new \DomainException('User with name "' . $name . '" was not found.');
|
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
|
||||||
return $this->userViewProxy->fromEntity($user);
|
return $this->userViewProxy->fromEntity($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ final class UserController extends AbstractController
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::LIST_USERS);
|
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::LIST_USERS);
|
||||||
|
|
||||||
$searchFormData = new \Szurubooru\FormData\SearchFormData($this->inputReader);
|
$formData = new \Szurubooru\FormData\SearchFormData($this->inputReader);
|
||||||
$searchResult = $this->userService->getFiltered($searchFormData);
|
$searchResult = $this->userService->getFiltered($formData);
|
||||||
$entities = $this->userViewProxy->fromArray($searchResult->entities);
|
$entities = $this->userViewProxy->fromArray($searchResult->entities);
|
||||||
return [
|
return [
|
||||||
'data' => $entities,
|
'data' => $entities,
|
||||||
|
@ -50,27 +50,66 @@ final class UserController extends AbstractController
|
||||||
'totalRecords' => $searchResult->totalRecords];
|
'totalRecords' => $searchResult->totalRecords];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function register()
|
public function createUser()
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::REGISTER);
|
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::REGISTER);
|
||||||
|
$formData = new \Szurubooru\FormData\RegistrationFormData($this->inputReader);
|
||||||
$input = new \Szurubooru\FormData\RegistrationFormData($this->inputReader);
|
$user = $this->userService->createUser($formData);
|
||||||
$user = $this->userService->register($input);
|
|
||||||
return $this->userViewProxy->fromEntity($user);
|
return $this->userViewProxy->fromEntity($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update($name)
|
public function updateUser($userName)
|
||||||
{
|
{
|
||||||
throw new \BadMethodCallException('Not implemented');
|
$formData = new \Szurubooru\FormData\UserEditFormData($this->inputReader);
|
||||||
|
|
||||||
|
if ($formData->avatarStyle !== null)
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertPrivilege(
|
||||||
|
$this->privilegeService->isLoggedIn($userName)
|
||||||
|
? \Szurubooru\Privilege::CHANGE_OWN_AVATAR_STYLE
|
||||||
|
: \Szurubooru\Privilege::CHANGE_ALL_AVATAR_STYLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->userName !== null)
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertPrivilege(
|
||||||
|
$this->privilegeService->isLoggedIn($userName)
|
||||||
|
? \Szurubooru\Privilege::CHANGE_OWN_NAME
|
||||||
|
: \Szurubooru\Privilege::CHANGE_ALL_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->password !== null)
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertPrivilege(
|
||||||
|
$this->privilegeService->isLoggedIn($userName)
|
||||||
|
? \Szurubooru\Privilege::CHANGE_OWN_PASSWORD
|
||||||
|
: \Szurubooru\Privilege::CHANGE_ALL_PASSWORDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->email !== null)
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertPrivilege(
|
||||||
|
$this->privilegeService->isLoggedIn($userName)
|
||||||
|
? \Szurubooru\Privilege::CHANGE_OWN_EMAIL_ADDRESS
|
||||||
|
: \Szurubooru\Privilege::CHANGE_ALL_EMAIL_ADDRESSES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->accessRank)
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::CHANGE_ACCESS_RANK);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userService->updateUser($userName, $formData);
|
||||||
|
return $this->userViewProxy->fromEntity($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete($name)
|
public function deleteUser($userName)
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(
|
$this->privilegeService->assertPrivilege(
|
||||||
$this->privilegeService->isLoggedIn($name)
|
$this->privilegeService->isLoggedIn($userName)
|
||||||
? \Szurubooru\Privilege::DELETE_OWN_ACCOUNT
|
? \Szurubooru\Privilege::DELETE_OWN_ACCOUNT
|
||||||
: \Szurubooru\Privilege::DELETE_ACCOUNTS);
|
: \Szurubooru\Privilege::DELETE_ACCOUNTS);
|
||||||
|
|
||||||
return $this->userService->deleteByName($name);
|
return $this->userService->deleteUserByName($userName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,9 @@ class UserViewProxy extends AbstractViewProxy
|
||||||
$result->accessRank = \Szurubooru\Helpers\EnumHelper::accessRankToString($user->accessRank);
|
$result->accessRank = \Szurubooru\Helpers\EnumHelper::accessRankToString($user->accessRank);
|
||||||
$result->registrationTime = $user->registrationTime;
|
$result->registrationTime = $user->registrationTime;
|
||||||
$result->lastLoginTime = $user->lastLoginTime;
|
$result->lastLoginTime = $user->lastLoginTime;
|
||||||
|
$result->avatarStyle = $user->avatarStyle;
|
||||||
|
|
||||||
if ($this->privilegeService->hasPrivilege(\Szurubooru\Privilege::PRIVILEGE_VIEW_ALL_EMAIL_ADDRESSES) or
|
if ($this->privilegeService->hasPrivilege(\Szurubooru\Privilege::VIEW_ALL_EMAIL_ADDRESSES) or
|
||||||
$this->privilegeService->isLoggedIn($user))
|
$this->privilegeService->isLoggedIn($user))
|
||||||
{
|
{
|
||||||
$result->email = $user->email;
|
$result->email = $user->email;
|
||||||
|
|
|
@ -10,10 +10,15 @@ final class User extends Entity
|
||||||
const ACCESS_RANK_MODERATOR = 4;
|
const ACCESS_RANK_MODERATOR = 4;
|
||||||
const ACCESS_RANK_ADMINISTRATOR = 5;
|
const ACCESS_RANK_ADMINISTRATOR = 5;
|
||||||
|
|
||||||
|
const AVATAR_STYLE_GRAVATAR = 'gravatar';
|
||||||
|
const AVATAR_STYLE_MANUAL = 'manual';
|
||||||
|
const AVATAR_STYLE_BLANK = 'blank';
|
||||||
|
|
||||||
public $name;
|
public $name;
|
||||||
public $email;
|
public $email;
|
||||||
public $passwordHash;
|
public $passwordHash;
|
||||||
public $accessRank;
|
public $accessRank;
|
||||||
public $registrationTime;
|
public $registrationTime;
|
||||||
public $lastLoginTime;
|
public $lastLoginTime;
|
||||||
|
public $avatarStyle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace Szurubooru\FormData;
|
||||||
|
|
||||||
class RegistrationFormData
|
class RegistrationFormData
|
||||||
{
|
{
|
||||||
public $name;
|
public $userName;
|
||||||
public $password;
|
public $password;
|
||||||
public $email;
|
public $email;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ class RegistrationFormData
|
||||||
{
|
{
|
||||||
if ($inputReader !== null)
|
if ($inputReader !== null)
|
||||||
{
|
{
|
||||||
$this->name = $inputReader->userName;
|
$this->userName = $inputReader->userName;
|
||||||
$this->password = $inputReader->password;
|
$this->password = $inputReader->password;
|
||||||
$this->email = $inputReader->email;
|
$this->email = $inputReader->email;
|
||||||
}
|
}
|
||||||
|
|
24
src/FormData/UserEditFormData.php
Normal file
24
src/FormData/UserEditFormData.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\FormData;
|
||||||
|
|
||||||
|
class UserEditFormData
|
||||||
|
{
|
||||||
|
public $userName;
|
||||||
|
public $email;
|
||||||
|
public $accessRank;
|
||||||
|
public $password;
|
||||||
|
public $avatarStyle;
|
||||||
|
|
||||||
|
public function __construct($inputReader = null)
|
||||||
|
{
|
||||||
|
if ($inputReader !== null)
|
||||||
|
{
|
||||||
|
$this->userName = $inputReader->userName;
|
||||||
|
$this->email = $inputReader->email;
|
||||||
|
$this->password = $inputReader->password;
|
||||||
|
$this->accessRank = $inputReader->accessRank;
|
||||||
|
$this->avatarStyle = $inputReader->avatarStyle;
|
||||||
|
$this->avatarContent = $inputReader->avatarContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,13 +7,38 @@ class EnumHelper
|
||||||
{
|
{
|
||||||
switch ($accessRank)
|
switch ($accessRank)
|
||||||
{
|
{
|
||||||
case \Szurubooru\Entities\User::ACCESS_RANK_ANONYMOUS: return 'anonymous'; break;
|
case \Szurubooru\Entities\User::ACCESS_RANK_ANONYMOUS: return 'anonymous';
|
||||||
case \Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER: return 'regularUser'; break;
|
case \Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER: return 'regularUser';
|
||||||
case \Szurubooru\Entities\User::ACCESS_RANK_POWER_USER: return 'powerUser'; break;
|
case \Szurubooru\Entities\User::ACCESS_RANK_POWER_USER: return 'powerUser';
|
||||||
case \Szurubooru\Entities\User::ACCESS_RANK_MODERATOR: return 'moderator'; break;
|
case \Szurubooru\Entities\User::ACCESS_RANK_MODERATOR: return 'moderator';
|
||||||
case \Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR: return 'administrator'; break;
|
case \Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR: return 'administrator';
|
||||||
default:
|
default:
|
||||||
throw new \DomainException('Invalid access rank!');
|
throw new \DomainException('Invalid access rank!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function accessRankFromString($accessRankString)
|
||||||
|
{
|
||||||
|
switch (trim(strtolower($accessRankString)))
|
||||||
|
{
|
||||||
|
case 'anonymous': return \Szurubooru\Entities\User::ACCESS_RANK_ANONYMOUS;
|
||||||
|
case 'regularuser': return \Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER;
|
||||||
|
case 'poweruser': return \Szurubooru\Entities\User::ACCESS_RANK_POWER_USER;
|
||||||
|
case 'moderator': return \Szurubooru\Entities\User::ACCESS_RANK_MODERATOR;
|
||||||
|
case 'administrator': return \Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR;
|
||||||
|
default:
|
||||||
|
throw new \DomainException('Unrecognized access rank: ' . $accessRankString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function avatarStyleFromString($avatarStyleString)
|
||||||
|
{
|
||||||
|
switch (trim(strtolower($avatarStyleString)))
|
||||||
|
{
|
||||||
|
case 'gravatar': return \Szurubooru\Entities\User::AVATAR_STYLE_GRAVATAR;
|
||||||
|
case 'manual': return \Szurubooru\Entities\User::AVATAR_STYLE_MANUAL;
|
||||||
|
case 'none':
|
||||||
|
case 'blank': return \Szurubooru\Entities\User::AVATAR_STYLE_BLANK;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,4 +45,10 @@ class HttpHelper
|
||||||
$requestUri = preg_replace('/\?.*$/', '', $requestUri);
|
$requestUri = preg_replace('/\?.*$/', '', $requestUri);
|
||||||
return $requestUri;
|
return $requestUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function nonCachedRedirect($destination)
|
||||||
|
{
|
||||||
|
$this->setResponseCode(303);
|
||||||
|
$this->setHeader('Location', $destination);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,16 @@ class Privilege
|
||||||
{
|
{
|
||||||
const REGISTER = 'register';
|
const REGISTER = 'register';
|
||||||
const LIST_USERS = 'listUsers';
|
const LIST_USERS = 'listUsers';
|
||||||
|
const VIEW_ALL_EMAIL_ADDRESSES = 'viewAllEmailAddresses';
|
||||||
|
const CHANGE_ACCESS_RANK = 'changeAccessRank';
|
||||||
|
const CHANGE_OWN_AVATAR_STYLE = 'changeOwnAvatarStyle';
|
||||||
|
const CHANGE_OWN_EMAIL_ADDRESS = 'changeOwnEmailAddress';
|
||||||
|
const CHANGE_OWN_NAME = 'changeOwnName';
|
||||||
|
const CHANGE_OWN_PASSWORD = 'changeOwnPassword';
|
||||||
|
const CHANGE_ALL_AVATAR_STYLES = 'changeAllAvatarStyles';
|
||||||
|
const CHANGE_ALL_EMAIL_ADDRESSES = 'changeAllEmailAddresses';
|
||||||
|
const CHANGE_ALL_NAMES = 'changeAllNames';
|
||||||
|
const CHANGE_ALL_PASSWORDS = 'changeAllPasswords';
|
||||||
const DELETE_OWN_ACCOUNT = 'deleteOwnAccount';
|
const DELETE_OWN_ACCOUNT = 'deleteOwnAccount';
|
||||||
const DELETE_ALL_ACCOUNTS = 'deleteAllAccounts';
|
const DELETE_ALL_ACCOUNTS = 'deleteAllAccounts';
|
||||||
}
|
}
|
||||||
|
|
93
src/Services/FileService.php
Normal file
93
src/Services/FileService.php
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Services;
|
||||||
|
|
||||||
|
class FileService
|
||||||
|
{
|
||||||
|
private $dataDirectory;
|
||||||
|
private $httpHelper;
|
||||||
|
|
||||||
|
public function __construct($dataDirectory, \Szurubooru\Helpers\HttpHelper $httpHelper)
|
||||||
|
{
|
||||||
|
$this->dataDirectory = $dataDirectory;
|
||||||
|
$this->httpHelper = $httpHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function serve($source, $options = [])
|
||||||
|
{
|
||||||
|
$finalSource = $this->getFullPath($source);
|
||||||
|
|
||||||
|
$daysToLive = isset($options->daysToLive)
|
||||||
|
? $options->daysToLive
|
||||||
|
: 7;
|
||||||
|
$secondsToLive = $daysToLive * 24 * 60 * 60;
|
||||||
|
$lastModified = filemtime($finalSource);
|
||||||
|
$eTag = md5(file_get_contents($finalSource)); //todo: faster
|
||||||
|
|
||||||
|
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
|
||||||
|
? $_SERVER['HTTP_IF_MODIFIED_SINCE']
|
||||||
|
: false;
|
||||||
|
|
||||||
|
$eTagHeader = isset($_SERVER['HTTP_IF_NONE_MATCH'])
|
||||||
|
? trim($_SERVER['HTTP_IF_NONE_MATCH'], "\" \t\r\n")
|
||||||
|
: false;
|
||||||
|
|
||||||
|
$this->httpHelper->setHeader('ETag', '"' . $eTag . '"');
|
||||||
|
$this->httpHelper->setHeader('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $lastModified));
|
||||||
|
$this->httpHelper->setHeader('Pragma', 'public');
|
||||||
|
$this->httpHelper->setHeader('Cache-Control', 'public, max-age=' . $secondsToLive);
|
||||||
|
$this->httpHelper->setHeader('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + $secondsToLive));
|
||||||
|
|
||||||
|
if (isset($options->customFileName))
|
||||||
|
{
|
||||||
|
$this->httpHelper->setHeader('Content-Disposition', 'inline; filename="' . $options->customFileName . '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($options->mimeType))
|
||||||
|
{
|
||||||
|
$this->httpHelper->setHeader('Content-Type', $options->mimeType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->httpHelper->setHeader('Content-Type', mime_content_type($finalSource));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strtotime($ifModifiedSince) == $lastModified or $eTagHeader == $eTag)
|
||||||
|
{
|
||||||
|
$this->httpHelper->setResponseCode(304);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->httpHelper->setResponseCode(200);
|
||||||
|
readfile($finalSource);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exists($source)
|
||||||
|
{
|
||||||
|
$finalSource = $this->getFullPath($source);
|
||||||
|
return $source and file_exists($finalSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($source)
|
||||||
|
{
|
||||||
|
$finalSource = $this->getFullPath($source);
|
||||||
|
if (file_exists($finalSource))
|
||||||
|
unlink($finalSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveFromBase64($base64string, $destination)
|
||||||
|
{
|
||||||
|
$finalDestination = $this->getFullPath($destination);
|
||||||
|
$commaPosition = strpos($base64string, ',');
|
||||||
|
if ($commaPosition !== null)
|
||||||
|
$base64string = substr($base64string, $commaPosition + 1);
|
||||||
|
$data = base64_decode($base64string);
|
||||||
|
file_put_contents($finalDestination, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFullPath($destination)
|
||||||
|
{
|
||||||
|
return $this->dataDirectory . DIRECTORY_SEPARATOR . $destination;
|
||||||
|
}
|
||||||
|
}
|
7
src/Services/ThumbnailGenerators/IThumbnailGenerator.php
Normal file
7
src/Services/ThumbnailGenerators/IThumbnailGenerator.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Services\ThumbnailGenerators;
|
||||||
|
|
||||||
|
interface IThumbnailGenerator
|
||||||
|
{
|
||||||
|
public function generateFromFile($srcPath, $dstPath, $width, $height);
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Services\ThumbnailGenerators;
|
||||||
|
|
||||||
|
class ImageGdThumbnailGenerator implements IThumbnailGenerator
|
||||||
|
{
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
public function __construct(\Szurubooru\Config $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateFromFile($srcPath, $dstPath, $width, $height)
|
||||||
|
{
|
||||||
|
if (!file_exists($srcPath))
|
||||||
|
throw new \InvalidArgumentException($srcPath . ' does not exist');
|
||||||
|
|
||||||
|
$mime = mime_content_type($srcPath);
|
||||||
|
|
||||||
|
switch ($mime)
|
||||||
|
{
|
||||||
|
case 'image/jpeg':
|
||||||
|
$srcImage = imagecreatefromjpeg($srcPath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'image/png':
|
||||||
|
$srcImage = imagecreatefrompng($srcPath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'image/gif':
|
||||||
|
$srcImage = imagecreatefromgif($srcPath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Invalid thumbnail image type');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->config->misc->thumbnailCropStyle)
|
||||||
|
{
|
||||||
|
case 'outside':
|
||||||
|
$dstImage = $this->cropOutside($srcImage, $width, $height);
|
||||||
|
break;
|
||||||
|
case 'inside':
|
||||||
|
$dstImage = $this->cropInside($srcImage, $width, $height);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown thumbnail crop style');
|
||||||
|
}
|
||||||
|
|
||||||
|
imagejpeg($dstImage, $dstPath);
|
||||||
|
imagedestroy($srcImage);
|
||||||
|
imagedestroy($dstImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cropOutside($srcImage, $dstWidth, $dstHeight)
|
||||||
|
{
|
||||||
|
$srcWidth = imagesx($srcImage);
|
||||||
|
$srcHeight = imagesy($srcImage);
|
||||||
|
|
||||||
|
if (($dstHeight / $dstWidth) > ($srcHeight / $srcWidth))
|
||||||
|
{
|
||||||
|
$h = $srcHeight;
|
||||||
|
$w = $h * $dstWidth / $dstHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$w = $srcWidth;
|
||||||
|
$h = $w * $dstHeight / $dstWidth;
|
||||||
|
}
|
||||||
|
$x = ($srcWidth - $w) / 2;
|
||||||
|
$y = ($srcHeight - $h) / 2;
|
||||||
|
|
||||||
|
$dstImage = imagecreatetruecolor($dstWidth, $dstHeight);
|
||||||
|
imagecopyresampled($dstImage, $srcImage, 0, 0, $x, $y, $dstWidth, $dstHeight, $w, $h);
|
||||||
|
return $dstImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cropInside($srcImage, $dstWidth, $dstHeight)
|
||||||
|
{
|
||||||
|
$srcWidth = imagesx($srcImage);
|
||||||
|
$srcHeight = imagesy($srcImage);
|
||||||
|
|
||||||
|
if (($dstHeight / $dstWidth) < ($srcHeight / $srcWidth))
|
||||||
|
{
|
||||||
|
$h = $dstHeight;
|
||||||
|
$w = $h * $srcWidth / $srcHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$w = $dstWidth;
|
||||||
|
$h = $w * $srcHeight / $srcWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dstImage = imagecreatetruecolor($w, $h);
|
||||||
|
imagecopyresampled($dstImage, $srcImage, 0, 0, 0, 0, $w, $h, $srcWidth, $srcHeight);
|
||||||
|
return $dstImage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Services\ThumbnailGenerators;
|
||||||
|
|
||||||
|
class ImageImagickThumbnailGenerator implements IThumbnailGenerator
|
||||||
|
{
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
public function __construct(\Szurubooru\Config $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateFromFile($srcPath, $dstPath, $width, $height)
|
||||||
|
{
|
||||||
|
if (!file_exists($srcPath))
|
||||||
|
throw new \InvalidArgumentException($srcPath . ' does not exist');
|
||||||
|
|
||||||
|
$image = new \Imagick($srcPath);
|
||||||
|
$image = $image->coalesceImages();
|
||||||
|
|
||||||
|
switch ($this->config->misc->thumbnailCropStyle)
|
||||||
|
{
|
||||||
|
case 'outside':
|
||||||
|
$this->cropOutside($image, $width, $height);
|
||||||
|
break;
|
||||||
|
case 'inside':
|
||||||
|
$this->cropInside($image, $width, $height);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown thumbnail crop style');
|
||||||
|
}
|
||||||
|
|
||||||
|
$image->writeImage($dstPath);
|
||||||
|
$image->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cropOutside($srcImage, $dstWidth, $dstHeight)
|
||||||
|
{
|
||||||
|
$srcWidth = $srcImage->getImageWidth();
|
||||||
|
$srcHeight = $srcImage->getImageHeight();
|
||||||
|
|
||||||
|
if (($dstHeight / $dstWidth) > ($srcHeight / $srcWidth))
|
||||||
|
{
|
||||||
|
$h = $dstHeight;
|
||||||
|
$w = $h * $srcWidth / $srcHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$w = $dstWidth;
|
||||||
|
$h = $w * $srcHeight / $srcWidth;
|
||||||
|
}
|
||||||
|
$x = ($srcWidth - $w) / 2;
|
||||||
|
$y = ($srcHeight - $h) / 2;
|
||||||
|
|
||||||
|
$srcImage->resizeImage($w, $h, \imagick::FILTER_LANCZOS, 0.9);
|
||||||
|
$srcImage->cropImage($dstWidth, $dstHeight, ($w - $dstWidth) >> 1, ($h - $dstHeight) >> 1);
|
||||||
|
$srcImage->setImagePage(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cropInside($srcImage, $dstWidth, $dstHeight)
|
||||||
|
{
|
||||||
|
$srcWidth = $srcImage->getImageWidth();
|
||||||
|
$srcHeight = $srcImage->getImageHeight();
|
||||||
|
|
||||||
|
if (($dstHeight / $dstWidth) < ($srcHeight / $srcWidth))
|
||||||
|
{
|
||||||
|
$h = $dstHeight;
|
||||||
|
$w = $h * $srcWidth / $srcHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$w = $dstWidth;
|
||||||
|
$h = $w * $srcHeight / $srcWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
$srcImage->resizeImage($w, $h, \imagick::FILTER_LANCZOS, 0.9);
|
||||||
|
}
|
||||||
|
}
|
28
src/Services/ThumbnailGenerators/ImageThumbnailGenerator.php
Normal file
28
src/Services/ThumbnailGenerators/ImageThumbnailGenerator.php
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Services\ThumbnailGenerators;
|
||||||
|
|
||||||
|
class ImageThumbnailGenerator implements IThumbnailGenerator
|
||||||
|
{
|
||||||
|
private $imageImagickThumbnailGenerator;
|
||||||
|
private $imageGdThumbnailGenerator;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ImageImagickThumbnailGenerator $imageImagickThumbnailGenerator,
|
||||||
|
ImageGdThumbnailGenerator $imageGdThumbnailGenerator)
|
||||||
|
{
|
||||||
|
$this->imageImagickThumbnailGenerator = $imageImagickThumbnailGenerator;
|
||||||
|
$this->imageGdThumbnailGenerator = $imageGdThumbnailGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateFromFile($srcPath, $dstPath, $width, $height)
|
||||||
|
{
|
||||||
|
if (extension_loaded('imagick'))
|
||||||
|
$strategy = $this->imageImagickThumbnailGenerator;
|
||||||
|
elseif (extension_loaded('gd'))
|
||||||
|
$strategy = $this->imageGdThumbnailGenerator;
|
||||||
|
else
|
||||||
|
throw new \Exception('Both imagick and gd extensions are disabled');
|
||||||
|
|
||||||
|
return $strategy->generateFromFile($srcPath, $dstPath, $width, $height);
|
||||||
|
}
|
||||||
|
}
|
30
src/Services/ThumbnailService.php
Normal file
30
src/Services/ThumbnailService.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Services;
|
||||||
|
|
||||||
|
class ThumbnailService
|
||||||
|
{
|
||||||
|
private $fileService;
|
||||||
|
private $thumbnailGenerator;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
FileService $fileService,
|
||||||
|
ThumbnailGenerators\SmartThumbnailGenerator $thumbnailGenerator)
|
||||||
|
{
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->thumbnailGenerator = $thumbnailGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateFromFile($source, $width, $height)
|
||||||
|
{
|
||||||
|
$target = $source . '-thumb' . $width . 'x' . $height . '.jpg';
|
||||||
|
|
||||||
|
if (!$this->fileService->exists($target))
|
||||||
|
{
|
||||||
|
$fullSource = $this->fileService->getFullPath($source);
|
||||||
|
$fullTarget = $this->fileService->getFullPath($target);
|
||||||
|
$this->thumbnailGenerator->generateFromFile($fullSource, $fullTarget, $width, $height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ class UserService
|
||||||
private $userSearchService;
|
private $userSearchService;
|
||||||
private $passwordService;
|
private $passwordService;
|
||||||
private $emailService;
|
private $emailService;
|
||||||
|
private $fileService;
|
||||||
private $timeService;
|
private $timeService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -18,6 +19,7 @@ class UserService
|
||||||
\Szurubooru\Dao\Services\UserSearchService $userSearchService,
|
\Szurubooru\Dao\Services\UserSearchService $userSearchService,
|
||||||
\Szurubooru\Services\PasswordService $passwordService,
|
\Szurubooru\Services\PasswordService $passwordService,
|
||||||
\Szurubooru\Services\EmailService $emailService,
|
\Szurubooru\Services\EmailService $emailService,
|
||||||
|
\Szurubooru\Services\FileService $fileService,
|
||||||
\Szurubooru\Services\TimeService $timeService)
|
\Szurubooru\Services\TimeService $timeService)
|
||||||
{
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
@ -26,6 +28,7 @@ class UserService
|
||||||
$this->userSearchService = $userSearchService;
|
$this->userSearchService = $userSearchService;
|
||||||
$this->passwordService = $passwordService;
|
$this->passwordService = $passwordService;
|
||||||
$this->emailService = $emailService;
|
$this->emailService = $emailService;
|
||||||
|
$this->fileService = $fileService;
|
||||||
$this->timeService = $timeService;
|
$this->timeService = $timeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,17 +45,17 @@ class UserService
|
||||||
return $this->userSearchService->getFiltered($searchFilter);
|
return $this->userSearchService->getFiltered($searchFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function register(\Szurubooru\FormData\RegistrationFormData $formData)
|
public function createUser(\Szurubooru\FormData\RegistrationFormData $formData)
|
||||||
{
|
{
|
||||||
$this->validator->validateUserName($formData->name);
|
$this->validator->validateUserName($formData->userName);
|
||||||
$this->validator->validatePassword($formData->password);
|
$this->validator->validatePassword($formData->password);
|
||||||
$this->validator->validateEmail($formData->email);
|
$this->validator->validateEmail($formData->email);
|
||||||
|
|
||||||
if ($this->userDao->getByName($formData->name))
|
if ($this->userDao->getByName($formData->userName))
|
||||||
throw new \DomainException('User with this name already exists.');
|
throw new \DomainException('User with this name already exists.');
|
||||||
|
|
||||||
$user = new \Szurubooru\Entities\User();
|
$user = new \Szurubooru\Entities\User();
|
||||||
$user->name = $formData->name;
|
$user->name = $formData->userName;
|
||||||
$user->email = $formData->email;
|
$user->email = $formData->email;
|
||||||
$user->passwordHash = $this->passwordService->getHash($formData->password);
|
$user->passwordHash = $this->passwordService->getHash($formData->password);
|
||||||
$user->accessRank = $this->userDao->hasAnyUsers()
|
$user->accessRank = $this->userDao->hasAnyUsers()
|
||||||
|
@ -60,15 +63,81 @@ class UserService
|
||||||
: \Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR;
|
: \Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR;
|
||||||
$user->registrationTime = $this->timeService->getCurrentTime();
|
$user->registrationTime = $this->timeService->getCurrentTime();
|
||||||
$user->lastLoginTime = null;
|
$user->lastLoginTime = null;
|
||||||
|
$user->avatarStyle = \Szurubooru\Entities\User::AVATAR_STYLE_GRAVATAR;
|
||||||
|
|
||||||
//todo: send activation mail if necessary
|
$this->sendActivationMailIfNeeded($user);
|
||||||
|
|
||||||
return $this->userDao->save($user);
|
return $this->userDao->save($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteByName($name)
|
public function updateUser($userName, \Szurubooru\FormData\UserEditFormData $formData)
|
||||||
{
|
{
|
||||||
$this->userDao->deleteByName($name);
|
$user = $this->getByName($userName);
|
||||||
|
if (!$user)
|
||||||
|
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
|
||||||
|
|
||||||
|
if ($formData->avatarStyle !== null)
|
||||||
|
{
|
||||||
|
$user->avatarStyle = \Szurubooru\Helpers\EnumHelper::avatarStyleFromString($formData->avatarStyle);
|
||||||
|
if ($formData->avatarContent)
|
||||||
|
$this->fileService->saveFromBase64($formData->avatarContent, $this->getCustomAvatarSourcePath($user));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->userName !== null and $formData->userName != $user->name)
|
||||||
|
{
|
||||||
|
$this->validator->validateUserName($formData->userName);
|
||||||
|
if ($this->userDao->getByName($formData->userName))
|
||||||
|
throw new \DomainException('User with this name already exists.');
|
||||||
|
|
||||||
|
$user->name = $formData->userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->password !== null)
|
||||||
|
{
|
||||||
|
$this->validator->validatePassword($formData->password);
|
||||||
|
$user->passwordHash = $this->passwordService->getHash($formData->password);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->email !== null)
|
||||||
|
{
|
||||||
|
$this->validator->validateEmail($formData->email);
|
||||||
|
$user->email = $formData->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->accessRank !== null)
|
||||||
|
{
|
||||||
|
$user->accessRank = \Szurubooru\Helpers\EnumHelper::accessRankFromString($formData->accessRank);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formData->email !== null)
|
||||||
|
$this->sendActivationMailIfNeeded($user);
|
||||||
|
|
||||||
|
return $this->userDao->save($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteUserByName($userName)
|
||||||
|
{
|
||||||
|
$user = $this->getByName($userName);
|
||||||
|
if (!$user)
|
||||||
|
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
|
||||||
|
|
||||||
|
$this->userDao->deleteByName($userName);
|
||||||
|
$this->fileService->delete($this->getCustomAvatarSourcePath($user));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCustomAvatarSourcePath($user)
|
||||||
|
{
|
||||||
|
return 'avatars' . DIRECTORY_SEPARATOR . $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBlankAvatarSourcePath()
|
||||||
|
{
|
||||||
|
return 'avatars' . DIRECTORY_SEPARATOR . 'blank.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendActivationMailIfNeeded(\Szurubooru\Entities\User &$user)
|
||||||
|
{
|
||||||
|
//todo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
$dataDirectory = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data';
|
||||||
return [
|
return [
|
||||||
\Szurubooru\Config::class => DI\object()->constructor([
|
\Szurubooru\Config::class => DI\object()->constructor([
|
||||||
__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'config.ini',
|
$dataDirectory . DIRECTORY_SEPARATOR . 'config.ini',
|
||||||
__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'local.ini']),
|
$dataDirectory . DIRECTORY_SEPARATOR . 'local.ini']),
|
||||||
|
|
||||||
|
\Szurubooru\Services\FileService::class => DI\object()->constructor($dataDirectory),
|
||||||
|
|
||||||
\Szurubooru\ControllerRepository::class => DI\object()->constructor(DI\link('controllers')),
|
\Szurubooru\ControllerRepository::class => DI\object()->constructor(DI\link('controllers')),
|
||||||
|
|
||||||
|
@ -10,6 +13,7 @@ return [
|
||||||
return [
|
return [
|
||||||
$c->get(\Szurubooru\Controllers\AuthController::class),
|
$c->get(\Szurubooru\Controllers\AuthController::class),
|
||||||
$c->get(\Szurubooru\Controllers\UserController::class),
|
$c->get(\Szurubooru\Controllers\UserController::class),
|
||||||
|
$c->get(\Szurubooru\Controllers\UserAvatarController::class),
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,6 +12,23 @@ abstract class AbstractTestCase extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
return new ConfigMock();
|
return new ConfigMock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTestDirectory()
|
||||||
|
{
|
||||||
|
return __DIR__ . DIRECTORY_SEPARATOR . 'files';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown()
|
||||||
|
{
|
||||||
|
$this->cleanTestDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanTestDirectory()
|
||||||
|
{
|
||||||
|
foreach (scandir($this->getTestDirectory()) as $fn)
|
||||||
|
if ($fn{0} != '.')
|
||||||
|
unlink($this->getTestDirectory() . DIRECTORY_SEPARATOR . $fn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
|
|
16
tests/Services/FileServiceTest.php
Normal file
16
tests/Services/FileServiceTest.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Tests\Services;
|
||||||
|
|
||||||
|
class FileServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testSaving()
|
||||||
|
{
|
||||||
|
$httpHelper = $this->mock( \Szurubooru\Helpers\HttpHelper::class);
|
||||||
|
$fileService = new \Szurubooru\Services\FileService($this->getTestDirectory(), $httpHelper);
|
||||||
|
$input = 'data:text/plain,YXdlc29tZSBkb2c=';
|
||||||
|
$fileService->saveFromBase64($input, 'dog.txt');
|
||||||
|
$expected = 'awesome dog';
|
||||||
|
$actual = file_get_contents($this->getTestDirectory() . DIRECTORY_SEPARATOR . 'dog.txt');
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
private $userSearchServiceMock;
|
private $userSearchServiceMock;
|
||||||
private $passwordServiceMock;
|
private $passwordServiceMock;
|
||||||
private $emailServiceMock;
|
private $emailServiceMock;
|
||||||
|
private $fileServiceMock;
|
||||||
private $timeServiceMock;
|
private $timeServiceMock;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
|
@ -19,6 +20,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->userSearchService = $this->mock(\Szurubooru\Dao\Services\UserSearchService::class);
|
$this->userSearchService = $this->mock(\Szurubooru\Dao\Services\UserSearchService::class);
|
||||||
$this->passwordServiceMock = $this->mock(\Szurubooru\Services\PasswordService::class);
|
$this->passwordServiceMock = $this->mock(\Szurubooru\Services\PasswordService::class);
|
||||||
$this->emailServiceMock = $this->mock(\Szurubooru\Services\EmailService::class);
|
$this->emailServiceMock = $this->mock(\Szurubooru\Services\EmailService::class);
|
||||||
|
$this->fileServiceMock = $this->mock(\Szurubooru\Services\FileService::class);
|
||||||
$this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class);
|
$this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +45,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
public function testValidRegistration()
|
public function testValidRegistration()
|
||||||
{
|
{
|
||||||
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
||||||
$formData->name = 'user';
|
$formData->userName = 'user';
|
||||||
$formData->password = 'password';
|
$formData->password = 'password';
|
||||||
$formData->email = 'email';
|
$formData->email = 'email';
|
||||||
|
|
||||||
|
@ -53,7 +55,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
||||||
|
|
||||||
$userService = $this->getUserService();
|
$userService = $this->getUserService();
|
||||||
$savedUser = $userService->register($formData);
|
$savedUser = $userService->createUser($formData);
|
||||||
|
|
||||||
$this->assertEquals('user', $savedUser->name);
|
$this->assertEquals('user', $savedUser->name);
|
||||||
$this->assertEquals('email', $savedUser->email);
|
$this->assertEquals('email', $savedUser->email);
|
||||||
|
@ -65,7 +67,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
public function testAccessRankOfFirstUser()
|
public function testAccessRankOfFirstUser()
|
||||||
{
|
{
|
||||||
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
||||||
$formData->name = 'user';
|
$formData->userName = 'user';
|
||||||
$formData->password = 'password';
|
$formData->password = 'password';
|
||||||
$formData->email = 'email';
|
$formData->email = 'email';
|
||||||
|
|
||||||
|
@ -73,7 +75,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
||||||
|
|
||||||
$userService = $this->getUserService();
|
$userService = $this->getUserService();
|
||||||
$savedUser = $userService->register($formData);
|
$savedUser = $userService->createUser($formData);
|
||||||
|
|
||||||
$this->assertEquals(\Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR, $savedUser->accessRank);
|
$this->assertEquals(\Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR, $savedUser->accessRank);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +83,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
public function testRegistrationWhenUserExists()
|
public function testRegistrationWhenUserExists()
|
||||||
{
|
{
|
||||||
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
||||||
$formData->name = 'user';
|
$formData->userName = 'user';
|
||||||
$formData->password = 'password';
|
$formData->password = 'password';
|
||||||
$formData->email = 'email';
|
$formData->email = 'email';
|
||||||
|
|
||||||
|
@ -92,7 +94,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$userService = $this->getUserService();
|
$userService = $this->getUserService();
|
||||||
|
|
||||||
$this->setExpectedException(\Exception::class, 'User with this name already exists');
|
$this->setExpectedException(\Exception::class, 'User with this name already exists');
|
||||||
$savedUser = $userService->register($formData);
|
$savedUser = $userService->createUser($formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getUserService()
|
private function getUserService()
|
||||||
|
@ -104,6 +106,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->userSearchService,
|
$this->userSearchService,
|
||||||
$this->passwordServiceMock,
|
$this->passwordServiceMock,
|
||||||
$this->emailServiceMock,
|
$this->emailServiceMock,
|
||||||
|
$this->fileServiceMock,
|
||||||
$this->timeServiceMock);
|
$this->timeServiceMock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
tests/files/.gitignore
vendored
Normal file
2
tests/files/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
Loading…
Reference in a new issue