mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
51dbc65754 | |||
b8fedc1297 | |||
bb0e844e4e | |||
09b5a38c95 | |||
b093a090eb | |||
e1c8139373 | |||
101864459d | |||
f7a0b7b440 | |||
b3f15dc049 | |||
be919603e3 | |||
ac506e8c95 | |||
8d5b82287a | |||
f32c045349 | |||
579df65c21 | |||
c4faa3bf85 | |||
c3b2c68add | |||
fe99f97287 | |||
bd05123cfc | |||
9110a27167 | |||
fd99821bd7 | |||
ad8f2a8038 | |||
86c811b0e7 | |||
ea4c7fac6e | |||
1714e9e665 | |||
157572d9ca | |||
19eea1e5b6 | |||
b7084d61ae | |||
36caef3831 | |||
e0c4c28e70 | |||
96d994eeea | |||
bc43883339 | |||
cf1b5837a7 | |||
f119ab724a | |||
3130a66ad3 | |||
e2e9d9bf13 | |||
9e6716021a | |||
49b91b7f55 | |||
2aaafcd0de | |||
24f5024db3 | |||
e346a8e57c | |||
558f8f42c8 | |||
2f8d43cb4b | |||
c4d5263422 | |||
3f3024d6ac | |||
b55a8f1dce | |||
f726690ea3 | |||
0d360d525e | |||
bddf04ea78 | |||
d92d49d60d | |||
35146e9587 |
@ -20,6 +20,7 @@ thumbHeight=150
|
||||
thumbStyle=outside
|
||||
endlessScrollingDefault=1
|
||||
maxSearchTokens=4
|
||||
maxRelatedPosts=50
|
||||
|
||||
[comments]
|
||||
minLength = 5
|
||||
@ -65,6 +66,8 @@ editPostSafety.all=moderator
|
||||
editPostTags=registered
|
||||
editPostThumb=moderator
|
||||
editPostSource=moderator
|
||||
editPostRelations.own=registered
|
||||
editPostRelations.all=moderator
|
||||
hidePost.own=moderator
|
||||
hidePost.all=moderator
|
||||
deletePost.own=moderator
|
||||
@ -97,3 +100,4 @@ deleteComment.all=moderator
|
||||
listTags=anonymous
|
||||
mergeTags=moderator
|
||||
renameTags=moderator
|
||||
massTag=moderator
|
||||
|
Submodule lib/chibi-core updated: 6f606d1075...0ec5cbda4b
@ -108,6 +108,7 @@ body {
|
||||
#top-nav li.safety span {
|
||||
display: none;
|
||||
}
|
||||
#top-nav li.safety a:focus,
|
||||
#top-nav li.safety a:hover { opacity: .7; }
|
||||
#top-nav li.safety a.inactive { opacity: 1; }
|
||||
#top-nav li.safety .safety-safe .enabled { background: #cfe6c2; background: linear-gradient(to bottom, #CFE6C2 0%, #80C670 100%); }
|
||||
|
@ -31,3 +31,9 @@
|
||||
.paginator li.disabled a {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.paginator li a:focus,
|
||||
.paginator li a:hover {
|
||||
border: 1px solid firebrick;
|
||||
background: pink;
|
||||
}
|
||||
|
@ -7,3 +7,25 @@
|
||||
.posts {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
.small-screen .form-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
form.aligned {
|
||||
margin: 0 auto;
|
||||
width: 24em;
|
||||
text-align: left;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
}
|
||||
form h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li.mass-tag {
|
||||
float: right;
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
.post {
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0.25em 0.25em #eee;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.post .link {
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0.25em 0.25em #eee;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.post-type-youtube:after,
|
||||
.post-type-flash:after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
right: 1px; /* border */
|
||||
top: 1px; /* border */
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
content: ' ';
|
||||
@ -29,13 +33,41 @@
|
||||
}
|
||||
|
||||
|
||||
.post:focus,
|
||||
.post:hover {
|
||||
.post .toggle-tag {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
background: whitesmoke;
|
||||
opacity: .5;
|
||||
border: 1px solid black;
|
||||
margin: 60px 25px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.post .toggle-tag:focus,
|
||||
.post .toggle-tag:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.post.taggable.tagged .toggle-tag {
|
||||
background-color: #0f0;
|
||||
color: black;
|
||||
}
|
||||
.post.taggable:not(.tagged) .toggle-tag {
|
||||
background-color: #f00;
|
||||
color: white;
|
||||
}
|
||||
.post .link {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
.post .link:focus,
|
||||
.post .link:hover {
|
||||
border: 1px solid firebrick;
|
||||
box-shadow: 0.25em 0.25em pink;
|
||||
}
|
||||
.post:focus img.thumb,
|
||||
.post:hover img.thumb {
|
||||
.post .link:focus img.thumb,
|
||||
.post .link:hover img.thumb {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
@ -53,13 +85,16 @@
|
||||
.post .info-bar {
|
||||
display: none;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
border-top: 1px solid firebrick;
|
||||
background: rgba(255, 128, 128, 0.75);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-indeX: 3;
|
||||
left: 1px; /* border */
|
||||
right: 1px; /* border */
|
||||
bottom: 1px; /* border */
|
||||
}
|
||||
.post:hover .info-bar {
|
||||
.post .link:focus .info-bar,
|
||||
.post .link:hover .info-bar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@ -70,14 +105,15 @@
|
||||
.post .icon-favs {
|
||||
background-position: -43px -1px;
|
||||
}
|
||||
.post [class^='icon-'] {
|
||||
.post .link [class^='icon-'] {
|
||||
opacity: .75;
|
||||
background-color: transparent;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.post span {
|
||||
.post .link span {
|
||||
vertical-align: top;
|
||||
font-size: small;
|
||||
line-height: 20px;
|
||||
|
@ -1,5 +1,5 @@
|
||||
#sidebar {
|
||||
width: 200px;
|
||||
width: 224px;
|
||||
line-height: 1.33em;
|
||||
font-size: 90%;
|
||||
}
|
||||
@ -16,6 +16,7 @@ embed {
|
||||
background: url('../img/bk-swf.png') lemonchiffon;
|
||||
}
|
||||
|
||||
#sidebar .relations ul,
|
||||
#sidebar .tags ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
@ -49,8 +50,22 @@ embed {
|
||||
#sidebar .uploader img {
|
||||
vertical-align: middle;
|
||||
margin: 0 0.5em 0 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=16');
|
||||
}
|
||||
|
||||
#sidebar .safety-safe {
|
||||
color: #43aa43;
|
||||
}
|
||||
#sidebar .safety-sketchy {
|
||||
color: #d4a627;
|
||||
}
|
||||
#sidebar .safety-unsafe {
|
||||
color: #df4b0d;
|
||||
}
|
||||
|
||||
|
||||
i.icon-prev {
|
||||
background-position: -12px -1px;
|
||||
}
|
||||
|
@ -24,3 +24,7 @@
|
||||
border-bottom: 1px solid white;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.tabs li a:focus {
|
||||
color: firebrick;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
.form-wrapper {
|
||||
width: 50%;
|
||||
max-width: 24em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
@ -29,14 +30,11 @@
|
||||
}
|
||||
form.aligned {
|
||||
text-align: left;
|
||||
margin: 2em auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
}
|
||||
form.aligned input {
|
||||
width: 24em;
|
||||
}
|
||||
form h1 {
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
@ -40,11 +40,11 @@
|
||||
}
|
||||
|
||||
.post .thumbnail {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
background-image: url('../img/thumb.png');
|
||||
background-size: 100px 100px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
line-height: 150px;
|
||||
background-image: url('../img/thumb.jpg');
|
||||
background-size: 150px 150px;
|
||||
border: 1px solid black;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
@ -126,18 +126,19 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 50%;
|
||||
white-space: pre;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 33px;
|
||||
}
|
||||
|
||||
.safety-sfw {
|
||||
color: #63ca63;
|
||||
.safety-safe {
|
||||
color: #43aa43;
|
||||
}
|
||||
.safety-sketchy {
|
||||
color: #f4c657;
|
||||
color: #d4a627;
|
||||
}
|
||||
.safety-nsfw {
|
||||
.safety-unsafe {
|
||||
color: #df4b0d;
|
||||
}
|
||||
|
||||
|
BIN
public_html/media/img/thumb.jpg
Normal file
BIN
public_html/media/img/thumb.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 933 B |
@ -1,3 +1,4 @@
|
||||
//core functionalities, prototypes
|
||||
$.fn.hasAttr = function(name)
|
||||
{
|
||||
return this.attr(name) !== undefined;
|
||||
@ -20,6 +21,9 @@ if ($.when.all === undefined)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//safety trigger
|
||||
$(function()
|
||||
{
|
||||
$('.safety a').click(function(e)
|
||||
@ -44,7 +48,15 @@ $(function()
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
//basic event listeners
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', function()
|
||||
{
|
||||
function confirmEvent(e)
|
||||
{
|
||||
if (!confirm($(this).attr('data-confirm-text')))
|
||||
@ -103,6 +115,9 @@ $(function()
|
||||
});
|
||||
});
|
||||
});
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
|
||||
|
||||
|
||||
//modify DOM on small viewports
|
||||
@ -131,6 +146,7 @@ $(function()
|
||||
});
|
||||
|
||||
|
||||
|
||||
//autocomplete
|
||||
function split(val)
|
||||
{
|
||||
@ -144,7 +160,9 @@ function extractLast(term)
|
||||
|
||||
$(function()
|
||||
{
|
||||
var searchInput = $('#top-nav .search input');
|
||||
$('[data-autocomplete-url]').each(function()
|
||||
{
|
||||
var searchInput = $(this);
|
||||
searchInput
|
||||
// don't navigate away from the field on tab when selecting an item
|
||||
.bind("keydown", function(event)
|
||||
@ -158,9 +176,10 @@ $(function()
|
||||
source: function(request, response)
|
||||
{
|
||||
var term = extractLast(request.term);
|
||||
if (term != '')
|
||||
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term}, function(data)
|
||||
{
|
||||
response($.map(data.tags, function(tag) { return { label: tag, value: tag }; }));
|
||||
response($.map(data.tags, function(tag) { return { label: tag.name, value: tag.name }; }));
|
||||
});
|
||||
},
|
||||
focus: function()
|
||||
@ -179,6 +198,7 @@ $(function()
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getTagItOptions()
|
||||
{
|
||||
@ -190,13 +210,18 @@ function getTagItOptions()
|
||||
function(request, response)
|
||||
{
|
||||
var term = request.term.toLowerCase();
|
||||
var results = $.grep(this.options.availableTags, function(a)
|
||||
var tags = $.map(this.options.availableTags, function(a)
|
||||
{
|
||||
return a.name;
|
||||
});
|
||||
var results = $.grep(tags, function(a)
|
||||
{
|
||||
if (term.length < 3)
|
||||
return a.toLowerCase().indexOf(term) == 0;
|
||||
else
|
||||
return a.toLowerCase().indexOf(term) != -1;
|
||||
});
|
||||
results = results.slice(0, 15);
|
||||
if (!this.options.allowDuplicates)
|
||||
results = this._subtractArray(results, this.assignedTags());
|
||||
response(results);
|
||||
@ -205,11 +230,14 @@ function getTagItOptions()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
//hotkeys
|
||||
$(function()
|
||||
{
|
||||
Mousetrap.bind('q', function() { $('#top-nav input').focus(); return false; });
|
||||
Mousetrap.bind('q', function() { $('#top-nav input').focus(); return false; }, 'keyup');
|
||||
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; });
|
||||
Mousetrap.bind('a', function() { var url = $('.paginator:visible .prev:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('d', function() { var url = $('.paginator:visible .next:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
});
|
||||
|
37
public_html/media/js/mass-tag.js
Normal file
37
public_html/media/js/mass-tag.js
Normal file
@ -0,0 +1,37 @@
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', function()
|
||||
{
|
||||
$('.post a.toggle-tag').click(function(e)
|
||||
{
|
||||
if(e.isPropagationStopped())
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
return;
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url, {submit: 1}, function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
aDom.parents('.post').toggleClass('tagged');
|
||||
aDom.text(aDom.parents('.post').hasClass('tagged')
|
||||
? aDom.attr('data-text-tagged')
|
||||
: aDom.attr('data-text-untagged'));
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -18,6 +18,7 @@ function scrolled()
|
||||
var nextPage = dom.find('.paginator .next:not(.disabled) a').attr('href');
|
||||
$(document).data('page-next', nextPage);
|
||||
$('.paginator-content').append($(response).find('.paginator-content').children().css({opacity: 0}).animate({opacity: 1}, 'slow'));
|
||||
$('body').trigger('dom-update');
|
||||
scrolled();
|
||||
});
|
||||
}
|
||||
|
@ -12,17 +12,20 @@ $(function()
|
||||
var tags = [];
|
||||
$.getJSON('/tags?json', function(data)
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
var formDom = $('form.edit-post');
|
||||
tags = data['tags'];
|
||||
|
||||
if (!$(formDom).is(':visible'))
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
formDom.find('input[type=text]:visible:eq(0)').focus();
|
||||
$('html, body').animate({ scrollTop: $(formDom).offset().top + 'px' }, 'fast');
|
||||
});
|
||||
});
|
||||
@ -121,7 +124,7 @@ $(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; });
|
||||
Mousetrap.bind('a', function() { var url = $('#sidebar .left a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('d', function() { var url = $('#sidebar .right a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('e', function() { $('li.edit a').trigger('click'); return false; }, 'keyup');
|
||||
});
|
||||
|
@ -89,18 +89,12 @@ $(function()
|
||||
|
||||
var postDom = posts.first();
|
||||
var url = postDom.find('form').attr('action') + '?json';
|
||||
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', sourceFile);
|
||||
fd.append('url', sourceUrl);
|
||||
fd.append('tags', tags);
|
||||
fd.append('safety', safety);
|
||||
fd.append('source', source);
|
||||
fd.append('submit', 1);
|
||||
console.log(postDom.find('form').get(0));
|
||||
var fd = new FormData(postDom.find('form').get(0));
|
||||
|
||||
fd.append('file', postDom.data('file'));
|
||||
fd.append('url', postDom.data('url'));
|
||||
|
||||
|
||||
var ajaxData =
|
||||
{
|
||||
|
28
scripts/find-posts.php
Normal file
28
scripts/find-posts.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
\Chibi\Registry::setConfig(configFactory());
|
||||
|
||||
function usage()
|
||||
{
|
||||
echo 'Usage: ' . basename(__FILE__);
|
||||
echo ' QUERY' . PHP_EOL;
|
||||
return true;
|
||||
}
|
||||
|
||||
array_shift($argv);
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
|
||||
$filesPath = rtrim(\Chibi\Registry::getConfig()->main->filesPath, DS);
|
||||
$query = array_shift($argv);
|
||||
$posts = Model_Post::getEntities($query, null, null);
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
echo implode("\t",
|
||||
[
|
||||
$post->id,
|
||||
$post->name,
|
||||
$filesPath . DS . $post->name,
|
||||
$post->mimeType,
|
||||
]). PHP_EOL;
|
||||
}
|
14
scripts/remove-letterbox.sh
Normal file
14
scripts/remove-letterbox.sh
Normal file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
process () {
|
||||
x="$1";
|
||||
echo "$x";
|
||||
convert "$x" -fuzz 5% -trim +repage tmp && mv tmp "$x"
|
||||
}
|
||||
|
||||
while read x; do
|
||||
process "$x";
|
||||
done
|
||||
for x in $@; do
|
||||
process "$x";
|
||||
done
|
@ -1,41 +1,6 @@
|
||||
<?php
|
||||
class Bootstrap
|
||||
{
|
||||
public function attachUser()
|
||||
{
|
||||
$this->context->loggedIn = false;
|
||||
if (isset($_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');
|
||||
$dummy->name = 'Anonymous';
|
||||
$dummy->access_rank = AccessRank::Anonymous;
|
||||
$this->context->user = $dummy;
|
||||
}
|
||||
}
|
||||
|
||||
public function workWrapper($workCallback)
|
||||
{
|
||||
$this->config->chibi->baseUrl = 'http://' . rtrim($_SERVER['HTTP_HOST'], '/') . '/';
|
||||
@ -56,13 +21,14 @@ class Bootstrap
|
||||
'core.js',
|
||||
];
|
||||
|
||||
$this->context->layoutName = isset($_GET['json'])
|
||||
$this->context->json = isset($_GET['json']);
|
||||
$this->context->layoutName = $this->context->json
|
||||
? 'layout-json'
|
||||
: 'layout-normal';
|
||||
$this->context->transport = new StdClass;
|
||||
$this->context->transport->success = null;
|
||||
|
||||
$this->attachUser();
|
||||
AuthController::doLogIn();
|
||||
|
||||
if (empty($this->context->route))
|
||||
{
|
||||
@ -85,14 +51,23 @@ class Bootstrap
|
||||
$this->context->viewName = 'error-simple';
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
catch (\Chibi\MissingViewFileException $e)
|
||||
{
|
||||
$this->context->json = true;
|
||||
$this->context->layoutName = 'layout-json';
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->context->transport->errorMessage = rtrim($e->getMessage(), '.') . '.';
|
||||
$this->context->transport->errorHtml = TextHelper::parseMarkdown($this->context->transport->errorMessage, true);
|
||||
$this->context->transport->exception = $e;
|
||||
$this->context->transport->queries = array_map(function($x) { return preg_replace('/\s+/', ' ', $x); }, queryLogger()->getLogs());
|
||||
$this->context->transport->success = false;
|
||||
$this->context->viewName = 'error-exception';
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
|
||||
AuthController::observeWorkFinish();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,21 @@
|
||||
<?php
|
||||
class AuthController
|
||||
{
|
||||
private static function redirectAfterLog()
|
||||
{
|
||||
if (isset($_SESSION['login-redirect-url']))
|
||||
{
|
||||
\Chibi\UrlHelper::forward($_SESSION['login-redirect-url']);
|
||||
unset($_SESSION['login-redirect-url']);
|
||||
return;
|
||||
}
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
}
|
||||
|
||||
public static function tryLogin($name, $password)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$context = \Chibi\Registry::getContext();
|
||||
|
||||
$dbUser = R::findOne('user', 'name = ?', [$name]);
|
||||
if ($dbUser === null)
|
||||
@ -22,9 +34,8 @@ class AuthController
|
||||
if ($config->registration->needEmailForRegistering)
|
||||
PrivilegesHelper::confirmEmail($dbUser);
|
||||
|
||||
$_SESSION['user-id'] = $dbUser->id;
|
||||
$_SESSION['user'] = serialize($dbUser);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
$context->user = $dbUser;
|
||||
self::doReLog();
|
||||
return $dbUser;
|
||||
}
|
||||
|
||||
@ -50,7 +61,7 @@ class AuthController
|
||||
//check if already logged in
|
||||
if ($this->context->loggedIn)
|
||||
{
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
self::redirectAfterLog();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -66,6 +77,7 @@ class AuthController
|
||||
setcookie('auth', TextHelper::encrypt($token), time() + 365 * 24 * 3600, '/');
|
||||
}
|
||||
$this->context->transport->success = true;
|
||||
self::redirectAfterLog();
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,9 +87,65 @@ class AuthController
|
||||
public function logoutAction()
|
||||
{
|
||||
$this->context->viewName = null;
|
||||
$this->context->viewName = null;
|
||||
unset($_SESSION['user-id']);
|
||||
$this->context->layoutName = null;
|
||||
self::doLogOut();
|
||||
setcookie('auth', false, 0, '/');
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
}
|
||||
|
||||
public static function doLogOut()
|
||||
{
|
||||
unset($_SESSION['user']);
|
||||
}
|
||||
|
||||
public static function doLogIn()
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if (!isset($_SESSION['user']))
|
||||
{
|
||||
if (!empty($context->user) and $context->user->id)
|
||||
{
|
||||
$dbUser = R::findOne('user', 'id = ?', [$context->user->id]);
|
||||
$_SESSION['user'] = serialize($dbUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
$dummy = R::dispense('user');
|
||||
$dummy->name = 'Anonymous';
|
||||
$dummy->access_rank = AccessRank::Anonymous;
|
||||
$dummy->anonymous = true;
|
||||
$_SESSION['user'] = serialize($dummy);
|
||||
}
|
||||
}
|
||||
$context->user = unserialize($_SESSION['user']);
|
||||
$context->loggedIn = $context->user->anonymous ? false : true;
|
||||
if (!$context->loggedIn)
|
||||
{
|
||||
try
|
||||
{
|
||||
self::tryAutoLogin();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function doReLog()
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if ($context->user !== null)
|
||||
$_SESSION['user'] = serialize($context->user);
|
||||
self::doLogIn();
|
||||
}
|
||||
|
||||
public static function observeWorkFinish()
|
||||
{
|
||||
if (strpos(\Chibi\HeadersHelper::get('Content-Type'), 'text/html') === false)
|
||||
return;
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if ($context->route->simpleControllerName == 'auth')
|
||||
return;
|
||||
$_SESSION['login-redirect-url'] = $context->query;
|
||||
}
|
||||
}
|
||||
|
@ -12,35 +12,19 @@ class CommentController
|
||||
$this->context->stylesheets []= 'comment-list.css';
|
||||
$this->context->stylesheets []= 'comment-small.css';
|
||||
$this->context->stylesheets []= 'paginator.css';
|
||||
$this->context->subTitle = 'comments';
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
|
||||
$page = intval($page);
|
||||
$commentsPerPage = intval($this->config->comments->commentsPerPage);
|
||||
$this->context->subTitle = 'comments';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListComments);
|
||||
|
||||
$buildDbQuery = function($dbQuery)
|
||||
{
|
||||
$dbQuery->from('comment');
|
||||
$dbQuery->orderBy('comment_date')->desc();
|
||||
};
|
||||
|
||||
$countDbQuery = R::$f->begin();
|
||||
$countDbQuery->select('COUNT(1)')->as('count');
|
||||
$buildDbQuery($countDbQuery);
|
||||
$commentCount = intval($countDbQuery->get('row')['count']);
|
||||
$commentCount = Model_Comment::getEntityCount(null);
|
||||
$pageCount = ceil($commentCount / $commentsPerPage);
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$comments = Model_Comment::getEntities(null, $commentsPerPage, $page);
|
||||
|
||||
$searchDbQuery = R::$f->begin();
|
||||
$searchDbQuery->select('comment.*');
|
||||
$buildDbQuery($searchDbQuery);
|
||||
$searchDbQuery->limit('?')->put($commentsPerPage);
|
||||
$searchDbQuery->offset('?')->put(($page - 1) * $commentsPerPage);
|
||||
|
||||
$comments = $searchDbQuery->get();
|
||||
$comments = R::convertToBeans('comment', $comments);
|
||||
R::preload($comments, ['commenter' => 'user', 'post', 'post.uploader' => 'user']);
|
||||
$this->context->postGroups = true;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
@ -72,6 +56,7 @@ class CommentController
|
||||
$text = Model_Comment::validateText($text);
|
||||
$comment = R::dispense('comment');
|
||||
$comment->post = $post;
|
||||
if ($this->context->loggedIn)
|
||||
$comment->commenter = $this->context->user;
|
||||
$comment->comment_date = time();
|
||||
$comment->text = $text;
|
||||
|
@ -48,20 +48,30 @@ class PostController
|
||||
|
||||
|
||||
/**
|
||||
* @route /posts
|
||||
* @route /posts/{page}
|
||||
* @route /posts/{query}/
|
||||
* @route /posts/{query}/{page}
|
||||
* @route /{source}
|
||||
* @route /{source}/{page}
|
||||
* @route /{source}/{query}/
|
||||
* @route /{source}/{query}/{page}
|
||||
* @route /{source}/{additionalInfo}/{query}/
|
||||
* @route /{source}/{additionalInfo}/{query}/{page}
|
||||
* @validate source posts|mass-tag
|
||||
* @validate page \d*
|
||||
* @validate query [^\/]*
|
||||
* @validate additionalInfo [^\/]*
|
||||
*/
|
||||
public function listAction($query = null, $page = 1)
|
||||
public function listAction($query = null, $page = 1, $source = 'posts', $additionalInfo = null)
|
||||
{
|
||||
$this->context->stylesheets []= 'post-small.css';
|
||||
$this->context->stylesheets []= 'post-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->stylesheets []= 'paginator.css';
|
||||
$this->context->viewName = 'post-list-wrapper';
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
if ($source == 'mass-tag')
|
||||
$this->context->scripts []= 'mass-tag.js';
|
||||
$this->context->source = $source;
|
||||
$this->context->additionalInfo = $additionalInfo;
|
||||
|
||||
//redirect requests in form of /posts/?query=... to canonical address
|
||||
$formQuery = InputHelper::get('query');
|
||||
@ -70,7 +80,7 @@ class PostController
|
||||
$this->context->transport->searchQuery = $formQuery;
|
||||
if (strpos($formQuery, '/') !== false)
|
||||
throw new SimpleException('Search query contains invalid characters');
|
||||
$url = \Chibi\UrlHelper::route('post', 'list', ['query' => urlencode($formQuery)]);
|
||||
$url = \Chibi\UrlHelper::route('post', 'list', ['source' => $source, 'additionalInfo' => $additionalInfo, 'query' => urlencode($formQuery)]);
|
||||
\Chibi\UrlHelper::forward($url);
|
||||
return;
|
||||
}
|
||||
@ -81,80 +91,18 @@ class PostController
|
||||
$this->context->subTitle = 'posts';
|
||||
$this->context->transport->searchQuery = $query;
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
||||
|
||||
$buildDbQuery = function($dbQuery, $query)
|
||||
if ($source == 'mass-tag')
|
||||
{
|
||||
$dbQuery
|
||||
->addSql(', ')
|
||||
->open()
|
||||
->select('COUNT(1)')
|
||||
->from('comment')
|
||||
->where('comment.post_id = post.id')
|
||||
->close()
|
||||
->as('comment_count');
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
$this->context->massTagTag = $additionalInfo;
|
||||
$this->context->massTagQuery = $query;
|
||||
}
|
||||
|
||||
$dbQuery
|
||||
->addSql(', ')
|
||||
->open()
|
||||
->select('COUNT(1)')
|
||||
->from('favoritee')
|
||||
->where('favoritee.post_id = post.id')
|
||||
->close()
|
||||
->as('fav_count');
|
||||
|
||||
$dbQuery
|
||||
->addSql(', ')
|
||||
->open()
|
||||
->select('COUNT(1)')
|
||||
->from('post_tag')
|
||||
->where('post_tag.post_id = post.id')
|
||||
->close()
|
||||
->as('tag_count');
|
||||
|
||||
$dbQuery->from('post');
|
||||
|
||||
/* safety */
|
||||
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
|
||||
{
|
||||
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
|
||||
$this->context->user->hasEnabledSafety($safety);
|
||||
});
|
||||
$dbQuery->where('safety IN (' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
|
||||
|
||||
/* hidden */
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->andNot('hidden');
|
||||
|
||||
|
||||
/* query tokens */
|
||||
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
|
||||
if (count($tokens) > $this->config->browsing->maxSearchTokens)
|
||||
throw new SimpleException('Too many search tokens (maximum: ' . $this->config->browsing->maxSearchTokens . ')');
|
||||
|
||||
|
||||
/* tokens */
|
||||
$this->decorateSearchQuery($dbQuery, $tokens);
|
||||
};
|
||||
|
||||
$countDbQuery = R::$f->begin();
|
||||
$countDbQuery->select('COUNT(1)')->as('count');
|
||||
$buildDbQuery($countDbQuery, $query);
|
||||
$postCount = intval($countDbQuery->get('row')['count']);
|
||||
$postCount = Model_Post::getEntityCount($query);
|
||||
$pageCount = ceil($postCount / $postsPerPage);
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$posts = Model_Post::getEntities($query, $postsPerPage, $page);
|
||||
|
||||
$searchDbQuery = R::$f->begin();
|
||||
$searchDbQuery->select('post.*');
|
||||
$buildDbQuery($searchDbQuery, $query);
|
||||
$searchDbQuery->limit('?')->put($postsPerPage);
|
||||
$searchDbQuery->offset('?')->put(($page - 1) * $postsPerPage);
|
||||
|
||||
$posts = $searchDbQuery->get();
|
||||
$posts = R::convertToBeans('post', $posts);
|
||||
R::preload($posts, ['uploader' => 'user']);
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
$this->context->transport->paginator->pageCount = $pageCount;
|
||||
@ -165,6 +113,37 @@ class PostController
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{id}/toggle-tag/{tag}
|
||||
* @validate tag [^\/]*
|
||||
*/
|
||||
public function toggleTagAction($id, $tag)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
$this->context->transport->post = $post;
|
||||
$tag = Model_Tag::validateTag($tag);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
$tags = array_map(function($x) { return $x->name; }, $post->sharedTag);
|
||||
|
||||
if (in_array($tag, $tags))
|
||||
$tags = array_diff($tags, [$tag]);
|
||||
else
|
||||
$tags += [$tag];
|
||||
|
||||
$dbTags = Model_Tag::insertOrUpdate($tags);
|
||||
$post->sharedTag = $dbTags;
|
||||
|
||||
R::store($post);
|
||||
$this->context->transport->success = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /favorites
|
||||
* @route /favorites/{page}
|
||||
@ -173,7 +152,6 @@ class PostController
|
||||
public function favoritesAction($page = 1)
|
||||
{
|
||||
$this->listAction('favmin:1', $page);
|
||||
$this->context->viewName = 'post-list';
|
||||
}
|
||||
|
||||
|
||||
@ -186,7 +164,6 @@ class PostController
|
||||
public function randomAction($page = 1)
|
||||
{
|
||||
$this->listAction('order:random', $page);
|
||||
$this->context->viewName = 'post-list';
|
||||
}
|
||||
|
||||
|
||||
@ -341,6 +318,7 @@ class PostController
|
||||
$dbPost->upload_date = time();
|
||||
$dbPost->image_width = $imageWidth;
|
||||
$dbPost->image_height = $imageHeight;
|
||||
if ($this->context->loggedIn and !InputHelper::get('anonymous'))
|
||||
$dbPost->uploader = $this->context->user;
|
||||
$dbPost->ownFavoritee = [];
|
||||
$dbPost->sharedTag = $dbTags;
|
||||
@ -414,7 +392,7 @@ class PostController
|
||||
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;
|
||||
$path = $this->config->main->thumbsPath . DS . $post->name . '.custom';
|
||||
move_uploaded_file($suppliedFile['tmp_name'], $path);
|
||||
}
|
||||
|
||||
@ -429,7 +407,29 @@ class PostController
|
||||
$edited = true;
|
||||
}
|
||||
|
||||
|
||||
/* relations */
|
||||
$suppliedRelations = InputHelper::get('relations');
|
||||
if ($suppliedRelations !== null)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$relatedIds = array_filter(preg_split('/\D/', $suppliedRelations));
|
||||
$relatedPosts = [];
|
||||
foreach ($relatedIds as $relatedId)
|
||||
{
|
||||
if ($relatedId == $post->id)
|
||||
continue;
|
||||
if (count($relatedPosts) > $this->config->browsing->maxRelatedPosts)
|
||||
throw new SimpleException('Too many related posts (maximum: ' . $this->config->browsing->maxRelatedPosts . ')');
|
||||
$relatedPosts []= Model_Post::locate($relatedId);
|
||||
}
|
||||
$post->via('crossref')->sharedPost = $relatedPosts;
|
||||
}
|
||||
|
||||
R::store($post);
|
||||
Model_Tag::removeUnused();
|
||||
|
||||
|
||||
$this->context->transport->success = true;
|
||||
}
|
||||
}
|
||||
@ -479,6 +479,11 @@ class PostController
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
//remove stuff from auxiliary tables
|
||||
foreach ($post->ownComment as $comment)
|
||||
{
|
||||
$comment->post = null;
|
||||
R::store($comment);
|
||||
}
|
||||
$post->ownFavoritee = [];
|
||||
$post->sharedTag = [];
|
||||
R::store($post);
|
||||
@ -568,7 +573,6 @@ class PostController
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, [
|
||||
'favoritee' => 'user',
|
||||
'uploader' => 'user',
|
||||
'tag',
|
||||
'comment',
|
||||
@ -585,6 +589,14 @@ class PostController
|
||||
->from('post')
|
||||
->where($next ? 'id > ?' : 'id < ?')
|
||||
->put($id);
|
||||
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
|
||||
{
|
||||
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
|
||||
$this->context->user->hasEnabledSafety($safety);
|
||||
});
|
||||
$dbQuery->and('safety')->in('(' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->andNot('hidden');
|
||||
$dbQuery->orderBy($next ? 'id asc' : 'id desc')
|
||||
@ -605,20 +617,6 @@ class PostController
|
||||
if ($fav->user->id == $this->context->user->id)
|
||||
$favorite = true;
|
||||
|
||||
$dbQuery = R::$f->begin();
|
||||
$dbQuery->select('tag.name, COUNT(1) AS count');
|
||||
$dbQuery->from('tag');
|
||||
$dbQuery->innerJoin('post_tag');
|
||||
$dbQuery->on('tag.id = post_tag.tag_id');
|
||||
$dbQuery->where('tag.id IN (' . R::genSlots($post->sharedTag) . ')');
|
||||
foreach ($post->sharedTag as $tag)
|
||||
$dbQuery->put($tag->id);
|
||||
$dbQuery->groupBy('tag.id');
|
||||
$rows = $dbQuery->get();
|
||||
$this->context->transport->tagDistribution = [];
|
||||
foreach ($rows as $row)
|
||||
$this->context->transport->tagDistribution[$row['name']] = $row['count'];
|
||||
|
||||
$this->context->stylesheets []= 'post-view.css';
|
||||
$this->context->stylesheets []= 'comment-small.css';
|
||||
$this->context->scripts []= 'post-view.js';
|
||||
@ -640,21 +638,22 @@ class PostController
|
||||
{
|
||||
$this->context->layoutName = 'layout-file';
|
||||
|
||||
$path = $this->config->main->thumbsPath . DS . $name;
|
||||
$path = $this->config->main->thumbsPath . DS . $name . '.custom';
|
||||
if (!file_exists($path))
|
||||
$path = $this->config->main->thumbsPath . DS . $name . '.default';
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$post = Model_Post::locate($name);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts, PostSafety::toString($post->safety));
|
||||
$srcPath = $this->config->main->filesPath . DS . $post->name;
|
||||
$dstPath = $path;
|
||||
$dstWidth = $this->config->browsing->thumbWidth;
|
||||
$dstHeight = $this->config->browsing->thumbHeight;
|
||||
|
||||
if ($post->type == PostType::Youtube)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
|
||||
$contents = file_get_contents('http://img.youtube.com/vi/' . $post->orig_name . '/mqdefault.jpg');
|
||||
file_put_contents($tmpPath, $contents);
|
||||
if (file_exists($tmpPath))
|
||||
@ -673,6 +672,16 @@ class PostController
|
||||
break;
|
||||
case 'application/x-shockwave-flash':
|
||||
$srcImage = null;
|
||||
exec('which dump-gnash', $tmp, $exitCode);
|
||||
if ($exitCode == 0)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
||||
exec('dump-gnash --screenshot last --screenshot-file ' . $tmpPath . ' -1 -r1 --max-advances 15 ' . $srcPath);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefrompng($tmpPath);
|
||||
}
|
||||
if (!$srcImage)
|
||||
{
|
||||
exec('which swfrender', $tmp, $exitCode);
|
||||
if ($exitCode == 0)
|
||||
{
|
||||
@ -681,6 +690,7 @@ class PostController
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefrompng($tmpPath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -700,13 +710,13 @@ class PostController
|
||||
throw new SimpleException('Unknown thumbnail crop style');
|
||||
}
|
||||
|
||||
imagepng($dstImage, $dstPath);
|
||||
imagejpeg($dstImage, $path);
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($dstImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb.png';
|
||||
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb.jpg';
|
||||
}
|
||||
|
||||
if (isset($tmpPath))
|
||||
@ -716,7 +726,7 @@ class PostController
|
||||
throw new SimpleException('Thumbnail file is not readable');
|
||||
|
||||
$this->context->transport->cacheDaysToLive = 30;
|
||||
$this->context->transport->mimeType = 'image/png';
|
||||
$this->context->transport->mimeType = 'image/jpeg';
|
||||
$this->context->transport->fileHash = 'thumb' . md5($name . filemtime($path));
|
||||
$this->context->transport->filePath = $path;
|
||||
}
|
||||
@ -759,224 +769,4 @@ class PostController
|
||||
$this->context->transport->fileHash = 'post' . $post->file_hash;
|
||||
$this->context->transport->filePath = $path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function decorateSearchQuery($dbQuery, $tokens)
|
||||
{
|
||||
$orderColumn = 'post.id';
|
||||
$orderDir = 1;
|
||||
$randomReset = true;
|
||||
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if ($token{0} == '-')
|
||||
{
|
||||
$andFunc = 'andNot';
|
||||
$token = substr($token, 1);
|
||||
$neg = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$andFunc = 'and';
|
||||
$neg = false;
|
||||
}
|
||||
|
||||
$pos = strpos($token, ':');
|
||||
if ($pos === false)
|
||||
{
|
||||
$val = $token;
|
||||
$dbQuery
|
||||
->$andFunc()
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->innerJoin('tag')
|
||||
->on('post_tag.tag_id = tag.id')
|
||||
->where('post_id = post.id')
|
||||
->and('LOWER(tag.name) = LOWER(?)')->put($val)
|
||||
->close();
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = substr($token, 0, $pos);
|
||||
$val = substr($token, $pos + 1);
|
||||
|
||||
switch ($key)
|
||||
{
|
||||
case 'favmin':
|
||||
case 'favmax':
|
||||
$operator = $key == 'favmin' ? '>=' : '<=';
|
||||
$dbQuery
|
||||
->$andFunc('fav_count ' . $operator . ' ?')->put(intval($val));
|
||||
break;
|
||||
|
||||
case 'type':
|
||||
switch ($val)
|
||||
{
|
||||
case 'swf':
|
||||
$type = PostType::Flash;
|
||||
break;
|
||||
case 'img':
|
||||
$type = PostType::Image;
|
||||
break;
|
||||
case 'yt':
|
||||
case 'youtube':
|
||||
$type = PostType::Youtube;
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown type "' . $val . '"');
|
||||
}
|
||||
$dbQuery
|
||||
->$andFunc('type = ?')
|
||||
->put($type);
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
case 'datemin':
|
||||
case 'datemax':
|
||||
list ($year, $month, $day) = explode('-', $val . '-0-0');
|
||||
$yearMin = $yearMax = intval($year);
|
||||
$monthMin = $monthMax = intval($month);
|
||||
$monthMin = $monthMin ?: 1;
|
||||
$monthMax = $monthMax ?: 12;
|
||||
$dayMin = $dayMax = intval($day);
|
||||
$dayMin = $dayMin ?: 1;
|
||||
$dayMax = $dayMax ?: intval(date('t', mktime(0, 0, 0, $monthMax, 1, $year)));
|
||||
$timeMin = mktime(0, 0, 0, $monthMin, $dayMin, $yearMin);
|
||||
$timeMax = mktime(0, 0, -1, $monthMax, $dayMax+1, $yearMax);
|
||||
|
||||
if ($key == 'date')
|
||||
{
|
||||
$dbQuery
|
||||
->$andFunc('upload_date >= ?')
|
||||
->and('upload_date <= ?')
|
||||
->put($timeMin)
|
||||
->put($timeMax);
|
||||
}
|
||||
elseif ($key == 'datemin')
|
||||
{
|
||||
$dbQuery
|
||||
->$andFunc('upload_date >= ?')
|
||||
->put($timeMin);
|
||||
}
|
||||
elseif ($key == 'datemax')
|
||||
{
|
||||
$dbQuery
|
||||
->$andFunc('upload_date <= ?')
|
||||
->put($timeMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception('Invalid key');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'fav':
|
||||
case 'favs':
|
||||
case 'favoritee':
|
||||
case 'favoriter':
|
||||
$dbQuery
|
||||
->$andFunc()
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('favoritee')
|
||||
->innerJoin('user')
|
||||
->on('favoritee.user_id = user.id')
|
||||
->where('post_id = post.id')
|
||||
->and('user.name = ?')->put($val)
|
||||
->close();
|
||||
break;
|
||||
|
||||
case 'submit':
|
||||
case 'upload':
|
||||
case 'uploader':
|
||||
case 'uploaded':
|
||||
$dbQuery
|
||||
->$andFunc('uploader_id = ')
|
||||
->open()
|
||||
->select('user.id')
|
||||
->from('user')
|
||||
->where('name = ?')->put($val)
|
||||
->close();
|
||||
break;
|
||||
|
||||
case 'order':
|
||||
if (substr($val, -4) == 'desc')
|
||||
{
|
||||
$orderDir = 1;
|
||||
$val = rtrim(substr($val, 0, -4), ',');
|
||||
}
|
||||
elseif (substr($val, -3) == 'asc')
|
||||
{
|
||||
$orderDir = -1;
|
||||
$val = rtrim(substr($val, 0, -3), ',');
|
||||
}
|
||||
if ($val{0} == '-')
|
||||
{
|
||||
$orderDir *= -1;
|
||||
$val = substr($val, 1);
|
||||
}
|
||||
if ($neg)
|
||||
{
|
||||
$orderDir *= -1;
|
||||
}
|
||||
|
||||
switch ($val)
|
||||
{
|
||||
case 'id':
|
||||
$orderColumn = 'post.id';
|
||||
break;
|
||||
case 'date':
|
||||
$orderColumn = 'post.upload_date';
|
||||
break;
|
||||
case 'comment':
|
||||
case 'comments':
|
||||
case 'commentcount':
|
||||
$orderColumn = 'comment_count';
|
||||
break;
|
||||
case 'fav':
|
||||
case 'favs':
|
||||
case 'favcount':
|
||||
$orderColumn = 'fav_count';
|
||||
break;
|
||||
case 'tag':
|
||||
case 'tags':
|
||||
case 'tagcount':
|
||||
$orderColumn = 'tag_count';
|
||||
break;
|
||||
case 'random':
|
||||
//seeding works like this: if you visit anything
|
||||
//that triggers order other than random, the seed
|
||||
//is going to reset. however, it stays the same as
|
||||
//long as you keep visiting pages with order:random
|
||||
//specified.
|
||||
$randomReset = false;
|
||||
if (!isset($_SESSION['browsing-seed']))
|
||||
$_SESSION['browsing-seed'] = mt_rand();
|
||||
$seed = $_SESSION['browsing-seed'];
|
||||
$orderColumn = 'SUBSTR(id * ' . $seed .', LENGTH(id) + 2)';
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown key "' . $val . '"');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new SimpleException('Unknown key "' . $key . '"');
|
||||
}
|
||||
}
|
||||
|
||||
if ($randomReset)
|
||||
unset($_SESSION['browsing-seed']);
|
||||
|
||||
$dbQuery->orderBy($orderColumn);
|
||||
if ($orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
}
|
||||
}
|
||||
|
@ -7,40 +7,31 @@ class TagController
|
||||
public function listAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListTags);
|
||||
$suppliedFilter = InputHelper::get('filter');
|
||||
|
||||
$dbQuery = R::$f->begin();
|
||||
$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 = [];
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$tags []= strval($row['name']);
|
||||
$tagDistribution[$row['name']] = intval($row['count']);
|
||||
}
|
||||
|
||||
$tags = Model_Tag::getEntitiesRows($suppliedFilter, null, null);
|
||||
$this->context->transport->tags = $tags;
|
||||
$this->context->transport->tagDistribution = $tagDistribution;
|
||||
|
||||
if ($this->context->json)
|
||||
{
|
||||
$this->context->transport->tags = array_values(array_map(function($tag) {
|
||||
return ['name' => $tag['name'], 'count' => $tag['post_count']];
|
||||
}, $this->context->transport->tags));
|
||||
usort($this->context->transport->tags, function($a, $b) {
|
||||
return $a['count'] > $b['count'] ? -1 : 1;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
uasort($this->context->transport->tags, function($a, $b) {
|
||||
return strnatcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,11 +39,24 @@ class TagController
|
||||
*/
|
||||
public function mergeAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$sourceTag = Model_Tag::locate(InputHelper::get('source-tag'));
|
||||
$targetTag = Model_Tag::locate(InputHelper::get('target-tag'));
|
||||
$suppliedSourceTag = InputHelper::get('source-tag');
|
||||
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
|
||||
$suppliedTargetTag = InputHelper::get('target-tag');
|
||||
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
|
||||
$targetTag = Model_Tag::locate($suppliedTargetTag);
|
||||
|
||||
if ($sourceTag->id == $targetTag->id)
|
||||
throw new SimpleException('Source and target tag are the same');
|
||||
|
||||
R::preload($sourceTag, 'post');
|
||||
|
||||
@ -75,6 +79,11 @@ class TagController
|
||||
*/
|
||||
public function renameAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
@ -83,6 +92,9 @@ class TagController
|
||||
|
||||
$suppliedTargetTag = InputHelper::get('target-tag');
|
||||
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
|
||||
$targetTag = Model_Tag::locate($suppliedTargetTag, false);
|
||||
if ($targetTag)
|
||||
throw new SimpleException('Target tag already exists');
|
||||
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
$sourceTag->name = $suppliedTargetTag;
|
||||
@ -92,4 +104,26 @@ class TagController
|
||||
$this->context->transport->success = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /mass-tag-redirect
|
||||
*/
|
||||
public function massTagRedirectAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedQuery = InputHelper::get('query');
|
||||
if (!$suppliedQuery)
|
||||
$suppliedQuery = ' ';
|
||||
$suppliedTag = InputHelper::get('tag');
|
||||
$suppliedTag = Model_Tag::validateTag($suppliedTag);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', ['source' => 'mass-tag', 'query' => urlencode($suppliedQuery), 'additionalInfo' => $suppliedTag]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,58 +56,21 @@ class UserController
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
|
||||
$page = intval($page);
|
||||
$usersPerPage = intval($this->config->browsing->usersPerPage);
|
||||
$this->context->subTitle = 'users';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
||||
|
||||
if ($sortStyle == '' or $sortStyle == 'alpha')
|
||||
$sortStyle = 'alpha,asc';
|
||||
if ($sortStyle == 'date')
|
||||
$sortStyle = 'date,asc';
|
||||
|
||||
$buildDbQuery = function($dbQuery, $sortStyle)
|
||||
{
|
||||
$dbQuery->from('user');
|
||||
$page = intval($page);
|
||||
$usersPerPage = intval($this->config->browsing->usersPerPage);
|
||||
$this->context->subTitle = 'users';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
||||
|
||||
switch ($sortStyle)
|
||||
{
|
||||
case 'alpha,asc':
|
||||
$dbQuery->orderBy('name')->asc();
|
||||
break;
|
||||
case 'alpha,desc':
|
||||
$dbQuery->orderBy('name')->desc();
|
||||
break;
|
||||
case 'date,asc':
|
||||
$dbQuery->orderBy('join_date')->asc();
|
||||
break;
|
||||
case 'date,desc':
|
||||
$dbQuery->orderBy('join_date')->desc();
|
||||
break;
|
||||
case 'pending':
|
||||
$dbQuery->where('staff_confirmed IS NULL');
|
||||
$dbQuery->or('staff_confirmed = 0');
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown sort style');
|
||||
}
|
||||
};
|
||||
|
||||
$countDbQuery = R::$f->begin();
|
||||
$countDbQuery->select('COUNT(1)')->as('count');
|
||||
$buildDbQuery($countDbQuery, $sortStyle);
|
||||
$userCount = intval($countDbQuery->get('row')['count']);
|
||||
$userCount = Model_User::getEntityCount($sortStyle);
|
||||
$pageCount = ceil($userCount / $usersPerPage);
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$users = Model_User::getEntities($sortStyle, $usersPerPage, $page);
|
||||
|
||||
$searchDbQuery = R::$f->begin();
|
||||
$searchDbQuery->select('user.*');
|
||||
$buildDbQuery($searchDbQuery, $sortStyle);
|
||||
$searchDbQuery->limit('?')->put($usersPerPage);
|
||||
$searchDbQuery->offset('?')->put(($page - 1) * $usersPerPage);
|
||||
|
||||
$users = $searchDbQuery->get();
|
||||
$users = R::convertToBeans('user', $users);
|
||||
$this->context->sortStyle = $sortStyle;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
@ -209,6 +172,8 @@ class UserController
|
||||
R::store($post);
|
||||
}
|
||||
$user->ownFavoritee = [];
|
||||
if ($user->id == $this->context->user->id)
|
||||
AuthController::doLogOut();
|
||||
R::store($user);
|
||||
R::trash($user);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
@ -247,7 +212,9 @@ class UserController
|
||||
$user->enableEndlessScrolling(InputHelper::get('endless-scrolling'));
|
||||
|
||||
R::store($user);
|
||||
$this->context->transport->user = $user;
|
||||
if ($user->id == $this->context->user->id)
|
||||
$this->context->user = $user;
|
||||
AuthController::doReLog();
|
||||
$this->context->transport->success = true;
|
||||
}
|
||||
}
|
||||
@ -367,67 +334,19 @@ class UserController
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
$this->context->subTitle = $name;
|
||||
|
||||
$buildDbQuery = function($dbQuery, $user, $tab)
|
||||
{
|
||||
$dbQuery->from('post');
|
||||
$query = '';
|
||||
if ($tab == 'uploads')
|
||||
$query = 'submit:' . $user->name;
|
||||
elseif ($tab == 'favs')
|
||||
$query = 'fav:' . $user->name;
|
||||
else
|
||||
throw new SimpleException('Wrong tab');
|
||||
|
||||
|
||||
/* safety */
|
||||
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
|
||||
{
|
||||
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
|
||||
$this->context->user->hasEnabledSafety($safety);
|
||||
});
|
||||
$dbQuery->where('safety IN (' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
|
||||
|
||||
/* hidden */
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->andNot('hidden');
|
||||
|
||||
|
||||
/* tab */
|
||||
switch ($tab)
|
||||
{
|
||||
case 'uploads':
|
||||
$dbQuery
|
||||
->and('uploader_id = ?')
|
||||
->put($user->id);
|
||||
break;
|
||||
case 'favs':
|
||||
$dbQuery
|
||||
->and()
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('favoritee')
|
||||
->where('post_id = post.id')
|
||||
->and('favoritee.user_id = ?')
|
||||
->put($user->id)
|
||||
->close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$countDbQuery = R::$f->begin()->select('COUNT(*)')->as('count');
|
||||
$buildDbQuery($countDbQuery, $user, $tab);
|
||||
$postCount = intval($countDbQuery->get('row')['count']);
|
||||
$postCount = Model_Post::getEntityCount($query);
|
||||
$pageCount = ceil($postCount / $postsPerPage);
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$posts = Model_Post::getEntities($query, $postsPerPage, $page);
|
||||
|
||||
$searchDbQuery = R::$f->begin()->select('*');
|
||||
$buildDbQuery($searchDbQuery, $user, $tab);
|
||||
$searchDbQuery->orderBy('id DESC')
|
||||
->limit('?')
|
||||
->put($postsPerPage)
|
||||
->offset('?')
|
||||
->put(($page - 1) * $postsPerPage);
|
||||
|
||||
$posts = $searchDbQuery->get();
|
||||
$posts = R::convertToBeans('post', $posts);
|
||||
R::preload($posts, ['uploader' => 'user']);
|
||||
$this->context->transport->user = $user;
|
||||
$this->context->transport->tab = $tab;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
@ -453,6 +372,8 @@ class UserController
|
||||
$this->context->user->enableSafety($safety,
|
||||
!$this->context->user->hasEnabledSafety($safety));
|
||||
|
||||
AuthController::doReLog();
|
||||
if (!$this->context->user->anonymous)
|
||||
R::store($this->context->user);
|
||||
|
||||
$this->context->transport->success = true;
|
||||
@ -535,8 +456,8 @@ class UserController
|
||||
|
||||
if (!$this->config->registration->needEmailForRegistering and !$this->config->registration->staffActivation)
|
||||
{
|
||||
$_SESSION['user-id'] = $dbUser->id;
|
||||
\Chibi\Registry::getBootstrap()->attachUser();
|
||||
$this->context->user = $dbUser;
|
||||
AuthController::doReLog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -567,8 +488,8 @@ class UserController
|
||||
|
||||
if (!$this->config->registration->staffActivation)
|
||||
{
|
||||
$_SESSION['user-id'] = $dbUser->id;
|
||||
\Chibi\Registry::getBootstrap()->attachUser();
|
||||
$this->context->user = $dbUser;
|
||||
AuthController::doReLog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,20 +7,37 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
$this->span_gamut += ['doSpoilers' => 71];
|
||||
$this->span_gamut += ['doPosts' => 8];
|
||||
$this->span_gamut += ['doTags' => 9];
|
||||
$this->span_gamut += ['doAutoLinks2' => 29];
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function doAutoLinks2($text)
|
||||
{
|
||||
$text = preg_replace_callback('{(?<!<)((https?|ftp):[^\'"><\s]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
$text = preg_replace_callback('{(?<!\w)(www\.[^\'"><\s]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function _doAnchors_inline_callback($matches)
|
||||
{
|
||||
if ($matches[3] == '')
|
||||
$url = &$matches[4];
|
||||
else
|
||||
$url = &$matches[3];
|
||||
if (!preg_match('/^((https?|ftp):|)\/\//', $url))
|
||||
$url = 'http://' . $url;
|
||||
return parent::_doAnchors_inline_callback($matches);
|
||||
}
|
||||
|
||||
protected function doHardBreaks($text)
|
||||
{
|
||||
return preg_replace_callback('/\n/', array(&$this, '_doHardBreaks_callback'), $text);
|
||||
return preg_replace_callback('/\n/', [&$this, '_doHardBreaks_callback'], $text);
|
||||
}
|
||||
|
||||
protected function doSpoilers($text)
|
||||
{
|
||||
if (is_array($text))
|
||||
{
|
||||
$text = $this->hashPart('<span class="spoiler">') . $text[1] . $this->hashPart('</span>');
|
||||
}
|
||||
return preg_replace_callback('{\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\])|(?R))+)\[\/spoiler\]}is', [__CLASS__, 'doSpoilers'], $text);
|
||||
}
|
||||
|
||||
|
20
src/Helpers/BenchmarkHelper.php
Normal file
20
src/Helpers/BenchmarkHelper.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
class BenchmarkHelper
|
||||
{
|
||||
protected static $lastTime;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$lastTime = microtime(true);
|
||||
}
|
||||
|
||||
public static function tick()
|
||||
{
|
||||
$t = microtime(true);
|
||||
$lt = self::$lastTime;
|
||||
self::$lastTime = $t;
|
||||
return $t - $lt;
|
||||
}
|
||||
}
|
||||
|
||||
BenchmarkHelper::init();
|
@ -22,6 +22,9 @@ class PrivilegesHelper
|
||||
|
||||
public static function confirm($privilege, $subPrivilege = null)
|
||||
{
|
||||
if (php_sapi_name() == 'cli')
|
||||
return true;
|
||||
|
||||
$user = \Chibi\Registry::getContext()->user;
|
||||
$minAccessRank = AccessRank::Admin;
|
||||
|
||||
@ -53,7 +56,7 @@ class PrivilegesHelper
|
||||
public static function getIdentitySubPrivilege($user)
|
||||
{
|
||||
if (!$user)
|
||||
return false;
|
||||
return 'all';
|
||||
$userFromContext = \Chibi\Registry::getContext()->user;
|
||||
return $user->id == $userFromContext->id ? 'own' : 'all';
|
||||
}
|
||||
@ -63,6 +66,19 @@ class PrivilegesHelper
|
||||
if (!$user->email_confirmed)
|
||||
throw new SimpleException('Need e-mail address confirmation to continue');
|
||||
}
|
||||
|
||||
public static function getAllowedSafety()
|
||||
{
|
||||
if (php_sapi_name() == 'cli')
|
||||
return PostSafety::getAll();
|
||||
|
||||
$context = \Chibi\Registry::getContext();
|
||||
return array_filter(PostSafety::getAll(), function($safety) use ($context)
|
||||
{
|
||||
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
|
||||
$context->user->hasEnabledSafety($safety);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PrivilegesHelper::init();
|
||||
|
@ -48,6 +48,14 @@ class TextHelper
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function humanCaseToKebabCase($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
$string = str_replace(' ', '-', $string);
|
||||
$string = strtolower($string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function resolveConstant($constantName, $className = null)
|
||||
{
|
||||
$constantName = self::kebabCaseToCamelCase($constantName);
|
||||
|
53
src/Models/AbstractModel.php
Normal file
53
src/Models/AbstractModel.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
abstract class AbstractModel extends RedBean_SimpleModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
throw new SimpleException('Not implemented.');
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
throw new SimpleException('Not implemented.');
|
||||
}
|
||||
|
||||
public static function getEntitiesRows($query, $perPage = null, $page = 1)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$dbQuery = R::$f->getNew()->begin();
|
||||
$dbQuery->select($table . '.*');
|
||||
$builder = static::getQueryBuilder();
|
||||
if ($builder)
|
||||
$builder::build($dbQuery, $query);
|
||||
else
|
||||
$dbQuery->from($table);
|
||||
if ($perPage !== null)
|
||||
{
|
||||
$dbQuery->limit('?')->put($perPage);
|
||||
$dbQuery->offset('?')->put(($page - 1) * $perPage);
|
||||
}
|
||||
$rows = $dbQuery->get();
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public static function getEntities($query, $perPage = null, $page = 1)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$rows = self::getEntitiesRows($query, $perPage, $page);
|
||||
$entities = R::convertToBeans($table, $rows);
|
||||
return $entities;
|
||||
}
|
||||
|
||||
public static function getEntityCount($query)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$dbQuery = R::$f->getNew()->begin();
|
||||
$dbQuery->select('COUNT(1)')->as('count');
|
||||
$builder = static::getQueryBuilder();
|
||||
if ($builder)
|
||||
$builder::build($dbQuery, $query);
|
||||
else
|
||||
$dbQuery->from($table);
|
||||
return intval($dbQuery->get('row')['count']);
|
||||
}
|
||||
}
|
5
src/Models/AbstractQueryBuilder
Normal file
5
src/Models/AbstractQueryBuilder
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
interface AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query);
|
||||
}
|
@ -1,9 +1,19 @@
|
||||
<?php
|
||||
class Model_Comment extends RedBean_SimpleModel
|
||||
class Model_Comment extends AbstractModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Comment_QueryBuilder';
|
||||
}
|
||||
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$comment = R::findOne('comment', 'id = ?', [$key]);
|
||||
$comment = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
||||
if (!$comment)
|
||||
{
|
||||
if ($throw)
|
||||
|
13
src/Models/Model_Comment_QueryBuilder.php
Normal file
13
src/Models/Model_Comment_QueryBuilder.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
class Model_Comment_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$dbQuery
|
||||
->from('comment')
|
||||
->where('post_id')
|
||||
->is()->not('NULL')
|
||||
->orderBy('id')
|
||||
->desc();
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
class Model_Post extends RedBean_SimpleModel
|
||||
class Model_Post extends AbstractModel
|
||||
{
|
||||
public static function locate($key, $disallowNumeric = false, $throw = true)
|
||||
{
|
||||
if (is_numeric($key) and !$disallowNumeric)
|
||||
{
|
||||
$post = R::findOne('post', 'id = ?', [$key]);
|
||||
$post = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
||||
if (!$post)
|
||||
{
|
||||
if ($throw)
|
||||
@ -15,7 +15,7 @@ class Model_Post extends RedBean_SimpleModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$post = R::findOne('post', 'name = ?', [$key]);
|
||||
$post = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
||||
if (!$post)
|
||||
{
|
||||
if ($throw)
|
||||
@ -40,10 +40,20 @@ class Model_Post extends RedBean_SimpleModel
|
||||
{
|
||||
$source = trim($source);
|
||||
|
||||
$maxLength = 100;
|
||||
$maxLength = 200;
|
||||
if (strlen($source) > $maxLength)
|
||||
throw new SimpleException('Source must have at most ' . $maxLength . ' characters');
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'post';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Post_QueryBuilder';
|
||||
}
|
||||
}
|
||||
|
380
src/Models/Model_Post_QueryBuilder.php
Normal file
380
src/Models/Model_Post_QueryBuilder.php
Normal file
@ -0,0 +1,380 @@
|
||||
<?php
|
||||
class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
protected static function attachTableCount($dbQuery, $tableName, $shortName)
|
||||
{
|
||||
$dbQuery
|
||||
->addSql(', ')
|
||||
->open()
|
||||
->select('COUNT(1)')
|
||||
->from($tableName)
|
||||
->where($tableName . '.post_id = post.id')
|
||||
->close()
|
||||
->as($shortName . '_count');
|
||||
}
|
||||
|
||||
protected static function attachCommentCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'comment', 'comment');
|
||||
}
|
||||
|
||||
protected static function attachFavCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'favoritee', 'fav');
|
||||
}
|
||||
|
||||
protected static function attachTagCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'post_tag', 'tag');
|
||||
}
|
||||
|
||||
protected static function filterUserSafety($dbQuery)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
$dbQuery->addSql('safety')->in('(' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
}
|
||||
|
||||
protected static function filterUserHidden($dbQuery)
|
||||
{
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->not()->addSql('hidden');
|
||||
else
|
||||
$dbQuery->addSql('1');
|
||||
}
|
||||
|
||||
protected static function filterChain($dbQuery)
|
||||
{
|
||||
if (isset($dbQuery->__chained))
|
||||
$dbQuery->and();
|
||||
else
|
||||
$dbQuery->where();
|
||||
$dbQuery->__chained = true;
|
||||
}
|
||||
|
||||
protected static function filterNegate($dbQuery)
|
||||
{
|
||||
$dbQuery->not();
|
||||
}
|
||||
|
||||
protected static function filterTag($dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->innerJoin('tag')
|
||||
->on('post_tag.tag_id = tag.id')
|
||||
->where('post_id = post.id')
|
||||
->and('LOWER(tag.name) = LOWER(?)')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenId($dbQuery, $val)
|
||||
{
|
||||
$ids = preg_split('/[;,]/', $val);
|
||||
$ids = array_map('intval', $ids);
|
||||
$dbQuery->addSql('id')->in('(' . R::genSlots($ids) . ')');
|
||||
foreach ($ids as $id)
|
||||
$dbQuery->put($id);
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenType($dbQuery, $val)
|
||||
{
|
||||
switch ($val)
|
||||
{
|
||||
case 'swf':
|
||||
$type = PostType::Flash;
|
||||
break;
|
||||
case 'img':
|
||||
$type = PostType::Image;
|
||||
break;
|
||||
case 'yt':
|
||||
case 'youtube':
|
||||
$type = PostType::Youtube;
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown type "' . $val . '"');
|
||||
}
|
||||
$dbQuery->addSql('type = ?')->put($type);
|
||||
}
|
||||
|
||||
protected static function __filterTokenDateParser($val)
|
||||
{
|
||||
list ($year, $month, $day) = explode('-', $val . '-0-0');
|
||||
$yearMin = $yearMax = intval($year);
|
||||
$monthMin = $monthMax = intval($month);
|
||||
$monthMin = $monthMin ?: 1;
|
||||
$monthMax = $monthMax ?: 12;
|
||||
$dayMin = $dayMax = intval($day);
|
||||
$dayMin = $dayMin ?: 1;
|
||||
$dayMax = $dayMax ?: intval(date('t', mktime(0, 0, 0, $monthMax, 1, $year)));
|
||||
$timeMin = mktime(0, 0, 0, $monthMin, $dayMin, $yearMin);
|
||||
$timeMax = mktime(0, 0, -1, $monthMax, $dayMax+1, $yearMax);
|
||||
return [$timeMin, $timeMax];
|
||||
}
|
||||
|
||||
protected static function filterTokenDate($dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery
|
||||
->addSql('upload_date >= ?')->and('upload_date <= ?')
|
||||
->put($timeMin)
|
||||
->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMin($dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date >= ?')->put($timeMin);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMax($dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date <= ?')->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenFav($dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('favoritee')
|
||||
->innerJoin('user')
|
||||
->on('favoritee.user_id = user.id')
|
||||
->where('post_id = post.id')
|
||||
->and('user.name = ?')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenFavs($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenFavitee($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenFaviter($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenSubmit($dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->addSql('uploader_id = ')
|
||||
->open()
|
||||
->select('user.id')
|
||||
->from('user')
|
||||
->where('name = ?')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenUploader($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUpload($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUploaded($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected static function order($dbQuery, $val)
|
||||
{
|
||||
$randomReset = true;
|
||||
|
||||
$orderDir = 1;
|
||||
if (substr($val, -4) == 'desc')
|
||||
{
|
||||
$orderDir = 1;
|
||||
$val = rtrim(substr($val, 0, -4), ',');
|
||||
}
|
||||
elseif (substr($val, -3) == 'asc')
|
||||
{
|
||||
$orderDir = -1;
|
||||
$val = rtrim(substr($val, 0, -3), ',');
|
||||
}
|
||||
if ($val{0} == '-')
|
||||
{
|
||||
$orderDir *= -1;
|
||||
$val = substr($val, 1);
|
||||
}
|
||||
|
||||
switch ($val)
|
||||
{
|
||||
case 'id':
|
||||
$orderColumn = 'post.id';
|
||||
break;
|
||||
case 'date':
|
||||
$orderColumn = 'post.upload_date';
|
||||
break;
|
||||
case 'comment':
|
||||
case 'comments':
|
||||
case 'commentcount':
|
||||
$orderColumn = 'comment_count';
|
||||
break;
|
||||
case 'fav':
|
||||
case 'favs':
|
||||
case 'favcount':
|
||||
$orderColumn = 'fav_count';
|
||||
break;
|
||||
case 'tag':
|
||||
case 'tags':
|
||||
case 'tagcount':
|
||||
$orderColumn = 'tag_count';
|
||||
break;
|
||||
case 'random':
|
||||
//seeding works like this: if you visit anything
|
||||
//that triggers order other than random, the seed
|
||||
//is going to reset. however, it stays the same as
|
||||
//long as you keep visiting pages with order:random
|
||||
//specified.
|
||||
$randomReset = false;
|
||||
if (!isset($_SESSION['browsing-seed']))
|
||||
$_SESSION['browsing-seed'] = mt_rand();
|
||||
$seed = $_SESSION['browsing-seed'];
|
||||
$orderColumn = 'SUBSTR(id * ' . $seed .', LENGTH(id) + 2)';
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown key "' . $val . '"');
|
||||
}
|
||||
|
||||
if ($randomReset and isset($_SESSION['browsing-seed']))
|
||||
unset($_SESSION['browsing-seed']);
|
||||
|
||||
$dbQuery->orderBy($orderColumn);
|
||||
if ($orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
self::attachCommentCount($dbQuery);
|
||||
self::attachFavCount($dbQuery);
|
||||
self::attachTagCount($dbQuery);
|
||||
|
||||
$dbQuery->from('post');
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserSafety($dbQuery);
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserHidden($dbQuery);
|
||||
|
||||
/* query tokens */
|
||||
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
|
||||
if (count($tokens) > $config->browsing->maxSearchTokens)
|
||||
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
||||
|
||||
$orderToken = 'id';
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if ($token{0} == '-')
|
||||
{
|
||||
$token = substr($token, 1);
|
||||
$neg = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$neg = false;
|
||||
}
|
||||
|
||||
$pos = strpos($token, ':');
|
||||
if ($pos === false)
|
||||
{
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::filterTag($dbQuery, $token);
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = substr($token, 0, $pos);
|
||||
$val = substr($token, $pos + 1);
|
||||
|
||||
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
||||
if (method_exists(__CLASS__, $methodName))
|
||||
{
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::$methodName($dbQuery, $val);
|
||||
}
|
||||
|
||||
elseif ($key == 'order')
|
||||
{
|
||||
if ($neg)
|
||||
$orderToken = $val;
|
||||
else
|
||||
$orderToken = '-' . $val;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw new SimpleException('Unknown key "' . $key . '"');
|
||||
}
|
||||
}
|
||||
|
||||
self::order($dbQuery, $orderToken);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
<?php
|
||||
class Model_Tag extends RedBean_SimpleModel
|
||||
class Model_Tag extends AbstractModel
|
||||
{
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$tag = R::findOne('tag', 'LOWER(name) = LOWER(?)', [$key]);
|
||||
$tag = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$key]);
|
||||
if (!$tag)
|
||||
{
|
||||
if ($throw)
|
||||
@ -13,6 +13,24 @@ class Model_Tag extends RedBean_SimpleModel
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public static function removeUnused()
|
||||
{
|
||||
$dbQuery = R::$f
|
||||
->begin()
|
||||
->select('id, name')
|
||||
->from(self::getTableName())
|
||||
->where()
|
||||
->not()->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.tag_id = tag.id')
|
||||
->close();
|
||||
$rows = $dbQuery->get();
|
||||
$entities = R::convertToBeans(self::getTableName(), $rows);
|
||||
R::trashAll($entities);
|
||||
}
|
||||
|
||||
public static function insertOrUpdate($tags)
|
||||
{
|
||||
$dbTags = [];
|
||||
@ -21,7 +39,7 @@ class Model_Tag extends RedBean_SimpleModel
|
||||
$dbTag = self::locate($tag, false);
|
||||
if (!$dbTag)
|
||||
{
|
||||
$dbTag = R::dispense('tag');
|
||||
$dbTag = R::dispense(self::getTableName());
|
||||
$dbTag->name = $tag;
|
||||
R::store($dbTag);
|
||||
}
|
||||
@ -50,6 +68,14 @@ class Model_Tag extends RedBean_SimpleModel
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public function getPostCount()
|
||||
{
|
||||
if ($this->bean->getMeta('post_count'))
|
||||
return $this->bean->getMeta('post_count');
|
||||
return $this->bean->countShared('post');
|
||||
}
|
||||
|
||||
|
||||
public static function validateTags($tags)
|
||||
{
|
||||
$tags = trim($tags);
|
||||
@ -65,4 +91,14 @@ class Model_Tag extends RedBean_SimpleModel
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'tag';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Tag_Querybuilder';
|
||||
}
|
||||
}
|
||||
|
37
src/Models/Model_Tag_QueryBuilder.php
Normal file
37
src/Models/Model_Tag_QueryBuilder.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
class model_Tag_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
$limitQuery = false;
|
||||
$dbQuery
|
||||
->addSql(', COUNT(post_tag.post_id)')
|
||||
->as('post_count')
|
||||
->from('tag')
|
||||
->innerJoin('post_tag')
|
||||
->on('tag.id = post_tag.tag_id')
|
||||
->innerJoin('post')
|
||||
->on('post.id = post_tag.post_id')
|
||||
->where('safety IN (' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
|
||||
if ($query !== null)
|
||||
{
|
||||
$limitQuery = true;
|
||||
if (strlen($query) >= 3)
|
||||
$query = '%' . $query;
|
||||
$query .= '%';
|
||||
$dbQuery
|
||||
->and('LOWER(tag.name)')
|
||||
->like('LOWER(?)')
|
||||
->put($query);
|
||||
}
|
||||
|
||||
$dbQuery->groupBy('tag.id');
|
||||
|
||||
if ($limitQuery)
|
||||
$dbQuery->limit(15);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
<?php
|
||||
class Model_User extends RedBean_SimpleModel
|
||||
class Model_User extends AbstractModel
|
||||
{
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$user = R::findOne('user', 'name = ?', [$key]);
|
||||
$user = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
||||
if (!$user)
|
||||
{
|
||||
if ($throw)
|
||||
@ -41,17 +41,6 @@ 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;
|
||||
@ -60,13 +49,17 @@ class Model_User extends RedBean_SimpleModel
|
||||
{
|
||||
$all = $this->getSetting(self::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
return true;
|
||||
return $safety == PostSafety::Safe;
|
||||
return $all & PostSafety::toFlag($safety);
|
||||
}
|
||||
|
||||
public function enableSafety($safety, $enabled)
|
||||
{
|
||||
$new = $this->getSetting(self::SETTING_SAFETY);
|
||||
$all = $this->getSetting(self::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
$all = PostSafety::toFlag(PostSafety::Safe);
|
||||
|
||||
$new = $all;
|
||||
if (!$enabled)
|
||||
{
|
||||
$new &= ~PostSafety::toFlag($safety);
|
||||
@ -77,6 +70,7 @@ class Model_User extends RedBean_SimpleModel
|
||||
{
|
||||
$new |= PostSafety::toFlag($safety);
|
||||
}
|
||||
|
||||
$this->setSetting(self::SETTING_SAFETY, $new);
|
||||
}
|
||||
|
||||
@ -99,7 +93,7 @@ class Model_User extends RedBean_SimpleModel
|
||||
{
|
||||
$userName = trim($userName);
|
||||
|
||||
$dbUser = R::findOne('user', 'name = ?', [$userName]);
|
||||
$dbUser = R::findOne(self::getTableName(), 'name = ?', [$userName]);
|
||||
if ($dbUser !== null)
|
||||
{
|
||||
if (!$dbUser->email_confirmed and \Chibi\Registry::getConfig()->registration->needEmailForRegistering)
|
||||
@ -170,4 +164,13 @@ class Model_User extends RedBean_SimpleModel
|
||||
return sha1($salt1 . $salt2 . $pass);
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_User_QueryBuilder';
|
||||
}
|
||||
}
|
||||
|
31
src/Models/Model_User_QueryBuilder.php
Normal file
31
src/Models/Model_User_QueryBuilder.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
class Model_User_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$sortStyle = $query;
|
||||
$dbQuery->from('user');
|
||||
|
||||
switch ($sortStyle)
|
||||
{
|
||||
case 'alpha,asc':
|
||||
$dbQuery->orderBy('name')->asc();
|
||||
break;
|
||||
case 'alpha,desc':
|
||||
$dbQuery->orderBy('name')->desc();
|
||||
break;
|
||||
case 'date,asc':
|
||||
$dbQuery->orderBy('join_date')->asc();
|
||||
break;
|
||||
case 'date,desc':
|
||||
$dbQuery->orderBy('join_date')->desc();
|
||||
break;
|
||||
case 'pending':
|
||||
$dbQuery->where('staff_confirmed IS NULL');
|
||||
$dbQuery->or('staff_confirmed = 0');
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown sort style');
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ class Privilege extends Enum
|
||||
const EditPostTags = 7;
|
||||
const EditPostThumb = 8;
|
||||
const EditPostSource = 26;
|
||||
const EditPostRelations = 30;
|
||||
const HidePost = 9;
|
||||
const DeletePost = 10;
|
||||
const FeaturePost = 25;
|
||||
@ -33,4 +34,5 @@ class Privilege extends Enum
|
||||
const ListTags = 21;
|
||||
const MergeTags = 27;
|
||||
const RenameTags = 27;
|
||||
const MassTag = 29;
|
||||
}
|
||||
|
1
src/Upgrades/Upgrade2.sql
Normal file
1
src/Upgrades/Upgrade2.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE user ADD COLUMN banned INTEGER;
|
10
src/Upgrades/Upgrade3.sql
Normal file
10
src/Upgrades/Upgrade3.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE crossref
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
post_id INTEGER,
|
||||
post2_id INTEGER,
|
||||
FOREIGN KEY(post_id) REFERENCES post(id) ON DELETE CASCADE ON UPDATE SET NULL,
|
||||
FOREIGN KEY(post2_id) REFERENCES post(id) ON DELETE CASCADE ON UPDATE SET NULL
|
||||
);
|
||||
CREATE INDEX idx_fk_crossref_post_id ON crossref(post_id);
|
||||
CREATE INDEX idx_fk_crossref_post2_id ON crossref(post2_id);
|
@ -16,8 +16,11 @@
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="input-wrapper">
|
||||
<input type="hidden" name="remember" value="0"/>
|
||||
<label>
|
||||
<input type="checkbox" name="remember" value="1"/>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -2,10 +2,10 @@
|
||||
<div class="avatar">
|
||||
<?php if ($this->context->comment->commenter): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->comment->commenter->name]) ?>">
|
||||
<img src="<?php echo htmlspecialchars($this->context->comment->commenter->getAvatarUrl(40)) ?>" alt="<?php echo $this->context->comment->commenter->name ?: '[deleted user]' ?>"/>
|
||||
<img src="<?php echo htmlspecialchars($this->context->comment->commenter->getAvatarUrl(40)) ?>" alt="<?php echo $this->context->comment->commenter->name ?: '[unknown user]' ?>"/>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[deleted user]">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[unknown user]">
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<?php echo $this->context->comment->commenter->name ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
[deleted user]
|
||||
[unknown user]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
|
@ -21,10 +21,14 @@
|
||||
<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>commented by at least three users: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'commentmin:3']) ?>"><code>commentmin:3</code></a></li>
|
||||
<li>tagged with at least seven tags: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'tagmin:7']) ?>"><code>tagmin:7</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><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><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>
|
||||
<li>having specific ID: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'id:1,2,3,8']) ?>"><code>id:1,2,3,8</code></a></li>
|
||||
<li>having ID no less than specified value: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'idmin:28']) ?>"><code>idmin:28</code></a></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><span class="comma">, </span><a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:yt'] )?>"><code>type:yt</code></a> (images, flash files and Youtube videos, 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>
|
||||
|
@ -27,12 +27,6 @@
|
||||
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')];
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::UploadPost))
|
||||
$nav []= ['Upload', \Chibi\UrlHelper::route('post', 'upload')];
|
||||
|
||||
@ -67,7 +61,7 @@
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($this->context->loggedIn): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($this->context->user))): ?>
|
||||
<li class="safety">
|
||||
<ul>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
|
@ -27,6 +27,13 @@
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="relations">Relations:</label>
|
||||
<div class="input-wrapper"><input type="text" name="relations" id="relations" placeholder="id1,id2,…" value="<?php echo join(',', array_map(function($post) { return $post->id; }, $this->context->transport->post->via('crossref')->sharedPost)) ?>"/></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>
|
||||
|
33
src/Views/post-list-wrapper.phtml
Normal file
33
src/Views/post-list-wrapper.phtml
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
$tabs = [];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['All posts', \Chibi\UrlHelper::route('post', 'list')];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['Random', \Chibi\UrlHelper::route('post', 'random')];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['Favorites', \Chibi\UrlHelper::route('post', 'favorites')];
|
||||
if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs []= ['Mass tag', \Chibi\UrlHelper::route('post', 'list', ['query' => isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '', 'source' => 'mass-tag', 'page' => $this->context->transport->paginator->page])];
|
||||
|
||||
$activeTab = 0;
|
||||
if ($this->context->route->simpleActionName == 'random') $activeTab = 1;
|
||||
if ($this->context->route->simpleActionName == 'favorites') $activeTab = 2;
|
||||
if ($this->context->source == 'mass-tag') $activeTab = 3;
|
||||
?>
|
||||
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<?php foreach ($tabs as $i => $tab): ?>
|
||||
<?php list($name, $url) = $tab ?>
|
||||
<?php if ($i == $activeTab): ?>
|
||||
<li class="selected <?php echo TextHelper::humanCaseToKebabCase($name) ?>">
|
||||
<?php else: ?>
|
||||
<li class="<?php echo TextHelper::humanCaseToKebabCase($name) ?>">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo $url ?>">
|
||||
<?php echo $name ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<?php $this->renderFile('post-list') ?>
|
@ -1,3 +1,7 @@
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag' and PrivilegesHelper::confirm(Privilege::MassTag)): ?>
|
||||
<?php $this->renderFile('tag-mass-tag') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (empty($this->context->transport->posts)): ?>
|
||||
<p class="alert alert-warning">No posts to show.</p>
|
||||
<?php else: ?>
|
||||
|
@ -1,9 +1,22 @@
|
||||
<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', ['name' => $this->context->post->name]) ?>" alt="@<?php echo $this->context->post->id ?>"/>
|
||||
<?php $classNames = ['post', 'post-type-' . TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post->type))] ?>
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag'): ?>
|
||||
<?php $classNames []= 'taggable' ?>
|
||||
<?php if (in_array($this->context->additionalInfo, array_map(function($x) { return $x->name; }, $this->context->post->sharedTag))): ?>
|
||||
<?php $classNames []= 'tagged' ?>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="<?php echo implode(' ', $classNames) ?>">
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag'): ?>
|
||||
<a class="toggle-tag" href="<?php echo \Chibi\UrlHelper::route('post', 'toggle-tag', ['id' => $this->context->post->id, 'tag' => $this->context->additionalInfo]) ?>" data-text-tagged="Tagged" data-text-untagged="Untagged">
|
||||
<?php echo in_array('tagged', $classNames) ? 'Tagged' : 'Untagged' ?>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
<a class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $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 ?>"/>
|
||||
<div class="info-bar">
|
||||
<i class="icon-comments"></i> <span><?php echo $this->context->post->countOwn('comment') ?></span>
|
||||
<i class="icon-favs"></i> <span><?php echo $this->context->post->countOwn('favoritee') ?></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -6,8 +6,8 @@
|
||||
<div class="unit">
|
||||
<h1>file upload</h1>
|
||||
<p>Use tags to describe uploaded images. Try to specify characters, their look and shows they are from.</p>
|
||||
<p>Set proper visibility setting if the image isn’t safe for work or you’re not sure it’s 100% <span class="safety-sfw">safe</span>.</p>
|
||||
<p>Only registered users can view <span class="safety-sketchy">sketchy</span> or <span class="safety-nsfw">NSFW</span> content.</p>
|
||||
<p>Set proper visibility setting if the image isn’t safe for work or you’re not sure it’s 100% <span class="safety-safe">safe</span>.</p>
|
||||
<p>Only registered users can view <span class="safety-sketchy">sketchy</span> or <span class="safety-unsafe">NSFW</span> content.</p>
|
||||
<p>Click submit when you’re done.</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,6 +101,12 @@
|
||||
<?php $checked = true ?>
|
||||
</label>
|
||||
<?php endforeach ?>
|
||||
<input type="hidden" name="anonymous" value="0"/>
|
||||
<label>
|
||||
<input type="checkbox" name="anonymous" value="1"/>
|
||||
Upload anonymously
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="tags">
|
||||
|
@ -28,13 +28,15 @@
|
||||
<div class="unit tags">
|
||||
<h1>tags (<?php echo count($this->context->transport->post->sharedTag) ?>)</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->sharedTag as $tag): ?>
|
||||
<?php $tags = $this->context->transport->post->sharedTag ?>
|
||||
<?php uasort($tags, function($a, $b) { return strnatcasecmp($a->name, $b->name); }) ?>
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<li title="<?php echo $tag->name ?>">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>">
|
||||
<?php echo $tag->name ?>
|
||||
</a>
|
||||
<span class="count">
|
||||
<?php echo TextHelper::useDecimalUnits($this->context->transport->tagDistribution[$tag->name]) ?>
|
||||
<?php echo TextHelper::useDecimalUnits($tag->getPostCount()) ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
@ -54,15 +56,16 @@
|
||||
</a>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="value" title="[deleted user]">
|
||||
[deleted user]
|
||||
<span class="value" title="[unknown user]">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[unknown user]"/>
|
||||
[unknown user]
|
||||
</span>
|
||||
<?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)) ?>">
|
||||
<span class="value safety-<?php echo $val = TextHelper::camelCaseToHumanCase(PostSafety::toString($this->context->transport->post->safety)) ?>" title="<?php echo $val ?>">
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
</div>
|
||||
@ -87,8 +90,12 @@
|
||||
|
||||
<div class="key-value source">
|
||||
<span class="key">Source:</span>
|
||||
<span class="value" title="<?php echo $val = htmlspecialchars($this->context->transport->post->source) ?>">
|
||||
<span class="value" title="<?php echo $val = htmlspecialchars($this->context->transport->post->source ?: 'unknown') ?>">
|
||||
<?php if (preg_match('/^((https?|ftp):|)\/\//', $val)): ?>
|
||||
<a href="<?php echo $val ?>"><?php echo $val ?></a>
|
||||
<?php else: ?>
|
||||
<?php echo $val ?>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -127,6 +134,21 @@
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?php if (count($this->context->transport->post->via('crossref')->sharedPost)): ?>
|
||||
<div class="relations unit">
|
||||
<h1>related</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->via('crossref')->sharedPost as $relatedPost): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $relatedPost->id]) ?>">
|
||||
@<?php echo $relatedPost->id ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="unit options">
|
||||
<h1>options</h1>
|
||||
|
||||
|
41
src/Views/tag-list-wrapper.phtml
Normal file
41
src/Views/tag-list-wrapper.phtml
Normal file
@ -0,0 +1,41 @@
|
||||
<?php $tabs = [] ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListTags)) $tabs['list'] = 'List'; ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::RenameTags)) $tabs['rename'] = 'Rename'; ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::MergeTags)) $tabs['merge'] = 'Merge'; ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs['mass-tag-redirect'] = 'Mass tag'; ?>
|
||||
|
||||
<?php if (count(array_diff($tabs, ['list'])) > 1): ?>
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<?php foreach ($tabs as $tab => $name): ?>
|
||||
<?php if ($this->context->route->simpleActionName == $tab): ?>
|
||||
<li class="selected <?php echo $tab ?>">
|
||||
<?php else: ?>
|
||||
<li class="<?php echo $tab ?>">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('tag', $tab) ?>">
|
||||
<?php echo $name ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'merge'): ?>
|
||||
<?php $this->renderFile('tag-merge') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'rename'): ?>
|
||||
<?php $this->renderFile('tag-rename') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'list'): ?>
|
||||
<?php $this->renderFile('tag-list') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'mass-tag-redirect'): ?>
|
||||
<?php $this->renderFile('tag-mass-tag') ?>
|
||||
<?php endif ?>
|
@ -1,60 +1,17 @@
|
||||
<?php $max = max($this->context->transport->tagDistribution) ?>
|
||||
<?php $max = max([0]+array_map(function($x) { return $x['post_count']; }, $this->context->transport->tags)); ?>
|
||||
<?php $add = 0.25 ?>
|
||||
<?php $mul = 0.75 / max(1, log(max(1, $max))) ?>
|
||||
<?php $url = \Chibi\UrlHelper::route('post', 'list', ['query' => '{query}']) ?>
|
||||
<div class="tags">
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->tagDistribution as $tagName => $count): ?>
|
||||
<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 . ')' ?>
|
||||
<?php foreach ($this->context->transport->tags as $tag): ?>
|
||||
<?php $name = $tag['name'] ?>
|
||||
<?php $count = $tag['post_count'] ?>
|
||||
<li class="tag" title="<?php echo $name ?> (<?php echo $count ?>)">
|
||||
<a href="<?php echo TextHelper::replaceTokens($url, ['query' => $name]) ?>" style="opacity: <?php printf('%.02f', $add + $mul * log($count)) ?>">
|
||||
<?php echo $name . ' (' . $count . ')' ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::MergeTags)): ?>
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'merge') ?>">
|
||||
<h1>merge tags</h1>
|
||||
<div>
|
||||
<label class="left" for="merge-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="merge-source-tag"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="merge-target-tag">Target tag:</label>
|
||||
<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"> </label>
|
||||
<button type="submit">Merge!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::RenameTags)): ?>
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned simple-action" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'rename') ?>">
|
||||
<h1>rename tags</h1>
|
||||
<div>
|
||||
<label class="left" for="rename-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="rename-source-tag"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="rename-target-tag">Target tag:</label>
|
||||
<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"> </label>
|
||||
<button type="submit">Rename!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
21
src/Views/tag-mass-tag.phtml
Normal file
21
src/Views/tag-mass-tag.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'mass-tag-redirect') ?>">
|
||||
<h1>mass tag</h1>
|
||||
<div>
|
||||
<label class="left" for="mass-tag-query">Search query:</label>
|
||||
<div class="input-wrapper"><input type="text" name="query" id="mass-tag-query" value="<?php echo isset($this->context->massTagQuery) ? $this->context->massTagQuery : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="mass-tag-tag">Tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tag" id="mass-tag-tag" value="<?php echo isset($this->context->massTagTag) ? $this->context->massTagTag : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Tag!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
21
src/Views/tag-merge.phtml
Normal file
21
src/Views/tag-merge.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'merge') ?>">
|
||||
<h1>merge tags</h1>
|
||||
<div>
|
||||
<label class="left" for="merge-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="merge-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="merge-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="target-tag" id="merge-target-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Merge!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
21
src/Views/tag-rename.phtml
Normal file
21
src/Views/tag-rename.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned simple-action" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'rename') ?>">
|
||||
<h1>rename tags</h1>
|
||||
<div>
|
||||
<label class="left" for="rename-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="rename-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="rename-target-tag">Target tag:</label>
|
||||
<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"> </label>
|
||||
<button type="submit">Rename!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
define('SZURU_VERSION', '0.2.0');
|
||||
define('SZURU_VERSION', '0.3.0');
|
||||
define('SZURU_LINK', 'http://github.com/rr-/szurubooru');
|
||||
|
||||
function trueStartTime()
|
||||
|
Reference in New Issue
Block a user