50 Commits
0.1.0 ... 0.2.0

Author SHA1 Message Date
cf749aa5fd Version upgrade (0.2.0) 2013-10-25 22:10:36 +02:00
0712f15ee4 Closed #50 2013-10-25 17:25:05 +02:00
db180376d4 Better help 2013-10-25 17:20:11 +02:00
0eb1ef4fff Added hotkey for next/prev page 2013-10-25 17:14:26 +02:00
5c76a41ae7 Fixed IE post list image alignment 2013-10-25 15:50:29 +02:00
0ea25dad24 Fixed IE border 2013-10-25 15:49:52 +02:00
c648cd848d Fixed hotkeys bug 2013-10-25 15:47:42 +02:00
c662d52d62 Closed #34
Introducing keyboard shortcut.
Every page:
[Q] - focus search
[W] - scroll up
[S] - scroll down
Post:
[A] - next post
[D] - previous post
[E] - edit post

Also, when clicking on post edit, browser is scrolled to the form.
2013-10-25 15:41:09 +02:00
e733da58d2 Removed background from images
Introducing it wasn't smart - I forgot about transparent PNGs
2013-10-25 15:40:32 +02:00
febf22a667 Various fixes
- Upload form: outlook
- Upload form: removed no files warning
- Upload form: fixed pasting empty text
- Forms: width of form elements
- Users: restored missing stylesheet
2013-10-25 14:57:04 +02:00
7d6bab9590 Fixed ce302c438d 2013-10-25 13:20:57 +02:00
2279e5605b Closed #37 2013-10-25 13:18:03 +02:00
4ecb3f3b81 Fixed updating safety settings 2013-10-25 11:55:03 +02:00
89826a0be9 Closed #49 2013-10-25 09:59:46 +02:00
d3eaf27bdc Closed #36 2013-10-25 09:59:42 +02:00
47759adb66 Closed #38 2013-10-24 16:41:41 +02:00
b5070e06fe Fixed thumbnail generating 2013-10-23 22:16:08 +02:00
e1acb8bd99 Reduced page loads
- Entity of user currently logged in is kept serialized in session
- Post is retrieved only if necessary in thumbnail generator
2013-10-23 00:16:52 +02:00
872780397d Fixed thumbnail cache
Custom thumbnails were loaded only after hard ctrl+f5. Now they should be
loaded with f5 alone.
2013-10-22 23:58:55 +02:00
d135f84bf2 Added paginator CSS to comments 2013-10-22 23:57:57 +02:00
31f07672c4 Improved #33 2013-10-22 23:57:53 +02:00
328d3f833b Fixed default user settings regarding safety 2013-10-22 21:56:27 +02:00
87eaa9ba9e Closed #33 2013-10-22 21:44:22 +02:00
18097b6192 Closed #45 2013-10-22 11:40:10 +02:00
739e5d3b5d Added uploader avatar 2013-10-22 09:24:17 +02:00
7cc2a98992 Post edit form moved to separate file 2013-10-22 00:39:41 +02:00
7f9aaad324 User settings DB column greatly compressed 2013-10-22 00:30:12 +02:00
319a9852fc Fixed deleting and (un)hiding 2013-10-22 00:20:58 +02:00
d45ab47d3b Always test your goddamn code 2013-10-22 00:18:41 +02:00
eaa8c4897d Closed #39 2013-10-22 00:17:40 +02:00
823888b0c1 Universal check for form submission 2013-10-22 00:17:36 +02:00
90a75e4d30 User edit/delete forms moved to separate files 2013-10-21 23:29:38 +02:00
ce302c438d Safety list in /upload is resolved automatically 2013-10-21 23:27:47 +02:00
70f931b921 Better checkboxes and radiobuttons 2013-10-21 23:25:56 +02:00
7743753641 Tag list visuals
- long tag text overflow in post-view and tag-list
- tag usage visualized in tag-list
2013-10-21 23:07:30 +02:00
e910d2f517 Smaller safety buttons 2013-10-21 15:16:34 +02:00
83355f3789 A bit more reasonable autocomplete (II) 2013-10-21 15:10:25 +02:00
9f5bdc3da0 A bit more reasonable autocomplete 2013-10-21 15:09:52 +02:00
0f72ef3963 Closed #48 2013-10-21 14:48:28 +02:00
6b55706fb4 Closed #46 2013-10-21 14:32:47 +02:00
ff3e4bc287 Closed #47 2013-10-21 14:24:34 +02:00
f2947a2550 Added "random" tab 2013-10-21 13:13:10 +02:00
aab67f4b6c Better main page 2013-10-21 09:35:06 +02:00
aed60da6f9 Changed post list view
- Thumbs are 150x150
- Centered main content
2013-10-20 19:51:49 +02:00
58a6345ae8 Fixed e-mail address visibility 2013-10-20 19:19:52 +02:00
bc24b7d2cf Fixed problems with Android keyboards
Users were completely unable to type anything.
2013-10-20 18:55:33 +02:00
3052a6f032 Disallowed . and .. as tag 2013-10-20 12:14:44 +02:00
4bfa2a019a Tags now allow dots 2013-10-20 12:08:52 +02:00
688385d553 Fixed /index link 2013-10-20 11:31:56 +02:00
a3be044ced First user doesn't see (unconfirmed) anymore 2013-10-20 11:19:59 +02:00
51 changed files with 1346 additions and 611 deletions

View File

@ -10,14 +10,15 @@ mediaPath=./public_html/media/
title=szurubooru
featuredPostMaxDays=7
debugQueries=0
salt = "1A2/$_4xVa"
[browsing]
usersPerPage=8
postsPerPage=20
thumbWidth=140
thumbHeight=140
thumbWidth=150
thumbHeight=150
thumbStyle=outside
endlessScrolling=1
endlessScrollingDefault=1
maxSearchTokens=4
[comments]
@ -32,7 +33,6 @@ passRegex = "/^.+$/"
userNameMinLength = 3
userNameMaxLength = 20
userNameRegex = "/^[\w_-]+$/ui"
salt = "1A2/$_4xVa"
needEmailForRegistering = 1
needEmailForCommenting = 0
@ -73,13 +73,16 @@ featurePost=moderator
listUsers=registered
viewUser=registered
viewUserEmail=admin
viewUserEmail.all=admin
viewUserEmail.own=registered
changeUserPassword.own=registered
changeUserPassword.all=admin
changeUserEmail.own=registered
changeUserEmail.all=admin
changeUserAccessRank=admin
changeUserName=moderator
changeUserSettings.all=nobody
changeUserSettings.own=registered
acceptUserRegistration=moderator
banUser.own=nobody
banUser.all=admin

View File

@ -49,6 +49,9 @@ foreach ($lines as $line)
download('http://raw.github.com/aehlke/tag-it/master/css/jquery.tagit.css', $libPath . 'tagit' . DS . 'jquery.tagit.css');
download('http://raw.github.com/aehlke/tag-it/master/js/tag-it.min.js', $libPath . 'tagit' . DS . 'jquery.tagit.js');
//Mousetrap
download('https://raw.github.com/ccampbell/mousetrap/master/mousetrap.min.js', $libPath . 'mousetrap' . DS . 'mousetrap.min.js');
//fonts
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans.ttf', $fontsPath . 'DroidSans.ttf');
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans-Bold.ttf', $fontsPath . 'DroidSans-Bold.ttf');

View File

@ -98,8 +98,8 @@ body {
display: inline-block;
float: left;
width: 25px;
line-height: 38px;
margin-right: -1px;
line-height: 28px;
margin: 5px -1px 5px 0;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
}
#top-nav li.safety a:after {
@ -265,7 +265,11 @@ form.aligned input[type=file] {
}
form.aligned input[type=radio],
form.aligned input[type=checkbox] {
vertical-align: text-top;
width: auto;
max-width: auto;
margin: 0 10px 0 0;
padding: 0;
vertical-align: middle;
}
.input-wrapper {
@ -276,8 +280,11 @@ form.aligned input[type=checkbox] {
.input-wrapper input,
.input-wrapper textarea,
.input-wrapper select {
width: 80%;
max-width: 80%;
width: 100%;
max-width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
label {
@ -368,3 +375,7 @@ pre.debug {
.spoiler:hover {
color: black;
}
img {
border: 0;
}

View File

@ -0,0 +1,9 @@
code {
margin: 0 0.5em;
}
span.comma {
margin-left: -0.5em;
}
h1 {
margin-top: 2em;
}

View File

@ -1,70 +1,69 @@
#sidebar {
min-width: 100px;
padding: 5em 0;
width: 25%;
margin-right: 5%;
#welcome {
text-align: center;
}
#sidebar p {
#welcome p {
font-size: small;
margin-top: 0;
}
#sidebar p span:not(:last-child):after {
#welcome p span:not(:last-child):after {
content: '\022C5';
margin: 0 0.5em;
}
#sidebar h1 {
#content h1 {
font-size: 26pt;
}
#sidebar input {
width: 100%;
max-width: 300px;
border: 2px solid #ccc;
padding: 5px;
margin-top: 1em;
margin-bottom: 0;
}
#inner-content {
float: right;
#content {
margin: 0 auto;
width: 70%;
min-width: 500px;
position: relative;
}
.small-screen #content {
width: 100%;
min-width: 0;
max-width: 500px;
}
#inner-content .header .tags:before {
#content .header .tags:before {
margin: 0 0.5em;
content: '\2013';
}
#inner-content .header ul {
#content .header ul {
list-style-type: none;
display: inline;
margin: 0;
padding: 0;
}
#inner-content .header li {
#content .header li {
display: inline;
}
#inner-content .header li:not(:last-child) a:after {
#content .header li:not(:last-child) a:after {
content: ', ';
}
#inner-content .body {
#content .body {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAJElEQVQImWNgYGBgePfu3X8YZoABFA6SIqwS+HXgtANZF7IEAJnGPJE70lLLAAAAAElFTkSuQmCC');
margin: 1em 0;
text-align: center;
}
#inner-content .body img {
#content .body img {
max-width: 100%;
margin: 0 auto;
display: block;
}
#inner-content .body a {
#content .body a {
display: block;
}
#inner-content .header .favs-comments {
#content .header .favs-comments {
margin-left: 0.5em;
float: right;
}
#inner-content .footer {
#content .footer {
text-align: right;
}

View File

@ -1,4 +1,9 @@
.post {
margin: 0.5em;
float: left;
}
.posts-wrapper {
text-align: center;
}
.posts {
margin: 0 auto;
}

View File

@ -5,10 +5,29 @@
position: relative;
display: inline-block;
}
.post-type-flash {
border-color: #dd5;
box-shadow: 0.25em 0.25em #eeb, 0.1em 0.1em 0.5em 0.1em rgba(238,238,187,0.5);
.post-type-youtube:after,
.post-type-flash:after {
position: absolute;
right: 0;
top: 0;
width: 150px;
height: 150px;
content: ' ';
pointer-events: none;
}
.post-type-flash {
border-color: red;
}
.post-type-youtube {
border-color: red;
}
.post-type-flash:after {
background: url('../img/thumb-overlay-swf.png');
}
.post-type-youtube:after {
background: url('../img/thumb-overlay-yt.png');
}
.post:focus,
.post:hover {
@ -20,10 +39,15 @@
opacity: .9;
}
.post a {
display: inline-block;
vertical-align: top;
}
.post img.thumb {
width: 140px;
height: 140px;
display: block;
display: inline-block;
width: 150px;
height: 150px;
vertical-align: top;
}
.post .info-bar {

View File

@ -10,7 +10,7 @@ embed {
}
.post-type-image img {
background: url('../img/bk-image.png') lemonchiffon;
/*background: url('../img/bk-image.png') lemonchiffon;*/
}
.post-type-flash embed {
background: url('../img/bk-swf.png') lemonchiffon;
@ -21,6 +21,10 @@ embed {
margin: 0;
padding: 0;
}
#sidebar .tags li {
overflow: hidden;
text-overflow: ellipsis;
}
#sidebar .tags li .count {
padding-left: 0.5em;
color: silver;
@ -42,6 +46,11 @@ embed {
background-color: silver;
}
#sidebar .uploader img {
vertical-align: middle;
margin: 0 0.5em 0 0;
}
i.icon-prev {
background-position: -12px -1px;
}

View File

@ -0,0 +1,26 @@
.tabs ul {
list-style-type: none;
margin: 0 0 1em 0;
padding: 0;
border-bottom: 1px solid #ccc;
}
.tabs li {
display: inline-block;
}
.tabs li a {
display: inline-block;
padding: 0.5em 1em;
margin-bottom: -1px;
}
.tabs li a {
border: 1px solid white;
border-bottom: 1px solid #ccc;
color: silver;
}
.tabs li.selected a {
border: 1px solid #ccc;
border-bottom: 1px solid white;
color: inherit;
}

View File

@ -6,11 +6,17 @@
-moz-column-width: 14em;
-webkit-column-width: 14em;
}
.tags li a:hover {
opacity: 1 !important;
}
.tags li {
margin: 0.2em 0.5em;
text-align: top;
width: 14em;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.form-wrapper {

View File

@ -8,6 +8,13 @@
float: left;
}
.tab {
margin-bottom: 1em;
}
.tab.url {
display: none;
}
#file-handler-wrapper {
display: table;
width: 100%;
@ -17,7 +24,7 @@
font-size: 150%;
text-align: center;
vertical-align: middle;
height: 300px;
height: 8em;
display: table-cell;
border: 3px dashed #ddd;
}
@ -26,28 +33,34 @@
border-color: firebrick;
}
#url-handler textarea {
width: 100%;
height: 10em;
margin-bottom: 0.5em;
}
.post .thumbnail {
width: 100px;
height: 100px;
line-height: 100px;
background-image: url('../img/thumb-upload.png');
background-image: url('../img/thumb.png');
background-size: 100px 100px;
border: 1px solid black;
vertical-align: middle;
text-align: center;
display: block;
float: left;
margin-right: 1em;
margin-right: 10px;
}
.post .alert,
#upload-step2,
#upload-no-posts,
#post-template {
display: none;
}
.post {
margin-bottom: 4em;
margin: 2em 0;
}
.post .ops {
@ -101,7 +114,8 @@
}
.post label.left {
display: inline-block;
width: 4em;
width: 60px;
padding-right: 10px;
float: left;
}
.post .safety label:not(.left) {
@ -134,11 +148,8 @@ ul.tagit {
font-size: 1em;
}
.submit-wrapper {
text-align: center;
}
#theSubmit {
margin: 0 auto;
#the-submit {
margin: 0 0 0 205px;
}
.post .form-wrapper {

View File

@ -3,33 +3,6 @@
font-size: 90%;
}
.tabs ul {
list-style-type: none;
margin: 0 0 1em 0;
padding: 0;
border-bottom: 1px solid #ccc;
}
.tabs li {
display: inline-block;
}
.tabs li a {
display: inline-block;
padding: 0.5em 1em;
margin-bottom: -1px;
}
.tabs li a {
border: 1px solid white;
border-bottom: 1px solid #ccc;
color: silver;
}
.tabs li.selected a {
border: 1px solid #ccc;
border-bottom: 1px solid white;
color: inherit;
}
.avatar-wrapper {
text-align: center;
}
@ -40,14 +13,20 @@
padding: 0;
}
form.settings label.left,
form.delete label.left,
form.edit label.left {
width: 9em;
}
form.settings .alert,
form.delete .alert,
form.edit .alert {
margin: 1em 0;
}
form.settings input,
form.delete input,
form.edit select,
form.edit input {
width: 16em;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 B

View File

@ -1,4 +1,5 @@
$.fn.hasAttr = function(name) {
$.fn.hasAttr = function(name)
{
return this.attr(name) !== undefined;
};
@ -69,7 +70,7 @@ $(function()
aDom.addClass('inactive');
var url = $(this).attr('href') + '?json';
$.get(url, function(data)
$.get(url, {submit: 1}, function(data)
{
if (data['success'])
{
@ -101,13 +102,14 @@ $(function()
form.append(input);
});
});
$(window).resize();
});
//modify DOM on small viewports
$(window).resize(function()
{
//modify DOM on small viewports
if ($('body').width() == $('body').data('last-width'))
return;
$('#inner-content .unit').addClass('bottom-unit');
if ($('body').width() < 600)
{
@ -118,7 +120,96 @@ $(window).resize(function()
else
{
$('body').removeClass('small-screen');
$('#inner-content').insertAfter($('#sidebar'));
$('#sidebar').insertBefore($('#inner-content'));
$('#sidebar .unit').removeClass('bottom-unit').addClass('left-unit');
}
$('body').data('last-width', $('body').width());
});
$(function()
{
$(window).resize();
});
//autocomplete
function split(val)
{
return val.split(/\s+/);
}
function extractLast(term)
{
return split(term).pop();
}
$(function()
{
var searchInput = $('#top-nav .search input');
searchInput
// don't navigate away from the field on tab when selecting an item
.bind("keydown", function(event)
{
if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active)
{
event.preventDefault();
}
}).autocomplete({
minLength: 1,
source: function(request, response)
{
var term = extractLast(request.term);
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term}, function(data)
{
response($.map(data.tags, function(tag) { return { label: tag, value: tag }; }));
});
},
focus: function()
{
// prevent value inserted on focus
return false;
},
select: function(event, ui)
{
var terms = split(this.value);
terms.pop();
terms.push(ui.item.value);
terms.push('');
this.value = terms.join(' ');
return false;
}
});
});
function getTagItOptions()
{
return {
caseSensitive: false,
autocomplete:
{
source:
function(request, response)
{
var term = request.term.toLowerCase();
var results = $.grep(this.options.availableTags, function(a)
{
if (term.length < 3)
return a.toLowerCase().indexOf(term) == 0;
else
return a.toLowerCase().indexOf(term) != -1;
});
if (!this.options.allowDuplicates)
results = this._subtractArray(results, this.assignedTags());
response(results);
},
}
};
}
$(function()
{
Mousetrap.bind('q', function() { $('#top-nav input').focus(); return false; });
Mousetrap.bind('w', function() { $('body,html').animate({scrollTop: '-=150px'}, 200); });
Mousetrap.bind('s', function() { $('body,html').animate({scrollTop: '+=150px'}, 200); });
Mousetrap.bind('a', function() { var url = $('.paginator .prev:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; });
Mousetrap.bind('d', function() { var url = $('.paginator .next:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; });
});

View File

@ -14,17 +14,16 @@ $(function()
{
tags = data['tags'];
var tagItOptions =
{
caseSensitive: true,
availableTags: tags,
placeholderText: $('.tags input').attr('placeholder')
};
var tagItOptions = getTagItOptions();
tagItOptions.availableTags = tags;
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
$('.tags input').tagit(tagItOptions);
e.preventDefault();
var formDom = $('form.edit-post');
formDom.show().css('height', formDom.height()).hide().slideDown();
$('html, body').animate({ scrollTop: $(formDom).offset().top + 'px' }, 'fast');
});
});
@ -121,4 +120,8 @@ $(function()
$.ajax(ajaxData);
});
Mousetrap.bind('a', function() { var url = $('#sidebar .left a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; });
Mousetrap.bind('d', function() { var url = $('#sidebar .right a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; });
Mousetrap.bind('e', function() { $('li.edit a').trigger('click'); return false; });
});

View File

@ -1,35 +1,36 @@
$(function()
{
$('.tabs nav a').click(function(e)
{
e.preventDefault();
var className = $(this).parents('li').attr('class').replace('selected', '').replace(/^\s+|\s+$/, '');
$('.tabs nav li').removeClass('selected');
$(this).parents('li').addClass('selected');
$('.tab').hide();
$('.tab.' + className).show();
});
var tags = [];
$.getJSON('/tags?json', function(data)
{
tags = data['tags'];
});
var handler = $('#file-handler');
handler.on('dragenter', function(e)
$('#file-handler').on('dragenter', function(e)
{
$(this).addClass('active');
});
handler.on('dragleave', function(e)
}).on('dragleave', function(e)
{
$(this).removeClass('active');
});
handler.on('dragover', function(e)
}).on('dragover', function(e)
{
e.preventDefault();
});
handler.on('drop', function(e)
}).on('drop', function(e)
{
e.preventDefault();
handleFiles(e.originalEvent.dataTransfer.files);
$(this).trigger('dragleave');
});
handler.on('click', function(e)
}).on('click', function(e)
{
$(':file').show().focus().trigger('click').hide();
});
@ -39,6 +40,24 @@ $(function()
handleFiles(this.files);
});
$('#url-handler-wrapper button').click(function(e)
{
var urls = [];
$.each($('#url-handler-wrapper textarea').val().split(/\s+/), function(i, url)
{
url = url.replace(/^\s+|\s+$/, '');
if (url == '')
return;
urls.push(url);
});
$('#url-handler-wrapper textarea').val('');
handleURLs(urls);
});
$('.post .move-down-trigger, .post .move-up-trigger').on('click', function()
{
var dir = $(this).hasClass('move-down-trigger') ? 'd' : 'u';
@ -53,15 +72,12 @@ $(function()
$(this).parents('.post').slideUp(function()
{
$(this).remove();
handleInputs([]);
});
if ($('#upload-step2 .post').length == 1)
{
$('#upload-step2').slideUp();
$('#upload-no-posts').slideDown();
}
});
function sendNextPost()
{
var posts = $('#upload-step2 .post');
@ -73,15 +89,18 @@ $(function()
var postDom = posts.first();
var url = postDom.find('form').attr('action') + '?json';
var file = postDom.data('file');
var sourceFile = postDom.data('file');
var sourceUrl = postDom.data('url');
var tags = postDom.find('[name=tags]').val();
var safety = postDom.find('[name=safety]:checked').val();
var source = postDom.find('[name=source]').val();
var fd = new FormData();
fd.append('file', file);
fd.append('file', sourceFile);
fd.append('url', sourceUrl);
fd.append('tags', tags);
fd.append('safety', safety);
fd.append('source', source);
fd.append('submit', 1);
var ajaxData =
{
@ -145,51 +164,79 @@ $(function()
function handleFiles(files)
{
$('#upload-step1').fadeOut(function()
handleInputs(files, function(postDom, file)
{
for (var i = 0; i < files.length; i ++)
postDom.data('file', file);
$('.file-name strong', postDom).text(file.name);
if (file.type.match('image.*'))
{
var file = files[i];
var postDom = $('#post-template').clone(true);
postDom.find('form').submit(false);
postDom.removeAttr('id');
postDom.data('file', file);
$('.file-name strong', postDom).text(file.name);
$('.posts').append(postDom);
postDom.show();
var tagItOptions =
{
caseSensitive: true,
availableTags: tags,
placeholderText: $('.tags input').attr('placeholder')
};
$('.tags input', postDom).tagit(tagItOptions);
if (!file.type.match('image.*'))
{
continue;
}
var img = postDom.find('img')
var reader = new FileReader();
reader.onload = (function(theFile, img)
{
return function(e)
{
/*img.css('max-width', img.css('width'));
img.css('max-height', img.css('height'));
img.css('width', 'auto');
img.css('height', 'auto');*/
img.css('background-image', 'none');
img.attr('src', e.target.result);
};
})(file, img);
reader.readAsDataURL(file);
}
$('#upload-step2').fadeIn(function()
{
});
});
}
function handleURLs(urls)
{
handleInputs(urls, function(postDom, url)
{
postDom.data('url', url);
postDom.find('[name=source]').val(url);
if (matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/))
{
postDom.find('.file-name strong').text(url);
$.getJSON('http://gdata.youtube.com/feeds/api/videos/' + matches[1] + '?v=2&alt=jsonc', function(data)
{
postDom.find('.file-name strong')
.text(data.data.title);
postDom.find('img')
.css('background-image', 'none')
.attr('src', data.data.thumbnail.hqDefault);
});
}
else
{
postDom.find('.file-name strong')
.text(url);
postDom.find('img')
.css('background-image', 'none')
.attr('src', url);
}
});
}
function handleInputs(inputs, callback)
{
for (var i = 0; i < inputs.length; i ++)
{
var input = inputs[i];
var postDom = $('#post-template').clone(true);
postDom.find('form').submit(false);
postDom.removeAttr('id');
$('.posts').append(postDom);
postDom.show();
var tagItOptions = getTagItOptions();
tagItOptions.availableTags = tags;
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
$('.tags input', postDom).tagit(tagItOptions);
callback(postDom, input);
}
if ($('.posts .post').length == 0)
$('#upload-step2').fadeOut();
else
$('#upload-step2').fadeIn();
}
});

View File

@ -0,0 +1,47 @@
<?php
require_once __DIR__ . '/../src/core.php';
function usage()
{
echo 'Usage: ' . basename(__FILE__);
echo ' -print|-purge';
return true;
}
array_shift($argv);
if (empty($argv))
usage() and die;
function printUser($user)
{
echo 'ID: ' . $user->id . PHP_EOL;
echo 'Name: ' . $user->name . PHP_EOL;
echo 'E-mail: ' . $user->email_unconfirmed . PHP_EOL;
echo 'Date joined: ' . date('Y-m-d H:i:s', $user->join_date) . PHP_EOL;
echo PHP_EOL;
}
$action = array_shift($argv);
switch ($action)
{
case '-print':
$func = 'printUser';
break;
case '-purge':
$func = function($user)
{
printUser($user);
R::trash($user);
};
break;
default:
die('Unknown action' . PHP_EOL);
}
$rows = R::find('user', 'email_confirmed IS NULL AND DATETIME(join_date) < DATETIME("now", "-21 days")');
foreach ($rows as $user)
{
$func($user);
}

View File

@ -6,12 +6,27 @@ class Bootstrap
$this->context->loggedIn = false;
if (isset($_SESSION['user-id']))
{
$this->context->user = R::findOne('user', 'id = ?', [$_SESSION['user-id']]);
if (!isset($_SESSION['user']))
{
$dbUser = R::findOne('user', 'id = ?', [$_SESSION['user-id']]);
$_SESSION['user'] = serialize($dbUser);
}
$this->context->user = unserialize($_SESSION['user']);
if (!empty($this->context->user))
{
$this->context->loggedIn = true;
}
}
if (!$this->context->loggedIn)
{
try
{
AuthController::tryAutoLogin();
}
catch (Exception $e)
{
}
}
if (empty($this->context->user))
{
$dummy = R::dispense('user');
@ -37,6 +52,7 @@ class Bootstrap
[
'../lib/jquery/jquery.min.js',
'../lib/jquery-ui/jquery-ui.min.js',
'../lib/mousetrap/mousetrap.min.js',
'core.js',
];

View File

@ -1,6 +1,43 @@
<?php
class AuthController
{
public static function tryLogin($name, $password)
{
$config = \Chibi\Registry::getConfig();
$dbUser = R::findOne('user', 'name = ?', [$name]);
if ($dbUser === null)
throw new SimpleException('Invalid username');
$passwordHash = Model_User::hashPassword($password, $dbUser->pass_salt);
if ($passwordHash != $dbUser->pass_hash)
throw new SimpleException('Invalid password');
if (!$dbUser->staff_confirmed and $config->registration->staffActivation)
throw new SimpleException('Staff hasn\'t confirmed your registration yet');
if ($dbUser->banned)
throw new SimpleException('You are banned');
if ($config->registration->needEmailForRegistering)
PrivilegesHelper::confirmEmail($dbUser);
$_SESSION['user-id'] = $dbUser->id;
$_SESSION['user'] = serialize($dbUser);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
return $dbUser;
}
public static function tryAutoLogin()
{
if (!isset($_COOKIE['auth']))
return;
$token = TextHelper::decrypt($_COOKIE['auth']);
list ($name, $password) = array_map('base64_decode', explode('|', $token));
return self::tryLogin($name, $password);
}
/**
* @route /auth/login
*/
@ -17,29 +54,17 @@ class AuthController
return;
}
$suppliedName = InputHelper::get('name');
$suppliedPassword = InputHelper::get('password');
if ($suppliedName !== null and $suppliedPassword !== null)
if (InputHelper::get('submit'))
{
$dbUser = R::findOne('user', 'name = ?', [$suppliedName]);
if ($dbUser === null)
throw new SimpleException('Invalid username');
$suppliedName = InputHelper::get('name');
$suppliedPassword = InputHelper::get('password');
$dbUser = self::tryLogin($suppliedName, $suppliedPassword);
$suppliedPasswordHash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
if ($suppliedPasswordHash != $dbUser->pass_hash)
throw new SimpleException('Invalid password');
if (!$dbUser->staff_confirmed and $this->config->registration->staffActivation)
throw new SimpleException('Staff hasn\'t confirmed your registration yet');
if ($dbUser->banned)
throw new SimpleException('You are banned');
if ($this->config->registration->needEmailForRegistering)
PrivilegesHelper::confirmEmail($dbUser);
$_SESSION['user-id'] = $dbUser->id;
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
if (InputHelper::get('remember'))
{
$token = implode('|', [base64_encode($suppliedName), base64_encode($suppliedPassword)]);
setcookie('auth', TextHelper::encrypt($token), time() + 365 * 24 * 3600, '/');
}
$this->context->transport->success = true;
}
}
@ -52,6 +77,7 @@ class AuthController
$this->context->viewName = null;
$this->context->viewName = null;
unset($_SESSION['user-id']);
setcookie('auth', false, 0, '/');
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
}
}

View File

@ -11,8 +11,9 @@ class CommentController
$this->context->stylesheets []= 'post-small.css';
$this->context->stylesheets []= 'comment-list.css';
$this->context->stylesheets []= 'comment-small.css';
$this->context->stylesheets []= 'paginator.css';
$this->context->subTitle = 'comments';
if ($this->config->browsing->endlessScrolling)
if ($this->context->user->hasEnabledEndlessScrolling())
$this->context->scripts []= 'paginator-endless.js';
$page = intval($page);

View File

@ -49,6 +49,7 @@ class IndexController
*/
public function helpAction()
{
$this->context->stylesheets []= 'index-help.css';
$this->context->subTitle = 'help';
}
}

View File

@ -60,7 +60,7 @@ class PostController
$this->context->stylesheets []= 'post-small.css';
$this->context->stylesheets []= 'post-list.css';
$this->context->stylesheets []= 'paginator.css';
if ($this->config->browsing->endlessScrolling)
if ($this->context->user->hasEnabledEndlessScrolling())
$this->context->scripts []= 'paginator-endless.js';
//redirect requests in form of /posts/?query=... to canonical address
@ -178,27 +178,95 @@ class PostController
/**
* @route /random
* @route /random/{page}
* @validate page \d*
*/
public function randomAction($page = 1)
{
$this->listAction('order:random', $page);
$this->context->viewName = 'post-list';
}
/**
* @route /post/upload
*/
public function uploadAction()
{
$this->context->stylesheets []= 'upload.css';
$this->context->stylesheets []= 'tabs.css';
$this->context->scripts []= 'upload.js';
$this->context->subTitle = 'upload';
PrivilegesHelper::confirmWithException(Privilege::UploadPost);
if ($this->config->registration->needEmailForUploading)
PrivilegesHelper::confirmEmail($this->context->user);
if (!empty($_FILES['file']['name']))
if (InputHelper::get('submit'))
{
/* file contents */
$suppliedFile = $_FILES['file'];
self::handleUploadErrors($suppliedFile);
if (isset($_FILES['file']))
{
$suppliedFile = $_FILES['file'];
self::handleUploadErrors($suppliedFile);
$origName = basename($suppliedFile['name']);
$sourcePath = $suppliedFile['tmp_name'];
}
elseif (InputHelper::get('url'))
{
$url = InputHelper::get('url');
$origName = $url;
if (!preg_match('/^https?:\/\//', $url))
throw new SimpleException('Invalid URL "' . $url . '"');
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $url, $matches))
{
$origName = $matches[1];
$postType = PostType::Youtube;
$sourcePath = null;
}
else
{
$sourcePath = tempnam(sys_get_temp_dir(), 'upload') . '.dat';
//warning: low level sh*t ahead
//download the URL $url into $sourcePath
$maxBytes = TextHelper::stripBytesUnits(ini_get('upload_max_filesize'));
set_time_limit(0);
$urlFP = fopen($url, 'rb');
if (!$urlFP)
throw new SimpleException('Cannot open URL for reading');
$sourceFP = fopen($sourcePath, 'w+b');
if (!$sourceFP)
{
fclose($urlFP);
throw new SimpleException('Cannot open file for writing');
}
try
{
while (!feof($urlFP))
{
$buffer = fread($urlFP, 4 * 1024);
if (fwrite($sourceFP, $buffer) === false)
throw new SimpleException('Cannot write into file');
fflush($sourceFP);
if (ftell($sourceFP) > $maxBytes)
throw new SimpleException('File is too big (maximum allowed size: ' . TextHelper::useBytesUnits($maxBytes) . ')');
}
}
finally
{
fclose($urlFP);
fclose($sourceFP);
}
}
}
/* file details */
$mimeType = mime_content_type($suppliedFile['tmp_name']);
$mimeType = $sourcePath ? mime_content_type($sourcePath) : null;
$imageWidth = null;
$imageHeight = null;
switch ($mimeType)
@ -207,20 +275,36 @@ class PostController
case 'image/png':
case 'image/jpeg':
$postType = PostType::Image;
list ($imageWidth, $imageHeight) = getimagesize($suppliedFile['tmp_name']);
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
break;
case 'application/x-shockwave-flash':
$postType = PostType::Flash;
list ($imageWidth, $imageHeight) = getimagesize($suppliedFile['tmp_name']);
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
break;
default:
throw new SimpleException('Invalid file type "' . $mimeType . '"');
if (!isset($postType))
throw new SimpleException('Invalid file type "' . $mimeType . '"');
}
$fileHash = md5_file($suppliedFile['tmp_name']);
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$fileHash]);
if ($duplicatedPost !== null)
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
if ($sourcePath)
{
$fileSize = filesize($sourcePath);
$fileHash = md5_file($sourcePath);
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$fileHash]);
if ($duplicatedPost !== null)
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
}
else
{
$fileSize = 0;
$fileHash = null;
if ($postType == PostType::Youtube)
{
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
if ($duplicatedPost !== null)
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
}
}
do
{
@ -247,9 +331,9 @@ class PostController
$dbPost = R::dispense('post');
$dbPost->type = $postType;
$dbPost->name = $name;
$dbPost->orig_name = basename($suppliedFile['name']);
$dbPost->orig_name = $origName;
$dbPost->file_hash = $fileHash;
$dbPost->file_size = filesize($suppliedFile['tmp_name']);
$dbPost->file_size = $fileSize;
$dbPost->mime_type = $mimeType;
$dbPost->safety = $suppliedSafety;
$dbPost->source = $suppliedSource;
@ -261,7 +345,13 @@ class PostController
$dbPost->ownFavoritee = [];
$dbPost->sharedTag = $dbTags;
move_uploaded_file($suppliedFile['tmp_name'], $path);
if ($sourcePath)
{
if (is_uploaded_file($sourcePath))
move_uploaded_file($sourcePath, $path);
else
rename($sourcePath, $path);
}
R::store($dbPost);
$this->context->transport->success = true;
@ -277,73 +367,71 @@ class PostController
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
$edited = false;
$this->context->transport->post = $post;
/* safety */
$suppliedSafety = InputHelper::get('safety');
if ($suppliedSafety !== null)
if (InputHelper::get('submit'))
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
$post->safety = $suppliedSafety;
$edited = true;
}
/* safety */
$suppliedSafety = InputHelper::get('safety');
if ($suppliedSafety !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
$post->safety = $suppliedSafety;
$edited = true;
}
/* tags */
$suppliedTags = InputHelper::get('tags');
if ($suppliedTags !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$currentToken = self::serializeTags($post);
if (InputHelper::get('tags-token') != $currentToken)
throw new SimpleException('Someone else has changed the tags in the meantime');
/* tags */
$suppliedTags = InputHelper::get('tags');
if ($suppliedTags !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$currentToken = self::serializeTags($post);
if (InputHelper::get('tags-token') != $currentToken)
throw new SimpleException('Someone else has changed the tags in the meantime');
$suppliedTags = Model_Tag::validateTags($suppliedTags);
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
$post->sharedTag = $dbTags;
$edited = true;
}
$suppliedTags = Model_Tag::validateTags($suppliedTags);
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
$post->sharedTag = $dbTags;
$edited = true;
}
/* thumbnail */
if (!empty($_FILES['thumb']['name']))
{
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedFile = $_FILES['thumb'];
self::handleUploadErrors($suppliedFile);
/* thumbnail */
if (!empty($_FILES['thumb']['name']))
{
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedFile = $_FILES['thumb'];
self::handleUploadErrors($suppliedFile);
$mimeType = mime_content_type($suppliedFile['tmp_name']);
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
throw new SimpleException('Invalid thumbnail type "' . $mimeType . '"');
list ($imageWidth, $imageHeight) = getimagesize($suppliedFile['tmp_name']);
if ($imageWidth != $this->config->browsing->thumbWidth)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbWidth . ')');
if ($imageWidth != $this->config->browsing->thumbHeight)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbHeight . ')');
$mimeType = mime_content_type($suppliedFile['tmp_name']);
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
throw new SimpleException('Invalid thumbnail type "' . $mimeType . '"');
list ($imageWidth, $imageHeight) = getimagesize($suppliedFile['tmp_name']);
if ($imageWidth != $this->config->browsing->thumbWidth)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbWidth . ')');
if ($imageWidth != $this->config->browsing->thumbHeight)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbHeight . ')');
$path = $this->config->main->thumbsPath . DS . $post->name;
move_uploaded_file($suppliedFile['tmp_name'], $path);
}
$path = $this->config->main->thumbsPath . DS . $post->name;
move_uploaded_file($suppliedFile['tmp_name'], $path);
}
/* source */
$suppliedSource = InputHelper::get('source');
if ($suppliedSource !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSource = Model_Post::validateSource($suppliedSource);
$post->source = $suppliedSource;
$edited = true;
}
/* source */
$suppliedSource = InputHelper::get('source');
if ($suppliedSource !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSource = Model_Post::validateSource($suppliedSource);
$post->source = $suppliedSource;
$edited = true;
}
/* db storage */
if ($edited)
R::store($post);
$this->context->transport->success = true;
$this->context->transport->success = true;
}
}
@ -354,10 +442,14 @@ class PostController
public function hideAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$post->hidden = true;
R::store($post);
$this->context->transport->success = true;
if (InputHelper::get('submit'))
{
$post->hidden = true;
R::store($post);
$this->context->transport->success = true;
}
}
/**
@ -366,10 +458,14 @@ class PostController
public function unhideAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$post->hidden = false;
R::store($post);
$this->context->transport->success = true;
if (InputHelper::get('submit'))
{
$post->hidden = false;
R::store($post);
$this->context->transport->success = true;
}
}
/**
@ -378,13 +474,17 @@ class PostController
public function deleteAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
//remove stuff from auxiliary tables
$post->ownFavoritee = [];
$post->sharedTag = [];
R::store($post);
R::trash($post);
$this->context->transport->success = true;
if (InputHelper::get('submit'))
{
//remove stuff from auxiliary tables
$post->ownFavoritee = [];
$post->sharedTag = [];
R::store($post);
R::trash($post);
$this->context->transport->success = true;
}
}
@ -397,18 +497,21 @@ class PostController
{
$post = Model_Post::locate($id);
R::preload($post, ['favoritee' => 'user']);
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
foreach ($post->via('favoritee')->sharedUser as $fav)
if ($fav->id == $this->context->user->id)
throw new SimpleException('Already in favorites');
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
$post->link('favoritee')->user = $this->context->user;
R::store($post);
$this->context->transport->success = true;
if (InputHelper::get('submit'))
{
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
foreach ($post->via('favoritee')->sharedUser as $fav)
if ($fav->id == $this->context->user->id)
throw new SimpleException('Already in favorites');
$post->link('favoritee')->user = $this->context->user;
R::store($post);
$this->context->transport->success = true;
}
}
/**
@ -419,22 +522,25 @@ class PostController
{
$post = Model_Post::locate($id);
R::preload($post, ['favoritee' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
$finalKey = null;
foreach ($post->ownFavoritee as $key => $fav)
if ($fav->user->id == $this->context->user->id)
$finalKey = $key;
if (InputHelper::get('submit'))
{
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
if ($finalKey === null)
throw new SimpleException('Not in favorites');
$finalKey = null;
foreach ($post->ownFavoritee as $key => $fav)
if ($fav->user->id == $this->context->user->id)
$finalKey = $key;
unset ($post->ownFavoritee[$finalKey]);
R::store($post);
$this->context->transport->success = true;
if ($finalKey === null)
throw new SimpleException('Not in favorites');
unset ($post->ownFavoritee[$finalKey]);
R::store($post);
$this->context->transport->success = true;
}
}
@ -528,25 +634,33 @@ class PostController
/**
* Action that renders the thumbnail of the requested file and sends it to user.
* @route /post/{id}/thumb
* @route /post/{name}/thumb
*/
public function thumbAction($id)
public function thumbAction($name)
{
$this->context->layoutName = 'layout-file';
$post = Model_Post::locate($id);
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
$path = $this->config->main->thumbsPath . DS . $post->name;
$path = $this->config->main->thumbsPath . DS . $name;
if (!file_exists($path))
{
$post = Model_Post::locate($name);
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
$srcPath = $this->config->main->filesPath . DS . $post->name;
$dstPath = $path;
$dstWidth = $this->config->browsing->thumbWidth;
$dstHeight = $this->config->browsing->thumbHeight;
switch ($post->mime_type)
if ($post->type == PostType::Youtube)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
$contents = file_get_contents('http://img.youtube.com/vi/' . $post->orig_name . '/mqdefault.jpg');
file_put_contents($tmpPath, $contents);
if (file_exists($tmpPath))
$srcImage = imagecreatefromjpeg($tmpPath);
}
else switch ($post->mime_type)
{
case 'image/jpeg':
$srcImage = imagecreatefromjpeg($srcPath);
@ -558,10 +672,17 @@ class PostController
$srcImage = imagecreatefromgif($srcPath);
break;
case 'application/x-shockwave-flash':
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb-swf.png';
$srcImage = null;
exec('which swfrender', $tmp, $exitCode);
if ($exitCode == 0)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
exec('swfrender ' . $srcPath . ' -o ' . $tmpPath);
if (file_exists($tmpPath))
$srcImage = imagecreatefrompng($tmpPath);
}
break;
default:
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb.png';
break;
}
@ -583,13 +704,20 @@ class PostController
imagedestroy($srcImage);
imagedestroy($dstImage);
}
else
{
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb.png';
}
if (isset($tmpPath))
unlink($tmpPath);
}
if (!is_readable($path))
throw new SimpleException('Thumbnail file is not readable');
$this->context->transport->cacheDaysToLive = 30;
$this->context->transport->mimeType = 'image/png';
$this->context->transport->fileHash = 'thumb' . $post->file_hash;
$this->context->transport->fileHash = 'thumb' . md5($name . filemtime($path));
$this->context->transport->filePath = $path;
}
@ -667,7 +795,7 @@ class PostController
->innerJoin('tag')
->on('post_tag.tag_id = tag.id')
->where('post_id = post.id')
->and('tag.name = ?')->put($val)
->and('LOWER(tag.name) = LOWER(?)')->put($val)
->close();
continue;
}
@ -693,6 +821,10 @@ class PostController
case 'img':
$type = PostType::Image;
break;
case 'yt':
case 'youtube':
$type = PostType::Youtube;
break;
default:
throw new SimpleException('Unknown type "' . $val . '"');
}

View File

@ -10,15 +10,26 @@ class TagController
$this->context->subTitle = 'tags';
PrivilegesHelper::confirmWithException(Privilege::ListTags);
$suppliedFilter = InputHelper::get('filter');
$dbQuery = R::$f->begin();
$dbQuery->select('tag.name, COUNT(1) AS count');
$dbQuery->select('tag.*, COUNT(1) AS count');
$dbQuery->from('tag');
$dbQuery->innerJoin('post_tag');
$dbQuery->on('tag.id = post_tag.tag_id');
if ($suppliedFilter)
{
if (strlen($suppliedFilter) >= 3)
$suppliedFilter = '%' . $suppliedFilter;
$suppliedFilter .= '%';
$dbQuery->where('LOWER(tag.name) LIKE LOWER(?)')->put($suppliedFilter);
}
$dbQuery->groupBy('tag.id');
$dbQuery->orderBy('LOWER(tag.name)')->asc();
if ($suppliedFilter)
$dbQuery->limit(15);
$rows = $dbQuery->get();
$tags = R::convertToBeans('tag', $rows);
$tags = [];
$tagDistribution = [];
@ -38,22 +49,25 @@ class TagController
public function mergeAction()
{
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
$sourceTag = Model_Tag::locate(InputHelper::get('source-tag'));
$targetTag = Model_Tag::locate(InputHelper::get('target-tag'));
R::preload($sourceTag, 'post');
foreach ($sourceTag->sharedPost as $post)
if (InputHelper::get('submit'))
{
foreach ($post->sharedTag as $key => $postTag)
if ($postTag->id == $sourceTag->id)
unset($post->sharedTag[$key]);
$post->sharedTag []= $targetTag;
R::store($post);
$sourceTag = Model_Tag::locate(InputHelper::get('source-tag'));
$targetTag = Model_Tag::locate(InputHelper::get('target-tag'));
R::preload($sourceTag, 'post');
foreach ($sourceTag->sharedPost as $post)
{
foreach ($post->sharedTag as $key => $postTag)
if ($postTag->id == $sourceTag->id)
unset($post->sharedTag[$key]);
$post->sharedTag []= $targetTag;
R::store($post);
}
R::trash($sourceTag);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
$this->view->context->success = true;
}
R::trash($sourceTag);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
$this->view->context->success = true;
}
/**
@ -62,18 +76,20 @@ class TagController
public function renameAction()
{
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
if (InputHelper::get('submit'))
{
$suppliedSourceTag = InputHelper::get('source-tag');
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
$suppliedSourceTag = InputHelper::get('source-tag');
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
$suppliedTargetTag = InputHelper::get('target-tag');
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
$suppliedTargetTag = InputHelper::get('target-tag');
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
$sourceTag = Model_Tag::locate($suppliedSourceTag);
$sourceTag->name = $suppliedTargetTag;
R::store($sourceTag);
$sourceTag = Model_Tag::locate($suppliedSourceTag);
$sourceTag->name = $suppliedTargetTag;
R::store($sourceTag);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
$this->context->transport->success = true;
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
$this->context->transport->success = true;
}
}
}

View File

@ -53,7 +53,7 @@ class UserController
{
$this->context->stylesheets []= 'user-list.css';
$this->context->stylesheets []= 'paginator.css';
if ($this->config->browsing->endlessScrolling)
if ($this->context->user->hasEnabledEndlessScrolling())
$this->context->scripts []= 'paginator-endless.js';
$page = intval($page);
@ -128,9 +128,12 @@ class UserController
{
$user = Model_User::locate($name);
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
$user->banned = true;
R::store($user);
$this->context->transport->success = true;
if (InputHelper::get('submit'))
{
$user->banned = true;
R::store($user);
$this->context->transport->success = true;
}
}
/**
@ -141,9 +144,12 @@ class UserController
{
$user = Model_User::locate($name);
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
$user->banned = false;
R::store($user);
$this->context->transport->success = true;
if (InputHelper::get('submit'))
{
$user->banned = false;
R::store($user);
$this->context->transport->success = true;
}
}
/**
@ -154,14 +160,16 @@ class UserController
{
$user = Model_User::locate($name);
PrivilegesHelper::confirmWithException(Privilege::AcceptUserRegistration);
$user->staff_confirmed = true;
R::store($user);
$this->context->transport->success = true;
if (InputHelper::get('submit'))
{
$user->staff_confirmed = true;
R::store($user);
$this->context->transport->success = true;
}
}
/**
* @route /user/{name}/delete
* @validate name [^\/]+
@ -176,12 +184,13 @@ class UserController
$this->context->transport->user = $user;
$this->context->transport->tab = 'delete';
$this->context->viewName = 'user-view';
$this->context->stylesheets []= 'tabs.css';
$this->context->stylesheets []= 'user-view.css';
$this->context->subTitle = $name;
$this->context->suppliedCurrentPassword = $suppliedCurrentPassword = InputHelper::get('current-password');
if (InputHelper::get('remove'))
if (InputHelper::get('submit'))
{
if ($this->context->user->id == $user->id)
{
@ -209,6 +218,42 @@ class UserController
/**
* @route /user/{name}/settings
* @validate name [^\/]+
*/
public function settingsAction($name)
{
$user = Model_User::locate($name);
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
PrivilegesHelper::confirmWithException(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($user));
$this->context->handleExceptions = true;
$this->context->transport->user = $user;
$this->context->transport->tab = 'settings';
$this->context->viewName = 'user-view';
$this->context->stylesheets []= 'tabs.css';
$this->context->stylesheets []= 'user-view.css';
$this->context->subTitle = $name;
if (InputHelper::get('submit'))
{
$suppliedSafety = InputHelper::get('safety');
if (!is_array($suppliedSafety))
$suppliedSafety = [];
foreach (PostSafety::getAll() as $safety)
$user->enableSafety($safety, in_array($safety, $suppliedSafety));
$user->enableEndlessScrolling(InputHelper::get('endless-scrolling'));
R::store($user);
$this->context->transport->user = $user;
$this->context->transport->success = true;
}
}
/**
* @route /user/{name}/edit
* @validate name [^\/]+
@ -217,15 +262,14 @@ class UserController
{
try
{
$user = Model_User::locate($name);
$edited = false;
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
$this->context->handleExceptions = true;
$this->context->transport->user = $user;
$this->context->transport->tab = 'edit';
$this->context->viewName = 'user-view';
$this->context->stylesheets []= 'tabs.css';
$this->context->stylesheets []= 'user-view.css';
$this->context->subTitle = $name;
@ -237,51 +281,47 @@ class UserController
$this->context->suppliedAccessRank = $suppliedAccessRank = InputHelper::get('access-rank');
$currentPasswordHash = $user->pass_hash;
if ($suppliedName != '' and $suppliedName != $user->name)
if (InputHelper::get('submit'))
{
PrivilegesHelper::confirmWithException(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($user));
$suppliedName = Model_User::validateUserName($suppliedName);
$user->name = $suppliedName;
$edited = true;
}
if ($suppliedPassword1 != '')
{
PrivilegesHelper::confirmWithException(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($user));
if ($suppliedPassword1 != $suppliedPassword2)
throw new SimpleException('Specified passwords must be the same');
$suppliedPassword = Model_User::validatePassword($suppliedPassword1);
$user->pass_hash = Model_User::hashPassword($suppliedPassword, $user->pass_salt);
$edited = true;
}
if ($suppliedEmail != '' and $suppliedEmail != $user->email_confirmed)
{
PrivilegesHelper::confirmWithException(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($user));
$suppliedEmail = Model_User::validateEmail($suppliedEmail);
if ($this->context->user->id == $user->id)
if ($suppliedName != '' and $suppliedName != $user->name)
{
$user->email_unconfirmed = $suppliedEmail;
if (!empty($user->email_unconfirmed))
self::sendEmailConfirmation($user);
PrivilegesHelper::confirmWithException(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($user));
$suppliedName = Model_User::validateUserName($suppliedName);
$user->name = $suppliedName;
}
else
if ($suppliedPassword1 != '')
{
$user->email_confirmed = $suppliedEmail;
PrivilegesHelper::confirmWithException(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($user));
if ($suppliedPassword1 != $suppliedPassword2)
throw new SimpleException('Specified passwords must be the same');
$suppliedPassword = Model_User::validatePassword($suppliedPassword1);
$user->pass_hash = Model_User::hashPassword($suppliedPassword, $user->pass_salt);
}
$edited = true;
}
if ($suppliedAccessRank != '' and $suppliedAccessRank != $user->access_rank)
{
PrivilegesHelper::confirmWithException(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($user));
$suppliedAccessRank = Model_User::validateAccessRank($suppliedAccessRank);
$user->access_rank = $suppliedAccessRank;
$edited = true;
}
if ($suppliedEmail != '' and $suppliedEmail != $user->email_confirmed)
{
PrivilegesHelper::confirmWithException(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($user));
$suppliedEmail = Model_User::validateEmail($suppliedEmail);
if ($this->context->user->id == $user->id)
{
$user->email_unconfirmed = $suppliedEmail;
if (!empty($user->email_unconfirmed))
self::sendEmailConfirmation($user);
}
else
{
$user->email_confirmed = $suppliedEmail;
}
}
if ($suppliedAccessRank != '' and $suppliedAccessRank != $user->access_rank)
{
PrivilegesHelper::confirmWithException(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($user));
$suppliedAccessRank = Model_User::validateAccessRank($suppliedAccessRank);
$user->access_rank = $suppliedAccessRank;
}
if ($edited)
{
if ($this->context->user->id == $user->id)
{
$suppliedPasswordHash = Model_User::hashPassword($suppliedCurrentPassword, $user->pass_salt);
@ -291,7 +331,6 @@ class UserController
R::store($user);
$this->context->transport->success = true;
}
}
catch (Exception $e)
{
@ -319,11 +358,12 @@ class UserController
$page = 1;
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
$this->context->stylesheets []= 'tabs.css';
$this->context->stylesheets []= 'user-view.css';
$this->context->stylesheets []= 'post-list.css';
$this->context->stylesheets []= 'post-small.css';
$this->context->stylesheets []= 'paginator.css';
if ($this->config->browsing->endlessScrolling)
if ($this->context->user->hasEnabledEndlessScrolling())
$this->context->scripts []= 'paginator-endless.js';
$this->context->subTitle = $name;
@ -405,8 +445,7 @@ class UserController
*/
public function toggleSafetyAction($safety)
{
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
PrivilegesHelper::confirmWithException(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($this->context->user));
if (!in_array($safety, PostSafety::getAll()))
throw new SimpleExcetpion('Invalid safety');
@ -446,7 +485,7 @@ class UserController
$this->context->suppliedPassword2 = $suppliedPassword2;
$this->context->suppliedEmail = $suppliedEmail;
if ($suppliedName !== null)
if (InputHelper::get('submit'))
{
$suppliedName = Model_User::validateUserName($suppliedName);
@ -478,6 +517,7 @@ class UserController
{
$dbUser->access_rank = AccessRank::Admin;
$dbUser->staff_confirmed = true;
$dbUser->email_unconfirmed = null;
$dbUser->email_confirmed = $suppliedEmail;
}
else

View File

@ -63,6 +63,18 @@ class TextHelper
return constant($constantName);
}
private static function stripUnits($string, $base, $suffixes)
{
$suffix = substr($string, -1, 1);
$index = array_search($suffix, $suffixes);
if ($index === false)
return $string;
$number = intval($string);
for ($i = 0; $i < $index; $i ++)
$number *= $base;
return $number;
}
private static function useUnits($number, $base, $suffixes)
{
$suffix = array_shift($suffixes);
@ -89,6 +101,16 @@ class TextHelper
return self::useUnits($number, 1000, ['', 'K', 'M']);
}
public static function stripBytesUnits($string)
{
return self::stripUnits($string, 1024, ['B', 'K', 'M', 'G']);
}
public static function stripDecimalUnits($string)
{
return self::stripUnits($string, 1000, ['', 'K', 'M']);
}
public static function removeUnsafeKeys(&$input, $regex)
{
if (is_array($input))
@ -149,4 +171,22 @@ class TextHelper
$output = preg_replace('{</?p>}', '', $output);
return $output;
}
public static function encrypt($text)
{
$salt = \Chibi\Registry::getConfig()->main->salt;
$alg = MCRYPT_RIJNDAEL_256;
$mode = MCRYPT_MODE_ECB;
$iv = mcrypt_create_iv(mcrypt_get_iv_size($alg, $mode), MCRYPT_RAND);
return trim(base64_encode(mcrypt_encrypt($alg, $salt, $text, $mode, $iv)));
}
public static function decrypt($text)
{
$salt = \Chibi\Registry::getConfig()->main->salt;
$alg = MCRYPT_RIJNDAEL_256;
$mode = MCRYPT_MODE_ECB;
$iv = mcrypt_create_iv(mcrypt_get_iv_size($alg, $mode), MCRYPT_RAND);
return trim(mcrypt_decrypt($alg, $salt, base64_decode($text), $mode, $iv));
}
}

View File

@ -1,11 +1,15 @@
<?php
class Model_Comment extends RedBean_SimpleModel
{
public static function locate($key)
public static function locate($key, $throw = true)
{
$comment = R::findOne('comment', 'id = ?', [$key]);
if (!$comment)
throw new SimpleException('Invalid comment ID "' . $key . '"');
{
if ($throw)
throw new SimpleException('Invalid comment ID "' . $key . '"');
return null;
}
return $comment;
}

View File

@ -1,19 +1,27 @@
<?php
class Model_Post extends RedBean_SimpleModel
{
public static function locate($key, $disallowNumeric = false)
public static function locate($key, $disallowNumeric = false, $throw = true)
{
if (is_numeric($key) and !$disallowNumeric)
{
$post = R::findOne('post', 'id = ?', [$key]);
if (!$post)
throw new SimpleException('Invalid post ID "' . $key . '"');
{
if ($throw)
throw new SimpleException('Invalid post ID "' . $key . '"');
return null;
}
}
else
{
$post = R::findOne('post', 'name = ?', [$key]);
if (!$post)
throw new SimpleException('Invalid post name "' . $key . '"');
{
if ($throw)
throw new SimpleException('Invalid post name "' . $key . '"');
return null;
}
}
return $post;
}

View File

@ -1,12 +1,16 @@
<?php
class Model_Tag extends RedBean_SimpleModel
{
public static function locate($key)
public static function locate($key, $throw = true)
{
$user = R::findOne('tag', 'name = ?', [$key]);
if (!$user)
throw new SimpleException('Invalid tag name "' . $key . '"');
return $user;
$tag = R::findOne('tag', 'LOWER(name) = LOWER(?)', [$key]);
if (!$tag)
{
if ($throw)
throw new SimpleException('Invalid tag name "' . $key . '"');
return null;
}
return $tag;
}
public static function insertOrUpdate($tags)
@ -14,7 +18,7 @@ class Model_Tag extends RedBean_SimpleModel
$dbTags = [];
foreach ($tags as $tag)
{
$dbTag = R::findOne('tag', 'name = ?', [$tag]);
$dbTag = self::locate($tag, false);
if (!$dbTag)
{
$dbTag = R::dispense('tag');
@ -37,7 +41,10 @@ class Model_Tag extends RedBean_SimpleModel
if (strlen($tag) > $maxLength)
throw new SimpleException('Tag must have at most ' . $maxLength . ' characters');
if (!preg_match('/^[a-zA-Z0-9_-]+$/i', $tag))
if (!preg_match('/^[a-zA-Z0-9_.-]+$/i', $tag))
throw new SimpleException('Invalid tag "' . $tag . '"');
if (preg_match('/^\.\.?$/', $tag))
throw new SimpleException('Invalid tag "' . $tag . '"');
return $tag;

View File

@ -1,11 +1,15 @@
<?php
class Model_User extends RedBean_SimpleModel
{
public static function locate($key)
public static function locate($key, $throw = true)
{
$user = R::findOne('user', 'name = ?', [$key]);
if (!$user)
throw new SimpleException('Invalid user name "' . $key . '"');
{
if ($throw)
throw new SimpleException('Invalid user name "' . $key . '"');
return null;
}
return $user;
}
@ -37,29 +41,60 @@ class Model_User extends RedBean_SimpleModel
$this->settings = $settings;
}
public function update()
{
$context = \Chibi\Registry::getContext();
if ($context->user->id == $this->id)
{
$context->user = $this;
unset($_SESSION['user']);
}
}
const SETTING_SAFETY = 1;
const SETTING_ENDLESS_SCROLLING = 2;
public function hasEnabledSafety($safety)
{
return $this->getSetting('safety-' . $safety) !== false;
$all = $this->getSetting(self::SETTING_SAFETY);
if (!$all)
return true;
return $all & PostSafety::toFlag($safety);
}
public function enableSafety($safety, $enabled)
{
$new = $this->getSetting(self::SETTING_SAFETY);
if (!$enabled)
{
$this->setSetting('safety-' . $safety, false);
$anythingEnabled = false;
foreach (PostSafety::getAll() as $safety)
if (self::hasEnabledSafety($safety))
$anythingEnabled = true;
if (!$anythingEnabled)
$this->setSetting('safety-' . PostSafety::Safe, true);
$new &= ~PostSafety::toFlag($safety);
if (!$new)
$new = PostSafety::toFlag(PostSafety::Safe);
}
else
{
$this->setSetting('safety-' . $safety, true);
$new |= PostSafety::toFlag($safety);
}
$this->setSetting(self::SETTING_SAFETY, $new);
}
public function hasEnabledEndlessScrolling()
{
$ret = $this->getSetting(self::SETTING_ENDLESS_SCROLLING);
if ($ret === null)
$ret = \Chibi\Registry::getConfig()->browsing->endlessScrollingDefault;
return $ret;
}
public function enableEndlessScrolling($enabled)
{
$this->setSetting(self::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0);
}
public static function validateUserName($userName)
{
$userName = trim($userName);
@ -131,7 +166,7 @@ class Model_User extends RedBean_SimpleModel
public static function hashPassword($pass, $salt2)
{
$salt1 = \Chibi\Registry::getConfig()->registration->salt;
$salt1 = \Chibi\Registry::getConfig()->main->salt;
return sha1($salt1 . $salt2 . $pass);
}

View File

@ -4,4 +4,9 @@ class PostSafety extends Enum
const Safe = 1;
const Sketchy = 2;
const Unsafe = 3;
public static function toFlag($safety)
{
return pow(2, $safety);
}
}

View File

@ -3,4 +3,5 @@ class PostType extends Enum
{
const Image = 1;
const Flash = 2;
const Youtube = 3;
}

View File

@ -23,6 +23,7 @@ class Privilege extends Enum
const ChangeUserAccessRank = 16;
const ChangeUserEmail = 17;
const ChangeUserName = 18;
const ChangeUserSettings = 28;
const DeleteUser = 19;
const ListComments = 20;

View File

@ -1,12 +1,11 @@
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth aligned" method="post">
<div>
<p>If you don't have an account yet,<br/><a href="<?php echo \Chibi\UrlHelper::route('user', 'registration'); ?>">click here</a> to create a new one.</p>
</div>
<div>
<label class="left" for="name">User name:</label>
<div class="input-wrapper"><input id="name" name="name"/></div>
<div class="input-wrapper"><input type="text" id="name" name="name"/></div>
</div>
<div>
@ -14,10 +13,20 @@
<div class="input-wrapper"><input type="password" id="password" name="password"/></div>
</div>
<div>
<label class="left">&nbsp;</label>
<div class="input-wrapper">
<input type="checkbox" name="remember" value="1"/>
Remember me
</div>
</div>
<?php if (isset($this->context->transport->errorMessage)): ?>
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
<?php endif ?>
<input type="hidden" name="submit" value="1"/>
<div>
<label class="left"></label>
<button type="submit">Log in</button>

View File

@ -4,6 +4,15 @@
<p>If you&rsquo;re not a registered user, you will only see public (Safe) posts. Logging in to your account will enable you to filter content by its rating: Safe, Sketchy, and NSFW.</p>
<p>You can use your keyboard to navigate around the site. There are a few shortcuts:</p>
<ul>
<li>focus search field: <code>[Q]</code></li>
<li>scroll up/down: <code>[W]</code><span class="comma">, </span><code>[S]</code></li>
<li>go to newer/older post or page: <code>[A]</code><span class="comma">, </span><code>[D]</code></li>
<li>edit post: <code>[E]</code></li>
</ul>
<h1>Search syntax</h1>
<ul>
@ -12,10 +21,10 @@
<li>uploaded by David: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'submit:David']) ?>"><code>submit:David</code></a> (note no spaces)</li>
<li>favorited by David: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'fav:David']) ?>"><code>fav:David</code></a></li>
<li>favorited by at least four users: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'favmin:4']) ?>"><code>favmin:4</code></a></li>
<li>exactly from the specified date: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'date:2001']) ?>"><code>date:2001</code></a>, <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'date:2012-09-29']) ?>"><code>date:2012-09-29</code></a> (yyyy-mm-dd format)</li>
<li>exactly from the specified date: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'date:2001']) ?>"><code>date:2001</code></a><span class="comma">, </span><a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'date:2012-09-29']) ?>"><code>date:2012-09-29</code></a> (yyyy-mm-dd format)</li>
<li>from the specified date onwards: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'datemin:2001-01-01']) ?>"><code>datemin:2001-01-01</code></a></li>
<li>up to the specified date: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'datemax:2004-07']) ?>"><code>datemax:2004-07</code></a></li>
<li>by content type: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:img']) ?>"><code>type:img</code></a>, <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:swf']) ?>"><code>type:swf</code></a> (images and flash files, respectively)</li>
<li>by content type: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:img']) ?>"><code>type:img</code></a><span class="comma">, </span><a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:swf']) ?>"><code>type:swf</code></a> (images and flash files, respectively)</li>
</ul>
<p>You can combine tags and negate any of them for interesting results. <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'sea -favmin:8 type:swf submit:Pirate']) ?>"><code>sea -favmin:8 type:swf submit:Pirate</code></a> will show you <strong>flash files</strong> tagged as <strong>sea</strong>, that were <strong>liked by seven people</strong> at most, uploaded by user <strong>Pirate</strong>.</p>
@ -30,7 +39,7 @@
<li>loved by most: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:favs']) ?>"><code>order:favs</code></a></li>
</ul>
<p>As shown with <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:date']) ?>"><code>order:date</code></a>, any of them can be reversed in the same way as negating other tags: by placing a dash before the tag.</p>
<p>As shown with <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => '-order:date']) ?>"><code>-order:date</code></a><span class="comma">, </span>any of them can be reversed in the same way as negating other tags: by placing a dash before the tag.</p>
<h1>Registration</h1>
@ -38,6 +47,16 @@
<p>Oh, and you can delete your account at any time. Posts you uploaded will stay, unless some angry admin removes them.</p>
<h1>Comments</h1>
<p>Registered users can post comments. Comments support <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a>, extended by some handy tags:</p>
<ul>
<li>permalink to post number 426: <a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => 426]) ?>"><code>@426</code></a></li>
<li>link to tag "Dragon_Ball": <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'Dragon_Ball']) ?>"><code>#Dragon_Ball</code></a></li>
<li>mark text as spoiler and hide it: <code class="spoiler">[spoiler]There is no spoon.[/spoiler]</code></li>
</ul>
<h1>Uploads</h1>
<p>After registering, you gain the power to upload files to the service, for everyone else to see. Owners of the site are not responsible for content uploaded by users. You are not allowed to post any form of <a href="http://www.urbandictionary.com/define.php?term=cp">cp</a>. If you possess it, we ask you to leave immediately and never come back.</p>
<p>After registering and activating your account, you gain the power to upload files to the service, for everyone else to see. Owners of the site are not responsible for content uploaded by users. You are not allowed to post any form of <a href="http://www.urbandictionary.com/define.php?term=cp">cp</a>. If you possess it, we ask you to leave immediately and never come back.</p>

View File

@ -1,53 +1,45 @@
<div id="sidebar">
<div id="welcome">
<h1><?php echo $this->config->main->title ?></h1>
<form name="search" action="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>" method="get">
<input type="search" name="query" placeholder="Search&hellip;" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>">
</form>
<p>
<span>serving <?php echo $this->context->transport->postCount ?> posts</span>
<span>powered by <a href="<?php echo SZURU_LINK ?>">szurubooru</a></span>
</p>
</div>
<?php if (!empty($this->context->featuredPost)): ?>
<div id="inner-content">
<div class="header">
Featured image
<div class="header">
Featured image
<ul class="tags">
<?php foreach ($this->context->featuredPost->sharedTag as $tag): ?>
<li>
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>">
<?php echo $tag->name ?>
</a>
</li>
<?php endforeach ?>
</ul>
<span class="favs-comments">
<?php printf('%d&nbsp;fav%s', $x = $this->context->featuredPost->countOwn('favoritee'), $x == 1 ? '' : 's') ?>,&#32;
<?php printf('%d&nbsp;comment%s', $x = $this->context->featuredPost->countOwn('comment'), $x == 1 ? '' : 's') ?>
</span>
<div class="clear"></div>
</div>
<div class="body">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->featuredPost->id]) ?>">
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->featuredPost->name]) ?>" alt="<?php echo $this->context->featuredPost->name ?>"/>
</a>
</div>
<div class="footer">
Featured&#32;
<?php if ($this->context->featuredPostUser): ?>
by <a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->featuredPostUser->name]) ?>"><?php echo $this->context->featuredPostUser->name ?></a>,&#32;
<?php endif ?>
<?php printf('%d day%s', $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)), $x == 1 ? '' : 's') ?> ago
<div class="clear"></div>
</div>
<ul class="tags">
<?php foreach ($this->context->featuredPost->sharedTag as $tag): ?>
<li>
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>">
<?php echo $tag->name ?>
</a>
</li>
<?php endforeach ?>
</ul>
<span class="favs-comments">
<?php printf('%d&nbsp;fav%s', $x = $this->context->featuredPost->countOwn('favoritee'), $x == 1 ? '' : 's') ?>,&#32;
<?php printf('%d&nbsp;comment%s', $x = $this->context->featuredPost->countOwn('comment'), $x == 1 ? '' : 's') ?>
</span>
<div class="clear"></div>
</div>
<div class="body">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->featuredPost->id]) ?>">
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->featuredPost->name]) ?>" alt="<?php echo $this->context->featuredPost->name ?>"/>
</a>
</div>
<div class="footer">
Featured&#32;
<?php if ($this->context->featuredPostUser): ?>
by <a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->featuredPostUser->name]) ?>"><?php echo $this->context->featuredPostUser->name ?></a>,&#32;
<?php endif ?>
<?php printf('%d day%s', $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)), $x == 1 ? '' : 's') ?> ago
<div class="clear"></div>
</div>
<div class="clear"></div>
<?php endif ?>

View File

@ -27,6 +27,9 @@
if (PrivilegesHelper::confirm(Privilege::ListPosts))
$nav []= ['Browse', \Chibi\UrlHelper::route('post', 'list')];
if (PrivilegesHelper::confirm(Privilege::ListPosts))
$nav []= ['Random', \Chibi\UrlHelper::route('post', 'random')];
if (PrivilegesHelper::confirm(Privilege::ListPosts))
$nav []= ['Favorites', \Chibi\UrlHelper::route('post', 'favorites')];
@ -82,7 +85,7 @@
<li class="search">
<form name="search" action="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>" method="get">
<input type="search" name="query" placeholder="Search&hellip;" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>">
<input type="search" name="query" placeholder="Search&hellip;" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/>
</form>
</li>
</ul>

43
src/Views/post-edit.phtml Normal file
View File

@ -0,0 +1,43 @@
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit-post aligned unit">
<h1>edit post</h1>
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
<div class="safety">
<label class="left">Safety:</label>
<?php foreach (PostSafety::getAll() as $safety): ?>
<label>
<input type="radio" name="safety" value="<?php echo $safety ?>" <?php if ($this->context->transport->post->safety == $safety) echo 'checked="checked"' ?>/>
&nbsp;<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
</label>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
<div class="tags">
<label class="left" for="tags">Tags:</label>
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags&hellip;" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->sharedTag)) ?>"/></div>
</div>
<input type="hidden" name="tags-token" id="tags-token" value="<?php echo $this->context->transport->tagsToken ?>"/>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
<div class="source">
<label class="left" for="source">Source:</label>
<div class="input-wrapper"><input type="text" name="source" id="source" value="<?php echo $this->context->transport->post->source ?>"/></div>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
<div class="thumb">
<label class="left" for="thumb">Thumb:</label>
<div class="input-wrapper"><input type="file" name="thumb" id="thumb"/></div>
</div>
<?php endif ?>
<input type="hidden" name="submit" value="1"/>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Submit</button>
</div>
</form>

View File

@ -1,6 +1,6 @@
<div class="post post-type-<?php echo TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post->type)) ?>">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
<img class="thumb" src="<?php echo \Chibi\UrlHelper::route('post', 'thumb', ['id' => $this->context->post->id]) ?>" alt="@<?php echo $this->context->post->id ?>"/>
<img class="thumb" src="<?php echo \Chibi\UrlHelper::route('post', 'thumb', ['name' => $this->context->post->name]) ?>" alt="@<?php echo $this->context->post->id ?>"/>
</a>
<div class="info-bar">
<i class="icon-comments"></i> <span><?php echo $this->context->post->countOwn('comment') ?></span>

View File

@ -14,11 +14,40 @@
<div id="inner-content">
<div id="upload-step1">
<input type=file multiple style="display: none"/>
<div id="file-handler-wrapper">
<div id="file-handler">
Drop files here!<br>
Or just click on this box.
<div class="tabs">
<nav>
<ul>
<li class="selected file">
<a href="#">
Upload from file
</a>
</li>
<li class="url">
<a href="#">
Upload from URL
</a>
</li>
</ul>
</nav>
</div>
<div class="tab file">
<input type=file multiple style="display: none"/>
<div id="file-handler-wrapper">
<div id="file-handler">
Drop files here!<br>
Or just click on this box.
</div>
</div>
</div>
<div class="tab url">
<div id="url-handler-wrapper">
<div id="url-handler">
<div class="input-wrapper"><textarea placeholder="Paste some URLs here, one per line." name="urls"></textarea></div>
</div>
<button type="submit">Add</button>
</div>
</div>
@ -26,6 +55,8 @@
</div>
<div id="upload-step2" data-redirect-url="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">
<hr>
<div class="posts">
</div>
@ -62,9 +93,14 @@
<div class="safety">
<label class="left">Safety:</label>
<label><input type="radio" name="safety" value="<?php echo PostSafety::Safe ?>" checked="checked"/> Safe for work</label>
<label><input type="radio" name="safety" value="<?php echo PostSafety::Sketchy ?>"/> Sketchy</label>
<label><input type="radio" name="safety" value="<?php echo PostSafety::Unsafe ?>"/> Not safe for work</label>
<?php $checked = false ?>
<?php foreach (PostSafety::getAll() as $safety): ?>
<label>
<input type="radio" name="safety" value="<?php echo $safety ?>"<?php if (!$checked) echo ' checked="checked"' ?>/>
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
<?php $checked = true ?>
</label>
<?php endforeach ?>
</div>
<div class="tags">
@ -76,14 +112,11 @@
<label class="left">Source:</label>
<div class="input-wrapper"><input type="text" name="source" placeholder="where did you get this from? (optional)"/></div>
</div>
<input type="hidden" name="submit" value="1"/>
</form>
</div>
</div>
<div id="upload-no-posts">
<p class="alert alert-warning">Well, that&rsquo;s disappointing&hellip;</p>
<p><a href="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">Back to post list</a> or <a href="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>">try uploading again</a>.</p>
</div>
</div>
<?php endif ?>

View File

@ -29,7 +29,7 @@
<h1>tags (<?php echo count($this->context->transport->post->sharedTag) ?>)</h1>
<ul>
<?php foreach ($this->context->transport->post->sharedTag as $tag): ?>
<li>
<li title="<?php echo $tag->name ?>">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>">
<?php echo $tag->name ?>
</a>
@ -44,18 +44,12 @@
<div class="unit details">
<h1>details</h1>
<div class="key-value safety">
<span class="key">Safety:</span>
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(PostSafety::toString($this->context->transport->post->safety)) ?>">
<?php echo $val ?>
</span>
</div>
<div class="key-value uploader">
<span class="key">Uploader:</span>
<?php if ($this->context->transport->post->uploader): ?>
<span class="value" title="<?php echo $val = $this->context->transport->post->uploader->name ?>">
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->transport->post->uploader->name]) ?>">
<img src="<?php echo htmlentities($this->context->transport->post->uploader->getAvatarUrl(16)) ?>" alt="<?php echo $this->context->transport->post->uploader->name ?>"/>
<?php echo $val ?>
</a>
</span>
@ -66,6 +60,13 @@
<?php endif ?>
</div>
<div class="key-value safety">
<span class="key">Safety:</span>
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(PostSafety::toString($this->context->transport->post->safety)) ?>">
<?php echo $val ?>
</span>
</div>
<div class="key-value date">
<span class="key">Date:</span>
<span class="value" title="<?php echo $val = date('Y-m-d H:i', $this->context->transport->post->upload_date) ?>">
@ -91,17 +92,21 @@
</span>
</div>
<div class="permalink">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" title="Download">
<i class="icon-dl"></i>
<span class="ext">
<?php echo strtoupper(substr($this->context->transport->post->orig_name, strrpos($this->context->transport->post->orig_name, '.') + 1)) ?>
</span>
<span class="size">
<?php echo TextHelper::useBytesUnits($this->context->transport->post->file_size) ?>
</span>
</a>
</div>
<?php if ($this->context->transport->post->type != PostType::Youtube): ?>
<div class="permalink">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" title="Download">
<i class="icon-dl"></i>
<span class="ext">
<?php $mimes = ['image/jpeg' => 'JPG', 'image/gif' => 'GIF', 'image/png' => 'PNG', 'application/x-shockwave-flash' => 'SWF'] ?>
<?php $mime = $this->context->transport->post->mimeType ?>
<?php echo isset($mimes[$mime]) ? $mimes[$mime] : 'unknown' ?>
</span>
<span class="size">
<?php echo TextHelper::useBytesUnits($this->context->transport->post->file_size) ?>
</span>
</a>
</div>
<?php endif ?>
</div>
<div class="unit favorites">
@ -208,55 +213,21 @@
<div id="inner-content">
<div class="post-wrapper post-type-<?php echo strtolower(PostType::toString($this->context->transport->post->type)) ?>">
<?php if ($this->context->transport->post->type == PostType::Image): ?>
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" alt="<?php echo $this->context->transport->post->name ?>"/>
<?php elseif ($this->context->transport->post->type == PostType::Flash): ?>
<embed width="<?php echo $this->context->transport->post->image_width ?>" height="<?php echo $this->context->transport->post->image_height ?>" type="application/x-shockwave-flash" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>"/>
<?php endif ?>
<?php switch ($this->context->transport->post->type):
case PostType::Image: ?>
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" alt="<?php echo $this->context->transport->post->name ?>"/>
<?php break ?>
<?php case PostType::Flash: ?>
<embed width="<?php echo $this->context->transport->post->image_width ?>" height="<?php echo $this->context->transport->post->image_height ?>" type="application/x-shockwave-flash" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>"/>
<?php break ?>
<?php case PostType::Youtube: ?>
<iframe width="800" height="600" src="//www.youtube.com/embed/<?php echo $this->context->transport->post->orig_name ?>" frameborder="0" allowfullscreen></iframe>
<?php break ?>
<?php endswitch ?>
</div>
<?php if ($canEditAnything): ?>
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit-post aligned unit">
<h1>edit post</h1>
<?php if ($editPostPrivileges[Privilege::EditPostSafety]): ?>
<div class="safety">
<label class="left">Safety:</label>
<?php foreach (PostSafety::getAll() as $safety): ?>
<label>
<input type="radio" name="safety" value="<?php echo $safety ?>" <?php if ($this->context->transport->post->safety == $safety) echo 'checked="checked"' ?>/>
&nbsp;<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
</label>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($editPostPrivileges[Privilege::EditPostTags]): ?>
<div class="tags">
<label class="left" for="tags">Tags:</label>
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags&hellip;" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->sharedTag)) ?>"/></div>
</div>
<input type="hidden" name="tags-token" id="tags-token" value="<?php echo $this->context->transport->tagsToken ?>"/>
<?php endif ?>
<?php if ($editPostPrivileges[Privilege::EditPostSource]): ?>
<div class="source">
<label class="left" for="source">Source:</label>
<div class="input-wrapper"><input type="text" name="source" id="suorce" value="<?php echo $this->context->transport->post->source ?>"/></div>
</div>
<?php endif ?>
<?php if ($editPostPrivileges[Privilege::EditPostThumb]): ?>
<div class="thumb">
<label class="left" for="thumb">Thumb:</label>
<div class="input-wrapper"><input type="file" name="thumb" id="thumb"/></div>
</div>
<?php endif ?>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Submit</button>
</div>
</form>
<?php $this->renderFile('post-edit') ?>
<?php endif ?>
<div class="comments unit">

View File

@ -1,8 +1,9 @@
<?php $max = max($this->context->transport->tagDistribution) ?>
<div class="tags">
<ul>
<?php foreach ($this->context->transport->tagDistribution as $tagName => $count): ?>
<li class="tag">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tagName]) ?>">
<li class="tag" title="<?php echo $tagName ?> (<?php echo $count ?>)">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tagName]) ?>" style="opacity: <?php printf('%.02f', 0.25 + 0.75 * log($count) / log($max)) ?>">
<?php echo $tagName . ' (' . $count . ')' ?>
</a>
</li>
@ -24,6 +25,8 @@
<div class="input-wrapper"><input type="text" name="target-tag" id="merge-target-tag"></div>
</div>
<input type="hidden" name="submit" value="1"/>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Merge!</button>
@ -46,6 +49,8 @@
<div class="input-wrapper"><input type="text" name="target-tag" id="rename-target-tag"></div>
</div>
<input type="hidden" name="submit" value="1"/>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Rename!</button>

View File

@ -0,0 +1,21 @@
<form action="<?php echo \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name]) ?>" method="post" class="delete aligned" autocomplete="off" data-confirm-text="Are you sure you want to delete your account?">
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
<div class="current-password">
<label class="left" for="current-password">Current password:</label>
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
</div>
<?php endif ?>
<input type="hidden" name="submit" value="1"/>
<?php if ($this->context->transport->success === true): ?>
<p class="alert alert-success">Account settings updated!</p>
<?php elseif (isset($this->context->transport->errorMessage)): ?>
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
<?php endif ?>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Delete account</button>
</div>
</form>

65
src/Views/user-edit.phtml Normal file
View File

@ -0,0 +1,65 @@
<form action="<?php echo \Chibi\UrlHelper::route('user', 'edit', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit aligned" autocomplete="off">
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
<div class="current-password">
<label class="left" for="current-password">Current password:</label>
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
</div>
<hr>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="nickname">
<label class="left" for="name">Name:</label>
<div class="input-wrapper"><input type="text" name="name" id="name" placeholder="New name&hellip;" value="<?php echo $this->context->suppliedName ?>"/></div>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="email">
<label class="left" for="name">E-mail:</label>
<div class="input-wrapper"><input type="text" name="email" id="email" placeholder="New e-mail&hellip;" value="<?php echo $this->context->suppliedEmail ?>"/></div>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="password1">
<label class="left" for="password1">New password:</label>
<div class="input-wrapper"><input type="password" name="password1" id="password1" placeholder="New password&hellip;" value="<?php echo $this->context->suppliedPassword1 ?>"/></div>
</div>
<div class="password2">
<label class="left" for="password2"></label>
<div class="input-wrapper"><input type="password" name="password2" id="password2" placeholder="New password&hellip; (repeat)" value="<?php echo $this->context->suppliedPassword2 ?>"/></div>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="access-rank">
<label class="left" for="access-rank">Access rank:</label>
<div class="input-wrapper"><select name="access-rank" id="access-rank">
<?php foreach (AccessRank::getAll() as $rank): ?>
<?php if ($rank == AccessRank::Nobody) continue ?>
<?php if (($this->context->suppliedAccessRank != '' and $rank == $this->context->suppliedAccessRank) or ($this->context->suppliedAccessRank == '' and $rank == $this->context->transport->user->access_rank)): ?>
<option value="<?php echo $rank ?>" selected="selected">
<?php else: ?>
<option value="<?php echo $rank ?>">
<?php endif ?>
<?php echo TextHelper::camelCaseToHumanCase(AccessRank::toString($rank)) ?>
</option>
<?php endforeach ?>
</select></div>
</div>
<?php endif ?>
<input type="hidden" name="submit" value="1"/>
<?php if ($this->context->transport->success === true): ?>
<p class="alert alert-success">Account settings updated! <?php if (!empty($this->context->mailSent)) echo 'You will be sent new e-mail address confirmation message soon.' ?></p>
<?php elseif (isset($this->context->transport->errorMessage)): ?>
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
<?php endif ?>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Submit</button>
</div>
</form>

View File

@ -16,7 +16,7 @@
<div>
<label class="left" for="name">User name:</label>
<div class="input-wrapper"><input id="name" name="name" value="<?php echo $this->context->suppliedName ?>" placeholder="e.g. darth_vader" autocomplete="off"/></div>
<div class="input-wrapper"><input type="text" id="name" name="name" value="<?php echo $this->context->suppliedName ?>" placeholder="e.g. darth_vader" autocomplete="off"/></div>
</div>
<div>
@ -31,7 +31,7 @@
<div>
<label class="left" for="email">E-mail address:</label>
<div class="input-wrapper"><input id="email" name="email" value="<?php echo $this->context->suppliedEmail ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/></div>
<div class="input-wrapper"><input type="text" id="email" name="email" value="<?php echo $this->context->suppliedEmail ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/></div>
</div>
<div>
@ -44,6 +44,8 @@
</div>
<?php endif ?>
<input type="hidden" name="submit" value="1"/>
<div>
<label class="left"></label>
<button type="submit">Register</button>

View File

@ -0,0 +1,37 @@
<form action="<?php echo \Chibi\UrlHelper::route('user', 'settings', ['name' => $this->context->transport->user->name]) ?>" method="post" class="settings aligned">
<div class="safety">
<label class="left" for="name">Safety:</label>
<div class="input-wrapper">
<?php foreach (PostSafety::getAll() as $safety): ?>
<?php if (PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety))): ?>
<label><input type="checkbox" name="safety[]" value="<?php echo $safety ?>"<?php if ($this->context->transport->user->hasEnabledSafety($safety)) echo ' checked="checked"' ?>/>
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?></label>
<?php endif ?>
<?php endforeach ?>
</div>
</div>
<div class="endless-scrolling">
<label class="left" for="name">Endless scrolling:</label>
<div class="input-wrapper">
<label>
<input type="checkbox" name="endless-scrolling" <?php if ($this->context->transport->user->hasEnabledEndlessScrolling()) echo ' checked="checked"' ?>/>
Enabled
</label>
</div>
</div>
<input type="hidden" name="submit" value="1"/>
<?php if ($this->context->transport->success === true): ?>
<p class="alert alert-success">Browsing settings updated!</p>
<?php elseif (isset($this->context->transport->errorMessage)): ?>
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
<?php endif ?>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Update settings</button>
</div>
</form>

View File

@ -19,7 +19,7 @@
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(AccessRank::toString($this->context->transport->user->access_rank)) ?>"><?php echo $val ?></span>
</div>
<?php if (PrivilegesHelper::confirm(Privilege::ViewUserEmail)): ?>
<?php if (PrivilegesHelper::confirm(Privilege::ViewUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="key-value email">
<span class="key">E-mail:</span>
<span class="value" title="<?php echo $val = ($this->context->transport->user->email_unconfirmed ? '(unconfirmed) ' . $this->context->transport->user->email_unconfirmed : $this->context->transport->user->email_confirmed ?: 'none specified') ?>"><?php echo $val ?></span>
@ -121,6 +121,18 @@
</a>
</li>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<?php if ($this->context->transport->tab == 'settings'): ?>
<li class="selected settings">
<?php else: ?>
<li class="settings">
<?php endif ?>
<a href="<?php echo \Chibi\UrlHelper::route('user', 'settings', ['name' => $this->context->transport->user->name]) ?>">
Browsing settings
</a>
</li>
<?php endif ?>
<?php if ($canModifyAnything): ?>
<?php if ($this->context->transport->tab == 'edit'): ?>
<li class="selected edit">
@ -152,95 +164,12 @@
<?php $this->renderFile('post-list') ?>
<?php endif ?>
<?php if ($this->context->transport->tab == 'edit'): ?>
<form action="<?php echo \Chibi\UrlHelper::route('user', 'edit', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit aligned" autocomplete="off">
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
<div class="current-password">
<label class="left" for="current-password">Current password:</label>
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
</div>
<hr>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="nickname">
<label class="left" for="name">Name:</label>
<div class="input-wrapper"><input type="text" name="name" id="name" placeholder="New name&hellip;" value="<?php echo $this->context->suppliedName ?>"/></div>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="email">
<label class="left" for="name">E-mail:</label>
<div class="input-wrapper"><input type="text" name="email" id="email" placeholder="New e-mail&hellip;" value="<?php echo $this->context->suppliedEmail ?>"/></div>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="password1">
<label class="left" for="password1">New password:</label>
<div class="input-wrapper"><input type="password" name="password1" id="password1" placeholder="New password&hellip;" value="<?php echo $this->context->suppliedPassword1 ?>"/></div>
</div>
<div class="password2">
<label class="left" for="password2"></label>
<div class="input-wrapper"><input type="password" name="password2" id="password2" placeholder="New password&hellip; (repeat)" value="<?php echo $this->context->suppliedPassword2 ?>"/></div>
</div>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
<div class="access-rank">
<label class="left" for="access-rank">Access rank:</label>
<div class="input-wrapper"><select name="access-rank" id="access-rank">
<?php foreach (AccessRank::getAll() as $rank): ?>
<?php if ($rank == AccessRank::Nobody) continue ?>
<?php if (($this->context->suppliedAccessRank != '' and $rank == $this->context->suppliedAccessRank) or ($this->context->suppliedAccessRank == '' and $rank == $this->context->transport->user->access_rank)): ?>
<option value="<?php echo $rank ?>" selected="selected">
<?php else: ?>
<option value="<?php echo $rank ?>">
<?php endif ?>
<?php echo TextHelper::camelCaseToHumanCase(AccessRank::toString($rank)) ?>
</option>
<?php endforeach ?>
</select></div>
</div>
<?php endif ?>
<?php if ($this->context->transport->success === true): ?>
<p class="alert alert-success">Account settings updated! <?php if (!empty($this->context->mailSent)) echo 'You will be sent new e-mail address confirmation message soon.' ?></p>
<?php elseif (isset($this->context->transport->errorMessage)): ?>
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
<?php endif ?>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Submit</button>
</div>
</form>
<?php if ($this->context->transport->tab == 'settings'): ?>
<?php $this->renderFile('user-settings') ?>
<?php elseif ($this->context->transport->tab == 'edit'): ?>
<?php $this->renderFile('user-edit') ?>
<?php elseif ($this->context->transport->tab == 'delete'): ?>
<form action="<?php echo \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit aligned" autocomplete="off" data-confirm-text="Are you sure you want to delete your account?">
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
<div class="current-password">
<label class="left" for="current-password">Current password:</label>
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
</div>
<?php endif ?>
<input type="hidden" name="remove" value="1"/>
<?php if ($this->context->transport->success === true): ?>
<p class="alert alert-success">Account settings updated!</p>
<?php elseif (isset($this->context->transport->errorMessage)): ?>
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
<?php endif ?>
<div>
<label class="left">&nbsp;</label>
<button type="submit">Delete account</button>
</div>
</form>
<?php $this->renderFile('user-delete') ?>
<?php endif ?>
</div>

View File

@ -1,5 +1,5 @@
<?php
define('SZURU_VERSION', '0.1.0');
define('SZURU_VERSION', '0.2.0');
define('SZURU_LINK', 'http://github.com/rr-/szurubooru');
function trueStartTime()