mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc30829c63 | |||
9ab961985d | |||
fdee23af99 | |||
3c41940142 | |||
da63c0fd19 | |||
4fd25b10c6 | |||
210342a5bf | |||
69a993c5af | |||
7b473ba06f | |||
4166200dbc | |||
4e64431a96 | |||
6582b395d2 | |||
04e9bad79e | |||
45e9d32f58 | |||
bb01ae7fca | |||
039d56c260 | |||
76a60ed5d7 | |||
fb02feeed3 | |||
9ec269330c | |||
8cd457848c | |||
70a4b46cf1 | |||
202c820a9a | |||
5e30253789 | |||
6fadc612fd | |||
7faf46beb9 | |||
7b014f036b |
30
config.ini
30
config.ini
@ -1,5 +1,4 @@
|
||||
[chibi]
|
||||
userCodeDir=./src/
|
||||
prettyPrint=1
|
||||
|
||||
[main]
|
||||
@ -8,9 +7,12 @@ filesPath=./files/
|
||||
thumbsPath=./thumbs/
|
||||
mediaPath=./public_html/media/
|
||||
title=szurubooru
|
||||
salt = "1A2/$_4xVa"
|
||||
logsPath=./logs/
|
||||
|
||||
[misc]
|
||||
featuredPostMaxDays=7
|
||||
debugQueries=0
|
||||
salt = "1A2/$_4xVa"
|
||||
|
||||
[browsing]
|
||||
usersPerPage=8
|
||||
@ -39,15 +41,25 @@ needEmailForRegistering = 1
|
||||
needEmailForCommenting = 0
|
||||
needEmailForUploading = 1
|
||||
confirmationEmailEnabled = 1
|
||||
confirmationEmailSenderName = "{host} registration engine"
|
||||
confirmationEmailSenderName = "{host} mailing system"
|
||||
confirmationEmailSenderEmail = "noreply@{host}"
|
||||
confirmationEmailSubject = "{host} activation"
|
||||
confirmationEmailSubject = "{host} - account activation"
|
||||
confirmationEmailBody = "Hello,
|
||||
|
||||
You received this e-mail because someone registered a user with this address at {host}. If it's you, visit {link} to finish registration process, otherwise you may ignore and delete this e-mail.
|
||||
You received this e-mail because someone registered a user with this e-mail address at {host}. If it's you, visit {link} to finish registration process, otherwise you may ignore and delete this e-mail.
|
||||
|
||||
Kind regards,
|
||||
{host} registration engine"
|
||||
{host} mailing system"
|
||||
|
||||
passwordResetEmailSenderName = "{host} mailing system"
|
||||
passwordResetEmailSenderEmail = "noreply@{host}"
|
||||
passwordResetEmailSubject = "{host} - password reset"
|
||||
passwordResetEmailBody = "Hello,
|
||||
|
||||
You received this e-mail because someone requested a password reset for user with this e-mail address at {host}. If it's you, visit {link} to finish password reset process, otherwise you may ignore and delete this e-mail.
|
||||
|
||||
Kind regards,
|
||||
{host} mailing system"
|
||||
|
||||
[privileges]
|
||||
uploadPost=registered
|
||||
@ -73,6 +85,8 @@ hidePost.all=moderator
|
||||
deletePost.own=moderator
|
||||
deletePost.all=moderator
|
||||
featurePost=moderator
|
||||
scorePost=registered
|
||||
flagPost=registered
|
||||
|
||||
listUsers=registered
|
||||
viewUser=registered
|
||||
@ -91,6 +105,7 @@ banUser.own=nobody
|
||||
banUser.all=admin
|
||||
deleteUser.own=registered
|
||||
deleteUser.all=nobody
|
||||
flagUser=registered
|
||||
|
||||
listComments=anonymous
|
||||
addComment=registered
|
||||
@ -101,3 +116,6 @@ listTags=anonymous
|
||||
mergeTags=moderator
|
||||
renameTags=moderator
|
||||
massTag=moderator
|
||||
|
||||
listLogs=moderator
|
||||
viewLog=moderator
|
||||
|
2
init.php
2
init.php
@ -50,7 +50,7 @@ download('http://raw.github.com/aehlke/tag-it/master/css/jquery.tagit.css', $lib
|
||||
download('http://raw.github.com/aehlke/tag-it/master/js/tag-it.min.js', $libPath . 'tagit' . DS . 'jquery.tagit.js');
|
||||
|
||||
//Mousetrap
|
||||
download('https://raw.github.com/ccampbell/mousetrap/master/mousetrap.min.js', $libPath . 'mousetrap' . DS . 'mousetrap.min.js');
|
||||
download('http://raw.github.com/ccampbell/mousetrap/master/mousetrap.min.js', $libPath . 'mousetrap' . DS . 'mousetrap.min.js');
|
||||
|
||||
//fonts
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans.ttf', $fontsPath . 'DroidSans.ttf');
|
||||
|
Submodule lib/chibi-core updated: 0ec5cbda4b...bf824e781c
2
logs/.gitignore
vendored
Normal file
2
logs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
chdir('..');
|
||||
require_once 'src/core.php';
|
||||
require_once 'src/Bootstrap.php';
|
||||
|
||||
$query = $_SERVER['REQUEST_URI'];
|
||||
\Chibi\Facade::run($query, configFactory(), new Bootstrap());
|
||||
\Chibi\Facade::run($query, new Bootstrap());
|
||||
|
@ -11,3 +11,20 @@ form.auth p {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
form.auth .help {
|
||||
opacity: .5;
|
||||
margin-top: 1em;
|
||||
font-size: small;
|
||||
}
|
||||
form.auth .help p {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
form.auth .help label+div {
|
||||
float: left;
|
||||
}
|
||||
form.auth .help ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -28,26 +28,9 @@
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
#content .header .tags:before {
|
||||
margin: 0 0.5em;
|
||||
content: '\2013';
|
||||
}
|
||||
#content .header ul {
|
||||
list-style-type: none;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#content .header li {
|
||||
display: inline;
|
||||
}
|
||||
#content .header li:not(:last-child) a:after {
|
||||
content: ', ';
|
||||
}
|
||||
|
||||
#content .body {
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAJElEQVQImWNgYGBgePfu3X8YZoABFA6SIqwS+HXgtANZF7IEAJnGPJE70lLLAAAAAElFTkSuQmCC');
|
||||
margin: 1em 0;
|
||||
margin-top: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
#content .body img {
|
||||
@ -59,11 +42,26 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
#content .header .favs-comments {
|
||||
margin-left: 0.5em;
|
||||
#content .footer {
|
||||
font-size: small;
|
||||
color: dimgray;
|
||||
margin: 0.5em 0 3em 0;
|
||||
}
|
||||
#content .footer .left {
|
||||
float: left;
|
||||
}
|
||||
#content .footer .right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#content .footer {
|
||||
text-align: right;
|
||||
#content .footer ul {
|
||||
list-style-type: none;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#content .footer li {
|
||||
display: inline;
|
||||
}
|
||||
#content .footer li:not(:last-child) a:after {
|
||||
content: ', ';
|
||||
}
|
||||
|
13
public_html/media/css/logs.css
Normal file
13
public_html/media/css/logs.css
Normal file
@ -0,0 +1,13 @@
|
||||
#content input {
|
||||
margin: 0 1em;
|
||||
height: 25px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
pre strong {
|
||||
background: #fee;
|
||||
}
|
@ -84,7 +84,6 @@
|
||||
|
||||
.post .info-bar {
|
||||
display: none;
|
||||
height: 20px;
|
||||
border-top: 1px solid firebrick;
|
||||
background: rgba(255, 128, 128, 0.75);
|
||||
position: absolute;
|
||||
@ -92,14 +91,17 @@
|
||||
left: 1px; /* border */
|
||||
right: 1px; /* border */
|
||||
bottom: 1px; /* border */
|
||||
text-align: center;
|
||||
}
|
||||
.post .link:focus .info-bar,
|
||||
.post .link:hover .info-bar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post .icon-score {
|
||||
background-position: -85px -1px;
|
||||
}
|
||||
.post .icon-comments {
|
||||
margin-left: 3px;
|
||||
background-position: -64px -1px;
|
||||
}
|
||||
.post .icon-favs {
|
||||
@ -120,3 +122,6 @@
|
||||
margin-right: 0.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
.post .link span.inactive {
|
||||
display: none;
|
||||
}
|
||||
|
@ -65,6 +65,10 @@ embed {
|
||||
color: #df4b0d;
|
||||
}
|
||||
|
||||
#sidebar .score .selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
i.icon-prev {
|
||||
background-position: -12px -1px;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 612 B After Width: | Height: | Size: 766 B |
@ -39,13 +39,9 @@ $(function()
|
||||
$.get(url, function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
window.location.reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
}
|
||||
alert(data['message']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -88,12 +84,14 @@ $(function()
|
||||
{
|
||||
if (aDom.hasAttr('data-redirect-url'))
|
||||
window.location.href = aDom.attr('data-redirect-url');
|
||||
else if (aDom.data('callback'))
|
||||
aDom.data('callback')();
|
||||
else
|
||||
window.location.reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
alert(data['message']);
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
@ -115,16 +113,13 @@ $(function()
|
||||
});
|
||||
});
|
||||
});
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
|
||||
|
||||
|
||||
//modify DOM on small viewports
|
||||
$(window).resize(function()
|
||||
function processSidebar()
|
||||
{
|
||||
if ($('body').width() == $('body').data('last-width'))
|
||||
return;
|
||||
$('#inner-content .unit').addClass('bottom-unit');
|
||||
if ($('body').width() < 600)
|
||||
{
|
||||
@ -139,10 +134,15 @@ $(window).resize(function()
|
||||
$('#sidebar .unit').removeClass('bottom-unit').addClass('left-unit');
|
||||
}
|
||||
$('body').data('last-width', $('body').width());
|
||||
});
|
||||
}
|
||||
$(function()
|
||||
{
|
||||
$(window).resize();
|
||||
$(window).resize(function()
|
||||
{
|
||||
if ($('body').width() == $('body').data('last-width'))
|
||||
return;
|
||||
});
|
||||
$('body').bind('dom-update', processSidebar);
|
||||
});
|
||||
|
||||
|
||||
@ -240,4 +240,11 @@ $(function()
|
||||
Mousetrap.bind('s', function() { $('body,html').animate({scrollTop: '+=150px'}, 200); });
|
||||
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');
|
||||
Mousetrap.bind('p', function() { $('.post a').eq(0).focus(); return false; }, 'keyup');
|
||||
});
|
||||
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
|
4
public_html/media/js/logs.js
Normal file
4
public_html/media/js/logs.js
Normal file
@ -0,0 +1,4 @@
|
||||
$(function()
|
||||
{
|
||||
$('#content form input').eq(0).focus().select();
|
||||
});
|
@ -28,10 +28,11 @@ $(function()
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
alert(data['message']);
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
$(function()
|
||||
function onDomUpdate()
|
||||
{
|
||||
$('li.edit a').click(function(e)
|
||||
{
|
||||
@ -30,6 +30,30 @@ $(function()
|
||||
});
|
||||
});
|
||||
|
||||
$('.comments.unit a.simple-action').data('callback', function()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('.comments.unit').replaceWith($(data).find('.comments.unit'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
});
|
||||
|
||||
$('#sidebar a.simple-action').data('callback', function()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('#sidebar').replaceWith($(data).find('#sidebar'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', onDomUpdate);
|
||||
onDomUpdate();
|
||||
|
||||
$('form.edit-post').submit(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
@ -59,7 +83,7 @@ $(function()
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
alert(data['message']);
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
@ -104,17 +128,22 @@ $(function()
|
||||
if (preview)
|
||||
{
|
||||
formDom.find('.preview').html(data['textPreview']).show();
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.reload();
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('.comments.unit').replaceWith($(data).find('.comments.unit'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
formDom.find('textarea').val('');
|
||||
}
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
alert(data['message']);
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ $(function()
|
||||
}
|
||||
else
|
||||
{
|
||||
postDom.find('.alert').html(data['errorHtml']).slideDown();
|
||||
postDom.find('.alert').html(data['messageHtml']).slideDown();
|
||||
enableUpload();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
\Chibi\Registry::setConfig(configFactory());
|
||||
|
||||
function usage()
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
function usage()
|
||||
{
|
||||
@ -30,23 +31,23 @@ switch ($action)
|
||||
mkdir($dir, 0755, true);
|
||||
if (!is_dir($dir))
|
||||
die($dir . ' is not a dir' . PHP_EOL);
|
||||
$func = function($name) use ($dir)
|
||||
$func = function($name) use ($dir, $config)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
static $filesPath = null;
|
||||
if ($filesPath == null)
|
||||
$filesPath = configFactory()->main->filesPath;
|
||||
$filesPath = $config->main->filesPath;
|
||||
rename($filesPath . DS . $name, $dir . DS . $name);
|
||||
};
|
||||
break;
|
||||
|
||||
case '-purge':
|
||||
$func = function($name) use ($dir)
|
||||
$func = function($name) use ($dir, $config)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
static $filesPath = null;
|
||||
if ($filesPath == null)
|
||||
$filesPath = configFactory()->main->filesPath;
|
||||
$filesPath = $config->main->filesPath;
|
||||
unlink($filesPath . DS . $name);
|
||||
};
|
||||
break;
|
||||
@ -62,7 +63,6 @@ foreach (R::findAll('post') as $post)
|
||||
}
|
||||
$names = array_flip($names);
|
||||
|
||||
$config = configFactory();
|
||||
$filesPath = $config->main->filesPath;
|
||||
foreach (glob($filesPath . DS . '*') as $name)
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ class Bootstrap
|
||||
? 'layout-json'
|
||||
: 'layout-normal';
|
||||
$this->context->transport = new StdClass;
|
||||
$this->context->transport->success = null;
|
||||
StatusHelper::init();
|
||||
|
||||
AuthController::doLogIn();
|
||||
|
||||
@ -41,29 +41,24 @@ class Bootstrap
|
||||
{
|
||||
$workCallback();
|
||||
}
|
||||
catch (SimpleException $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->success = false;
|
||||
if (!$this->context->handleExceptions)
|
||||
$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 (SimpleException $e)
|
||||
{
|
||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
||||
if (!$this->context->handleExceptions)
|
||||
$this->context->viewName = 'message';
|
||||
(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);
|
||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
||||
$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);
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class AuthController
|
||||
$token = implode('|', [base64_encode($suppliedName), base64_encode($suppliedPassword)]);
|
||||
setcookie('auth', TextHelper::encrypt($token), time() + 365 * 24 * 3600, '/');
|
||||
}
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
self::redirectAfterLog();
|
||||
}
|
||||
}
|
||||
|
@ -50,10 +50,11 @@ class CommentController
|
||||
|
||||
$post = Model_Post::locate($postId);
|
||||
|
||||
$text = InputHelper::get('text');
|
||||
if (!empty($text))
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$text = InputHelper::get('text');
|
||||
$text = Model_Comment::validateText($text);
|
||||
|
||||
$comment = R::dispense('comment');
|
||||
$comment->post = $post;
|
||||
if ($this->context->loggedIn)
|
||||
@ -61,9 +62,12 @@ class CommentController
|
||||
$comment->comment_date = time();
|
||||
$comment->text = $text;
|
||||
if (InputHelper::get('sender') != 'preview')
|
||||
{
|
||||
R::store($comment);
|
||||
LogHelper::logEvent('comment-add', '{user} commented on {post}', ['post' => TextHelper::reprPost($post->id)]);
|
||||
}
|
||||
$this->context->transport->textPreview = $comment->getText();
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +82,8 @@ class CommentController
|
||||
$comment = Model_Comment::locate($id);
|
||||
R::preload($comment, ['commenter' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($comment->commenter));
|
||||
LogHelper::logEvent('comment-del', '{user} removed comment from {post}', ['post' => TextHelper::reprPost($comment->post)]);
|
||||
R::trash($comment);
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class IndexController
|
||||
$this->context->stylesheets []= 'index-index.css';
|
||||
$this->context->transport->postCount = R::$f->begin()->select('count(1)')->as('count')->from('post')->get('row')['count'];
|
||||
|
||||
$featuredPostRotationTime = $this->config->main->featuredPostMaxDays * 24 * 3600;
|
||||
$featuredPostRotationTime = $this->config->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$featuredPostId = Model_Property::get(Model_Property::FeaturedPostId);
|
||||
$featuredPostUserId = Model_Property::get(Model_Property::FeaturedPostUserId);
|
||||
|
67
src/Controllers/LogController.php
Normal file
67
src/Controllers/LogController.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
class LogController
|
||||
{
|
||||
/**
|
||||
* @route /logs
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$this->context->subTitle = 'latest logs';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListLogs);
|
||||
|
||||
$path = $this->context->rootDir . DS . $this->config->main->logsPath;
|
||||
$path = TextHelper::cleanPath($path);
|
||||
|
||||
$logs = [];
|
||||
foreach (glob($path . DS . '*.log') as $log)
|
||||
$logs []= basename($log);
|
||||
|
||||
usort($logs, function($a, $b)
|
||||
{
|
||||
return strnatcasecmp($b, $a); //reverse natcasesort
|
||||
});
|
||||
|
||||
$this->context->transport->logs = $logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /log/{name}
|
||||
* @validate name [0-9a-zA-Z._-]+
|
||||
*/
|
||||
public function viewAction($name)
|
||||
{
|
||||
$this->context->subTitle = 'logs (' . $name . ')';
|
||||
$this->context->stylesheets []= 'logs.css';
|
||||
$this->context->scripts []= 'logs.js';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewLog);
|
||||
|
||||
$name = str_replace(['/', '\\'], '', $name); //paranoia mode
|
||||
$path = $this->context->rootDir . DS . $this->config->main->logsPath . DS . $name;
|
||||
$path = TextHelper::cleanPath($path);
|
||||
if (!file_exists($path))
|
||||
throw new SimpleException('Specified log doesn\'t exist');
|
||||
|
||||
$filter = InputHelper::get('filter');
|
||||
|
||||
$lines = file_get_contents($path);
|
||||
$lines = explode(PHP_EOL, str_replace(["\r", "\n"], PHP_EOL, $lines));
|
||||
$lines = array_reverse($lines);
|
||||
|
||||
if (!empty($filter))
|
||||
$lines = array_filter($lines, function($line) use ($filter) { return stripos($line, $filter) !== false; });
|
||||
|
||||
//stylize important lines
|
||||
foreach ($lines as &$line)
|
||||
if (strpos($line, 'flag') !== false)
|
||||
$line = '**' . $line . '**';
|
||||
unset($line);
|
||||
|
||||
$lines = join(PHP_EOL, $lines);
|
||||
$lines = TextHelper::parseMarkdown($lines);
|
||||
$lines = trim($lines);
|
||||
|
||||
$this->context->transport->filter = $filter;
|
||||
$this->context->transport->name = $name;
|
||||
$this->context->transport->log = $lines;
|
||||
}
|
||||
}
|
@ -130,15 +130,21 @@ class PostController
|
||||
$tags = array_map(function($x) { return $x->name; }, $post->sharedTag);
|
||||
|
||||
if (in_array($tag, $tags))
|
||||
{
|
||||
$tags = array_diff($tags, [$tag]);
|
||||
LogHelper::logEvent('post-tag-del', '{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tags += [$tag];
|
||||
LogHelper::logEvent('post-tag-add', '{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
|
||||
$dbTags = Model_Tag::insertOrUpdate($tags);
|
||||
$post->sharedTag = $dbTags;
|
||||
|
||||
R::store($post);
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,7 +249,14 @@ class PostController
|
||||
|
||||
|
||||
/* file details */
|
||||
$mimeType = $sourcePath ? mime_content_type($sourcePath) : null;
|
||||
$mimeType = null;
|
||||
if ($sourcePath)
|
||||
{
|
||||
if (function_exists('mime_content_type'))
|
||||
$mimeType = mime_content_type($sourcePath);
|
||||
else
|
||||
$mimeType = $suppliedFile['type'];
|
||||
}
|
||||
$imageWidth = null;
|
||||
$imageHeight = null;
|
||||
switch ($mimeType)
|
||||
@ -332,7 +345,12 @@ class PostController
|
||||
}
|
||||
R::store($dbPost);
|
||||
|
||||
$this->context->transport->success = true;
|
||||
LogHelper::logEvent('post-new', '{user} added {post} tagged with {tags} marked as {safety}', [
|
||||
'post' => TextHelper::reprPost($dbPost),
|
||||
'tags' => join(', ', array_map(['TextHelper', 'reprTag'], $dbTags)),
|
||||
'safety' => PostSafety::toString($dbPost->safety)]);
|
||||
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,14 +367,17 @@ class PostController
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
LogHelper::bufferChanges();
|
||||
|
||||
/* safety */
|
||||
$suppliedSafety = InputHelper::get('safety');
|
||||
if ($suppliedSafety !== null)
|
||||
if ($suppliedSafety !== null and $suppliedSafety != $post->safety)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
|
||||
$post->safety = $suppliedSafety;
|
||||
$edited = true;
|
||||
LogHelper::logEvent('post-edit', '{user} changed safety for {post} to {safety}', ['post' => TextHelper::reprPost($post), 'safety' => PostSafety::toString($post->safety)]);
|
||||
}
|
||||
|
||||
|
||||
@ -371,8 +392,15 @@ class PostController
|
||||
|
||||
$suppliedTags = Model_Tag::validateTags($suppliedTags);
|
||||
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
|
||||
|
||||
$oldTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
|
||||
$post->sharedTag = $dbTags;
|
||||
$edited = true;
|
||||
|
||||
foreach (array_diff($oldTags, $suppliedTags) as $tag)
|
||||
LogHelper::logEvent('post-tag-del', '{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
foreach (array_diff($suppliedTags, $oldTags) as $tag)
|
||||
LogHelper::logEvent('post-tag-add', '{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
|
||||
|
||||
@ -394,17 +422,19 @@ class PostController
|
||||
|
||||
$path = $this->config->main->thumbsPath . DS . $post->name . '.custom';
|
||||
move_uploaded_file($suppliedFile['tmp_name'], $path);
|
||||
LogHelper::logEvent('post-edit', '{user} added custom thumb for {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
|
||||
|
||||
/* source */
|
||||
$suppliedSource = InputHelper::get('source');
|
||||
if ($suppliedSource !== null)
|
||||
if ($suppliedSource !== null and $suppliedSource != $post->sorce)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$suppliedSource = Model_Post::validateSource($suppliedSource);
|
||||
$post->source = $suppliedSource;
|
||||
$edited = true;
|
||||
LogHelper::logEvent('post-edit', '{user} changed source for {post} to {source}', ['post' => TextHelper::reprPost($post), 'source' => $post->source]);
|
||||
}
|
||||
|
||||
|
||||
@ -423,14 +453,46 @@ class PostController
|
||||
throw new SimpleException('Too many related posts (maximum: ' . $this->config->browsing->maxRelatedPosts . ')');
|
||||
$relatedPosts []= Model_Post::locate($relatedId);
|
||||
}
|
||||
|
||||
$oldRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
|
||||
$post->via('crossref')->sharedPost = $relatedPosts;
|
||||
|
||||
foreach (array_diff($oldRelatedIds, $relatedIds) as $post2id)
|
||||
LogHelper::logEvent('post-relation-del', '{user} removed relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
|
||||
foreach (array_diff($relatedIds, $oldRelatedIds) as $post2id)
|
||||
LogHelper::logEvent('post-relation-add', '{user} added relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
|
||||
}
|
||||
|
||||
R::store($post);
|
||||
Model_Tag::removeUnused();
|
||||
|
||||
LogHelper::flush();
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
$this->context->transport->success = true;
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{id}/flag
|
||||
*/
|
||||
public function flagAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagPost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$key = TextHelper::reprPost($post);
|
||||
|
||||
$flagged = SessionHelper::get('flagged', []);
|
||||
if (in_array($key, $flagged))
|
||||
throw new SimpleException('You already flagged this post');
|
||||
$flagged []= $key;
|
||||
SessionHelper::set('flagged', $flagged);
|
||||
|
||||
LogHelper::logEvent('post-flag', '{user} flagged {post} for moderator attention', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,14 +506,19 @@ class PostController
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$post->hidden = true;
|
||||
R::store($post);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('post-hide', '{user} hidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{id}/unhide
|
||||
*/
|
||||
@ -460,14 +527,19 @@ class PostController
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$post->hidden = false;
|
||||
R::store($post);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('post-unhide', '{user} unhidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{id}/delete
|
||||
*/
|
||||
@ -476,6 +548,7 @@ class PostController
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
//remove stuff from auxiliary tables
|
||||
@ -488,7 +561,9 @@ class PostController
|
||||
$post->sharedTag = [];
|
||||
R::store($post);
|
||||
R::trash($post);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('post-delete', '{user} deleted {post}', ['post' => TextHelper::reprPost($id)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -515,7 +590,7 @@ class PostController
|
||||
|
||||
$post->link('favoritee')->user = $this->context->user;
|
||||
R::store($post);
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -544,7 +619,36 @@ class PostController
|
||||
|
||||
unset ($post->ownFavoritee[$finalKey]);
|
||||
R::store($post);
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{id}/score/{score}
|
||||
* @validate score -1|0|1
|
||||
*/
|
||||
public function scoreAction($id, $score)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ScorePost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
|
||||
$p = R::findOne('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->context->user->id]);
|
||||
if (!$p)
|
||||
{
|
||||
$p = R::dispense('postscore');
|
||||
$p->post = $post;
|
||||
$p->user = $this->context->user;
|
||||
}
|
||||
$p->score = $score;
|
||||
R::store($p);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,7 +664,8 @@ class PostController
|
||||
Model_Property::set(Model_Property::FeaturedPostId, $post->id);
|
||||
Model_Property::set(Model_Property::FeaturedPostUserId, $this->context->user->id);
|
||||
Model_Property::set(Model_Property::FeaturedPostDate, time());
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
LogHelper::logEvent('post-feature', '{user} featured {post} on main page', ['post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
|
||||
|
||||
@ -612,16 +717,27 @@ class PostController
|
||||
$nextPost = $nextPostQuery->get('row');
|
||||
|
||||
$favorite = false;
|
||||
$score = null;
|
||||
if ($this->context->loggedIn)
|
||||
{
|
||||
foreach ($post->ownFavoritee as $fav)
|
||||
if ($fav->user->id == $this->context->user->id)
|
||||
$favorite = true;
|
||||
|
||||
$s = R::findOne('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->context->user->id]);
|
||||
if ($s)
|
||||
$score = intval($s->score);
|
||||
}
|
||||
|
||||
$flagged = in_array(TextHelper::reprPost($post), SessionHelper::get('flagged', []));
|
||||
|
||||
$this->context->stylesheets []= 'post-view.css';
|
||||
$this->context->stylesheets []= 'comment-small.css';
|
||||
$this->context->scripts []= 'post-view.js';
|
||||
$this->context->subTitle = 'showing @' . $post->id . ' – ' . join(', ', array_map(function($x) { return $x['name']; }, $post->sharedTag));
|
||||
$this->context->favorite = $favorite;
|
||||
$this->context->score = $score;
|
||||
$this->context->flagged = $flagged;
|
||||
$this->context->transport->post = $post;
|
||||
$this->context->transport->prevPostId = $prevPost ? $prevPost['id'] : null;
|
||||
$this->context->transport->nextPostId = $nextPost ? $nextPost['id'] : null;
|
||||
|
@ -69,8 +69,10 @@ class TagController
|
||||
R::store($post);
|
||||
}
|
||||
R::trash($sourceTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
$this->view->context->success = true;
|
||||
LogHelper::logEvent('tag-merge', '{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +103,8 @@ class TagController
|
||||
R::store($sourceTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
$this->context->transport->success = true;
|
||||
LogHelper::logEvent('tag-rename', '{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,27 +1,39 @@
|
||||
<?php
|
||||
class UserController
|
||||
{
|
||||
private static function sendEmailConfirmation(&$user)
|
||||
private static function sendTokenizedEmail(
|
||||
$user,
|
||||
$body,
|
||||
$subject,
|
||||
$senderName,
|
||||
$senderEmail,
|
||||
$recipientEmail,
|
||||
$tokens)
|
||||
{
|
||||
$regConfig = \Chibi\Registry::getConfig()->registration;
|
||||
|
||||
if (!$regConfig->confirmationEmailEnabled)
|
||||
//prepare unique user token
|
||||
do
|
||||
{
|
||||
$user->email_confirmed = $user->email_unconfirmed;
|
||||
$user->email_unconfirmed = null;
|
||||
return;
|
||||
$tokenText = md5(mt_rand() . uniqid());
|
||||
}
|
||||
while (R::findOne('usertoken', 'token = ?', [$tokenText]) !== null);
|
||||
$token = R::dispense('usertoken');
|
||||
$token->user = $user;
|
||||
$token->token = $tokenText;
|
||||
$token->used = false;
|
||||
$token->expires = null;
|
||||
R::store($token);
|
||||
|
||||
\Chibi\Registry::getContext()->mailSent = true;
|
||||
$tokens = [];
|
||||
$tokens['host'] = $_SERVER['HTTP_HOST'];
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', 'activation', ['token' => $user->email_token]);
|
||||
$tokens['token'] = $tokenText;
|
||||
|
||||
$body = wordwrap(TextHelper::replaceTokens($regConfig->confirmationEmailBody, $tokens), 70);
|
||||
$subject = TextHelper::replaceTokens($regConfig->confirmationEmailSubject, $tokens);
|
||||
$senderName = TextHelper::replaceTokens($regConfig->confirmationEmailSenderName, $tokens);
|
||||
$senderEmail = TextHelper::replaceTokens($regConfig->confirmationEmailSenderEmail, $tokens);
|
||||
$recipientEmail = $user->email_unconfirmed;
|
||||
$body = wordwrap(TextHelper::replaceTokens($body, $tokens), 70);
|
||||
$subject = TextHelper::replaceTokens($subject, $tokens);
|
||||
$senderName = TextHelper::replaceTokens($senderName, $tokens);
|
||||
$senderEmail = TextHelper::replaceTokens($senderEmail, $tokens);
|
||||
|
||||
if (empty($recipientEmail))
|
||||
throw new SimpleException('Destination e-mail address was not found');
|
||||
|
||||
$headers = [];
|
||||
$headers []= sprintf('MIME-Version: 1.0');
|
||||
@ -35,8 +47,50 @@ class UserController
|
||||
$headers []= sprintf('Content-Type: text/plain; charset=utf-8', $subject);
|
||||
$headers []= sprintf('X-Mailer: PHP/%s', phpversion());
|
||||
$headers []= sprintf('X-Originating-IP: %s', $_SERVER['SERVER_ADDR']);
|
||||
$subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
||||
mail($recipientEmail, $subject, $body, implode("\r\n", $headers), '-f' . $senderEmail);
|
||||
$encodedSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
||||
mail($recipientEmail, $encodedSubject, $body, implode("\r\n", $headers), '-f' . $senderEmail);
|
||||
|
||||
LogHelper::logEvent('mail', 'Sending e-mail with subject "{subject}" to {mail}', ['subject' => $subject, 'mail' => $recipientEmail]);
|
||||
}
|
||||
|
||||
private static function sendEmailChangeConfirmation($user)
|
||||
{
|
||||
$regConfig = \Chibi\Registry::getConfig()->registration;
|
||||
if (!$regConfig->confirmationEmailEnabled)
|
||||
{
|
||||
$user->email_confirmed = $user->email_unconfirmed;
|
||||
$user->email_unconfirmed = null;
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', 'activation', ['token' => '{token}']);
|
||||
|
||||
return self::sendTokenizedEmail(
|
||||
$user,
|
||||
$regConfig->confirmationEmailBody,
|
||||
$regConfig->confirmationEmailSubject,
|
||||
$regConfig->confirmationEmailSenderName,
|
||||
$regConfig->confirmationEmailSenderEmail,
|
||||
$user->email_unconfirmed,
|
||||
$tokens);
|
||||
}
|
||||
|
||||
private static function sendPasswordResetConfirmation($user)
|
||||
{
|
||||
$regConfig = \Chibi\Registry::getConfig()->registration;
|
||||
|
||||
$tokens = [];
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', 'password-reset', ['token' => '{token}']);
|
||||
|
||||
return self::sendTokenizedEmail(
|
||||
$user,
|
||||
$regConfig->passwordResetEmailBody,
|
||||
$regConfig->passwordResetEmailSubject,
|
||||
$regConfig->passwordResetEmailSenderName,
|
||||
$regConfig->passwordResetEmailSenderEmail,
|
||||
$user->email_confirmed,
|
||||
$tokens);
|
||||
}
|
||||
|
||||
|
||||
@ -83,6 +137,32 @@ class UserController
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /user/{name}/flag
|
||||
* @validate name [^\/]+
|
||||
*/
|
||||
public function flagAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagUser);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$key = TextHelper::reprUser($user);
|
||||
|
||||
$flagged = SessionHelper::get('flagged', []);
|
||||
if (in_array($key, $flagged))
|
||||
throw new SimpleException('You already flagged this user');
|
||||
$flagged []= $key;
|
||||
SessionHelper::set('flagged', $flagged);
|
||||
|
||||
LogHelper::logEvent('user-flag', '{user} flagged {subject} for moderator attention', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /user/{name}/ban
|
||||
* @validate name [^\/]+
|
||||
@ -91,14 +171,19 @@ class UserController
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = true;
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('ban', '{user} banned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{name}/unban
|
||||
* @validate name [^\/]+
|
||||
@ -107,14 +192,19 @@ class UserController
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = false;
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('unban', '{user} unbanned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{name}/accept-registration
|
||||
* @validate name [^\/]+
|
||||
@ -127,7 +217,8 @@ class UserController
|
||||
{
|
||||
$user->staff_confirmed = true;
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
LogHelper::logEvent('reg-accept', '{user} confirmed account for {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,6 +246,7 @@ class UserController
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = $user->name;
|
||||
if ($this->context->user->id == $user->id)
|
||||
{
|
||||
$suppliedPasswordHash = Model_User::hashPassword($suppliedCurrentPassword, $user->pass_salt);
|
||||
@ -176,8 +268,10 @@ class UserController
|
||||
AuthController::doLogOut();
|
||||
R::store($user);
|
||||
R::trash($user);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
$this->context->transport->success = true;
|
||||
LogHelper::logEvent('user-del', '{user} removed account for {subject}', ['subject' => TextHelper::reprUser($name)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +309,7 @@ class UserController
|
||||
if ($user->id == $this->context->user->id)
|
||||
$this->context->user = $user;
|
||||
AuthController::doReLog();
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success('Browsing settings updated!');
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,11 +344,16 @@ class UserController
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$confirmMail = false;
|
||||
LogHelper::bufferChanges();
|
||||
|
||||
if ($suppliedName != '' and $suppliedName != $user->name)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$suppliedName = Model_User::validateUserName($suppliedName);
|
||||
$oldName = $user->name;
|
||||
$user->name = $suppliedName;
|
||||
LogHelper::logEvent('user-edit', '{user} renamed {old} to {new}', ['old' => TextHelper::reprUser($oldName), 'new' => TextHelper::reprUser($suppliedName)]);
|
||||
}
|
||||
|
||||
if ($suppliedPassword1 != '')
|
||||
@ -264,6 +363,7 @@ class UserController
|
||||
throw new SimpleException('Specified passwords must be the same');
|
||||
$suppliedPassword = Model_User::validatePassword($suppliedPassword1);
|
||||
$user->pass_hash = Model_User::hashPassword($suppliedPassword, $user->pass_salt);
|
||||
LogHelper::logEvent('user-edit', '{user} changed password for {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
}
|
||||
|
||||
if ($suppliedEmail != '' and $suppliedEmail != $user->email_confirmed)
|
||||
@ -274,11 +374,14 @@ class UserController
|
||||
{
|
||||
$user->email_unconfirmed = $suppliedEmail;
|
||||
if (!empty($user->email_unconfirmed))
|
||||
self::sendEmailConfirmation($user);
|
||||
$confirmMail = true;
|
||||
LogHelper::logEvent('user-edit', '{user} changed e-mail to {mail}', ['mail' => $suppliedEmail]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$user->email_unconfirmed = null;
|
||||
$user->email_confirmed = $suppliedEmail;
|
||||
LogHelper::logEvent('user-edit', '{user} changed e-mail for {subject} to {mail}', ['subject' => TextHelper::reprUser($user), 'mail' => $suppliedEmail]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,6 +390,7 @@ class UserController
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$suppliedAccessRank = Model_User::validateAccessRank($suppliedAccessRank);
|
||||
$user->access_rank = $suppliedAccessRank;
|
||||
LogHelper::logEvent('user-edit', '{user} changed access rank for {subject} to {rank}', ['subject' => TextHelper::reprUser($user), 'rank' => AccessRank::toString($suppliedAccessRank)]);
|
||||
}
|
||||
|
||||
if ($this->context->user->id == $user->id)
|
||||
@ -296,7 +400,15 @@ class UserController
|
||||
throw new SimpleException('Must supply valid current password');
|
||||
}
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
if ($confirmMail)
|
||||
self::sendEmailChangeConfirmation($user);
|
||||
|
||||
LogHelper::flush();
|
||||
$message = 'Account settings updated!';
|
||||
if ($confirmMail)
|
||||
$message .= ' You will be sent an e-mail address confirmation message soon.';
|
||||
StatusHelper::success($message);
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
@ -347,6 +459,9 @@ class UserController
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$posts = Model_Post::getEntities($query, $postsPerPage, $page);
|
||||
|
||||
$flagged = in_array(TextHelper::reprUser($user), SessionHelper::get('flagged', []));
|
||||
|
||||
$this->context->flagged = $flagged;
|
||||
$this->context->transport->user = $user;
|
||||
$this->context->transport->tab = $tab;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
@ -376,7 +491,7 @@ class UserController
|
||||
if (!$this->context->user->anonymous)
|
||||
R::store($this->context->user);
|
||||
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
|
||||
|
||||
@ -425,17 +540,10 @@ class UserController
|
||||
$dbUser->pass_hash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
|
||||
$dbUser->email_unconfirmed = $suppliedEmail;
|
||||
|
||||
//prepare unique registration token
|
||||
do
|
||||
{
|
||||
$emailToken = md5(mt_rand() . uniqid());
|
||||
}
|
||||
while (R::findOne('user', 'email_token = ?', [$emailToken]) !== null);
|
||||
$dbUser->email_token = $emailToken;
|
||||
|
||||
$dbUser->join_date = time();
|
||||
if (R::findOne('user') === null)
|
||||
{
|
||||
//very first user
|
||||
$dbUser->access_rank = AccessRank::Admin;
|
||||
$dbUser->staff_confirmed = true;
|
||||
$dbUser->email_unconfirmed = null;
|
||||
@ -446,13 +554,26 @@ class UserController
|
||||
$dbUser->access_rank = AccessRank::Registered;
|
||||
$dbUser->staff_confirmed = false;
|
||||
$dbUser->staff_confirmed = null;
|
||||
if (!empty($dbUser->email_unconfirmed))
|
||||
self::sendEmailConfirmation($dbUser);
|
||||
}
|
||||
|
||||
//save the user to db if everything went okay
|
||||
R::store($dbUser);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
if (!empty($dbUser->email_unconfirmed))
|
||||
self::sendEmailChangeConfirmation($dbUser);
|
||||
|
||||
$message = 'Congratulations, your account was created.';
|
||||
if (!empty($this->context->mailSent))
|
||||
{
|
||||
$message .= ' Please wait for activation e-mail.';
|
||||
if ($this->config->registration->staffActivation)
|
||||
$message .= ' After this, your registration must be confirmed by staff.';
|
||||
}
|
||||
elseif ($this->config->registration->staffActivation)
|
||||
$message .= ' Your registration must be now confirmed by staff.';
|
||||
|
||||
LogHelper::logEvent('user-reg', '{subject} just signed up', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
StatusHelper::success($message);
|
||||
|
||||
if (!$this->config->registration->needEmailForRegistering and !$this->config->registration->staffActivation)
|
||||
{
|
||||
@ -470,21 +591,22 @@ class UserController
|
||||
public function activationAction($token)
|
||||
{
|
||||
$this->context->subTitle = 'account activation';
|
||||
$this->context->viewName = 'message';
|
||||
|
||||
if (empty($token))
|
||||
throw new SimpleException('Invalid activation token');
|
||||
|
||||
$dbUser = R::findOne('user', 'email_token = ?', [$token]);
|
||||
if ($dbUser === null)
|
||||
throw new SimpleException('No user with such activation token');
|
||||
|
||||
if (!$dbUser->email_unconfirmed)
|
||||
throw new SimpleException('This user was already activated');
|
||||
$dbToken = Model_Token::locate($token);
|
||||
|
||||
$dbUser = $dbToken->user;
|
||||
$dbUser->email_confirmed = $dbUser->email_unconfirmed;
|
||||
$dbUser->email_unconfirmed = null;
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
R::store($dbUser);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('user-activation', '{subject} just activated account', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
$message = 'Activation completed successfully.';
|
||||
if ($this->config->registration->staffActivation)
|
||||
$message .= ' However, your account still must be confirmed by staff.';
|
||||
StatusHelper::success($message);
|
||||
|
||||
if (!$this->config->registration->staffActivation)
|
||||
{
|
||||
@ -492,4 +614,85 @@ class UserController
|
||||
AuthController::doReLog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /password-reset/{token}
|
||||
*/
|
||||
public function passwordResetAction($token)
|
||||
{
|
||||
$this->context->subTitle = 'password reset';
|
||||
$this->context->viewName = 'message';
|
||||
|
||||
$dbToken = Model_Token::locate($token);
|
||||
|
||||
$alphabet = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
|
||||
$randomPassword = join('', array_map(function($x) use ($alphabet)
|
||||
{
|
||||
return $alphabet[$x];
|
||||
}, array_rand($alphabet, 8)));
|
||||
|
||||
$dbUser = $dbToken->user;
|
||||
$dbUser->pass_hash = Model_User::hashPassword($randomPassword, $dbUser->pass_salt);
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
R::store($dbUser);
|
||||
|
||||
LogHelper::logEvent('user-pass-reset', '{subject} just reset password', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
$message = 'Password reset successful. Your new password is **' . $randomPassword . '**.';
|
||||
StatusHelper::success($message);
|
||||
|
||||
$this->context->user = $dbUser;
|
||||
AuthController::doReLog();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /password-reset-proxy
|
||||
*/
|
||||
public function passwordResetProxyAction()
|
||||
{
|
||||
$this->context->subTtile = 'password reset';
|
||||
$this->context->viewName = 'user-select';
|
||||
$this->context->stylesheets []= 'auth.css';
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = InputHelper::get('name');
|
||||
$user = Model_User::locate($name);
|
||||
if (empty($user->email_confirmed))
|
||||
throw new SimpleException('This user has no e-mail confirmed; password reset cannot proceed');
|
||||
|
||||
self::sendPasswordResetConfirmation($user);
|
||||
StatusHelper::success('E-mail sent. Follow instructions to reset password.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /activation-proxy
|
||||
*/
|
||||
public function activationProxyAction()
|
||||
{
|
||||
$this->context->subTitle = 'account activation';
|
||||
$this->context->viewName = 'user-select';
|
||||
$this->context->stylesheets []= 'auth.css';
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = InputHelper::get('name');
|
||||
$user = Model_User::locate($name);
|
||||
if (empty($user->email_unconfirmed))
|
||||
{
|
||||
if (!empty($user->email_confirmed))
|
||||
throw new SimpleException('E-mail was already confirmed; activation skipped');
|
||||
else
|
||||
throw new SimpleException('This user has no e-mail specified; activation cannot proceed');
|
||||
}
|
||||
self::sendEmailChangeConfirmation($user);
|
||||
StatusHelper::success('Activation e-mail resent.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,20 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
{
|
||||
$this->no_markup = true;
|
||||
$this->span_gamut += ['doSpoilers' => 71];
|
||||
$this->span_gamut += ['doUsers' => 7];
|
||||
$this->span_gamut += ['doPosts' => 8];
|
||||
$this->span_gamut += ['doTags' => 9];
|
||||
$this->span_gamut += ['doAutoLinks2' => 29];
|
||||
|
||||
//fix italics/bold in the middle of sentence
|
||||
$prop = ['em_relist', 'strong_relist', 'em_strong_relist'];
|
||||
for ($i = 0; $i < 3; $i ++)
|
||||
{
|
||||
$this->{$prop[$i]}[''] = '(?:(?<!\*)' . str_repeat('\*', $i + 1) . '(?!\*)|(?<![a-zA-Z0-9_])' . str_repeat('_', $i + 1) . '(?!_))(?=\S|$)(?![\.,:;]\s)';
|
||||
$this->{$prop[$i]}[str_repeat('*', $i + 1)] = '(?<=\S|^)(?<!\*)' . str_repeat('\*', $i + 1) . '(?!\*)';
|
||||
$this->{$prop[$i]}[str_repeat('_', $i + 1)] = '(?<=\S|^)(?<!_)' . str_repeat('_', $i + 1) . '(?![a-zA-Z0-9_])';
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -43,7 +54,7 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
|
||||
protected function doPosts($text)
|
||||
{
|
||||
return preg_replace_callback('/@(\d+)/', function($x)
|
||||
return preg_replace_callback('/(?:(?<!\w))@(\d+)/', function($x)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('post', 'view', ['id' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
}, $text);
|
||||
@ -51,9 +62,17 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
|
||||
protected function doTags($text)
|
||||
{
|
||||
return preg_replace_callback('/#([a-zA-Z0-9_-]+)/', function($x)
|
||||
return preg_replace_callback('/(?:(?<!\w))#([a-zA-Z0-9_-]+)/', function($x)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('post', 'list', ['query' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
}, $text);
|
||||
}
|
||||
|
||||
protected function doUsers($text)
|
||||
{
|
||||
return preg_replace_callback('/(?:(?<!\w))\+([a-zA-Z0-9_-]+)/', function($x)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('user', 'view', ['name' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
}, $text);
|
||||
}
|
||||
}
|
||||
|
69
src/Helpers/LogHelper.php
Normal file
69
src/Helpers/LogHelper.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
class LogHelper
|
||||
{
|
||||
static $path;
|
||||
static $context;
|
||||
static $config;
|
||||
static $autoFlush;
|
||||
static $content;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$config = \Chibi\Registry::getConfig();
|
||||
self::$context = \Chibi\Registry::getContext();
|
||||
self::$path = self::$config->main->logsPath . date('Y-m') . '.log';
|
||||
self::$autoFlush = true;
|
||||
|
||||
self::$content = '';
|
||||
}
|
||||
|
||||
public static function bufferChanges()
|
||||
{
|
||||
self::$autoFlush = false;
|
||||
}
|
||||
|
||||
public static function flush()
|
||||
{
|
||||
$fh = fopen(self::getLogPath(), 'ab');
|
||||
if (!$fh)
|
||||
throw new SimpleException('Cannot write to log files');
|
||||
if (flock($fh, LOCK_EX))
|
||||
{
|
||||
fwrite($fh, self::$content);
|
||||
fflush($fh);
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
}
|
||||
self::$content = '';
|
||||
self::$autoFlush = true;
|
||||
}
|
||||
|
||||
public static function getLogPath()
|
||||
{
|
||||
return self::$path;
|
||||
}
|
||||
|
||||
public static function log($text, array $tokens = [])
|
||||
{
|
||||
if (isset(self::$context->user))
|
||||
$tokens['user'] = TextHelper::reprUser(self::$context->user->name);
|
||||
|
||||
$text = TextHelper::replaceTokens($text, $tokens);
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
|
||||
$line = sprintf('[%s] %s: %s' . PHP_EOL, $timestamp, $ip, $text);
|
||||
|
||||
self::$content .= $line;
|
||||
|
||||
if (self::$autoFlush)
|
||||
self::flush();
|
||||
}
|
||||
|
||||
public static function logEvent($event, $text, array $tokens = [])
|
||||
{
|
||||
return self::log(sprintf('[%s] %s', $event, $text), $tokens);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper::init();
|
15
src/Helpers/SessionHelper.php
Normal file
15
src/Helpers/SessionHelper.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
class SessionHelper
|
||||
{
|
||||
public static function get($key, $default = null)
|
||||
{
|
||||
if (!isset($_SESSION[$key]))
|
||||
return $default;
|
||||
return $_SESSION[$key];
|
||||
}
|
||||
|
||||
public static function set($key, $value)
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
}
|
30
src/Helpers/StatusHelper.php
Normal file
30
src/Helpers/StatusHelper.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
class StatusHelper
|
||||
{
|
||||
private static function flag($success, $message = null)
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if (!empty($message))
|
||||
{
|
||||
$context->transport->message = $message;
|
||||
$context->transport->messageHtml = TextHelper::parseMarkdown($message, true);
|
||||
}
|
||||
$context->transport->success = $success;
|
||||
}
|
||||
|
||||
public static function init()
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$context->transport->success = null;
|
||||
}
|
||||
|
||||
public static function success($message = null)
|
||||
{
|
||||
self::flag(true, $message);
|
||||
}
|
||||
|
||||
public static function failure($message = null)
|
||||
{
|
||||
self::flag(false, $message);
|
||||
}
|
||||
}
|
@ -180,6 +180,27 @@ class TextHelper
|
||||
return $output;
|
||||
}
|
||||
|
||||
public static function reprPost($post)
|
||||
{
|
||||
if (!is_object($post))
|
||||
return '@' . $post;
|
||||
return '@' . $post->id;
|
||||
}
|
||||
|
||||
public static function reprUser($user)
|
||||
{
|
||||
if (!is_object($user))
|
||||
return '+' . $user;
|
||||
return '+' . $user->name;
|
||||
}
|
||||
|
||||
public static function reprTag($tag)
|
||||
{
|
||||
if (!is_object($tag))
|
||||
return '#' . $tag;
|
||||
return '#' . $tag->name;
|
||||
}
|
||||
|
||||
public static function encrypt($text)
|
||||
{
|
||||
$salt = \Chibi\Registry::getConfig()->main->salt;
|
||||
@ -197,4 +218,14 @@ class TextHelper
|
||||
$iv = mcrypt_create_iv(mcrypt_get_iv_size($alg, $mode), MCRYPT_RAND);
|
||||
return trim(mcrypt_decrypt($alg, $salt, base64_decode($text), $mode, $iv));
|
||||
}
|
||||
|
||||
public static function cleanPath($path)
|
||||
{
|
||||
$path = str_replace(['/', '\\'], DS, $path);
|
||||
$path = preg_replace('{[^' . DS . ']+' . DS . '\.\.(' . DS . '|$)}', '', $path);
|
||||
$path = preg_replace('{(' . DS . '|^)\.' . DS . '}', '\1', $path);
|
||||
$path = preg_replace('{' . DS . '{2,}}', DS, $path);
|
||||
$path = rtrim($path, DS);
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,16 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
$dbQuery->addSql('id <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count >= ?')->put(intval($val));
|
||||
@ -273,6 +283,10 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
case 'favcount':
|
||||
$orderColumn = 'fav_count';
|
||||
break;
|
||||
case 'score':
|
||||
$orderDir *= -1;
|
||||
$orderColumn = 'score';
|
||||
break;
|
||||
case 'tag':
|
||||
case 'tags':
|
||||
case 'tagcount':
|
||||
|
25
src/Models/Model_Token.php
Normal file
25
src/Models/Model_Token.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
class Model_Token extends AbstractModel
|
||||
{
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
if (empty($key))
|
||||
throw new SimpleException('Invalid security token');
|
||||
|
||||
$token = R::findOne('usertoken', 'token = ?', [$key]);
|
||||
if ($token === null)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('No user with security token');
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($token->used)
|
||||
throw new SimpleException('This token was already used');
|
||||
|
||||
if ($token->expires !== null and time() > $token->expires)
|
||||
throw new SimpleException('This token has expired');
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
@ -4,13 +4,17 @@ class Model_User extends AbstractModel
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$user = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
||||
if (!$user)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
return $user;
|
||||
if ($user)
|
||||
return $user;
|
||||
|
||||
$user = R::findOne(self::getTableName(), 'LOWER(email_confirmed) = LOWER(?)', [trim($key)]);
|
||||
if ($user)
|
||||
return $user;
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAvatarUrl($size = 32)
|
||||
|
@ -14,6 +14,8 @@ class Privilege extends Enum
|
||||
const HidePost = 9;
|
||||
const DeletePost = 10;
|
||||
const FeaturePost = 25;
|
||||
const ScorePost = 31;
|
||||
const FlagPost = 34;
|
||||
|
||||
const ListUsers = 11;
|
||||
const ViewUser = 12;
|
||||
@ -26,6 +28,7 @@ class Privilege extends Enum
|
||||
const ChangeUserName = 18;
|
||||
const ChangeUserSettings = 28;
|
||||
const DeleteUser = 19;
|
||||
const FlagUser = 35;
|
||||
|
||||
const ListComments = 20;
|
||||
const AddComment = 23;
|
||||
@ -35,4 +38,7 @@ class Privilege extends Enum
|
||||
const MergeTags = 27;
|
||||
const RenameTags = 27;
|
||||
const MassTag = 29;
|
||||
|
||||
const ListLogs = 32;
|
||||
const ViewLog = 33;
|
||||
}
|
||||
|
30
src/Upgrades/Upgrade4.sql
Normal file
30
src/Upgrades/Upgrade4.sql
Normal file
@ -0,0 +1,30 @@
|
||||
ALTER TABLE post ADD COLUMN score INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE post SET score = 0;
|
||||
|
||||
CREATE TABLE post_score
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
post_id INTEGER,
|
||||
user_id INTEGER,
|
||||
score INTEGER,
|
||||
FOREIGN KEY(post_id) REFERENCES post(id) ON DELETE CASCADE ON UPDATE SET NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE SET NULL
|
||||
);
|
||||
CREATE INDEX idx_fk_post_score_post_id ON post_score(post_id);
|
||||
CREATE INDEX idx_fk_post_score_user_id ON post_score(user_id);
|
||||
|
||||
CREATE TRIGGER post_score_update AFTER UPDATE ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score - old.score + new.score WHERE post.id = new.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER post_score_insert AFTER INSERT ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score + new.score WHERE post.id = new.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER post_score_delete BEFORE DELETE ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score - old.score WHERE post.id = old.post_id;
|
||||
END;
|
1
src/Upgrades/Upgrade5.sql
Normal file
1
src/Upgrades/Upgrade5.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE post_score RENAME TO postscore
|
53
src/Upgrades/Upgrade6.sql
Normal file
53
src/Upgrades/Upgrade6.sql
Normal file
@ -0,0 +1,53 @@
|
||||
CREATE TABLE user2
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT,
|
||||
pass_salt TEXT,
|
||||
pass_hash TEXT,
|
||||
staff_confirmed INTEGER,
|
||||
email_unconfirmed TEXT,
|
||||
email_confirmed TEXT,
|
||||
join_date INTEGER,
|
||||
access_rank INTEGER,
|
||||
settings TEXT,
|
||||
banned INTEGER
|
||||
);
|
||||
|
||||
INSERT INTO user2
|
||||
(id,
|
||||
name,
|
||||
pass_salt,
|
||||
pass_hash,
|
||||
staff_confirmed,
|
||||
email_unconfirmed,
|
||||
email_confirmed,
|
||||
join_date,
|
||||
access_rank,
|
||||
settings,
|
||||
banned)
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
pass_salt,
|
||||
pass_hash,
|
||||
staff_confirmed,
|
||||
email_unconfirmed,
|
||||
email_confirmed,
|
||||
join_date,
|
||||
access_rank,
|
||||
settings,
|
||||
banned
|
||||
FROM user;
|
||||
|
||||
DROP TABLE user;
|
||||
ALTER TABLE user2 RENAME TO user;
|
||||
|
||||
CREATE TABLE usertoken
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
token VARCHAR(32),
|
||||
used BOOLEAN,
|
||||
expires INTEGER --TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_fk_usertoken_user_id ON usertoken(user_id);
|
@ -16,6 +16,8 @@
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="input-wrapper">
|
||||
<button type="submit">Log in</button>
|
||||
|
||||
<input type="hidden" name="remember" value="0"/>
|
||||
<label>
|
||||
<input type="checkbox" name="remember" value="1"/>
|
||||
@ -24,14 +26,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($this->context->transport->errorMessage)): ?>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php endif ?>
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"></label>
|
||||
<button type="submit">Log in</button>
|
||||
<div class="help">
|
||||
<label class="left"> </label>
|
||||
<div>
|
||||
<p>Problems logging in?</p>
|
||||
<ul>
|
||||
<li><a href="<?php echo \Chibi\UrlHelper::route('user', 'password-reset-proxy') ?>">I don't remember my password</a></li>
|
||||
<li><a href="<?php echo \Chibi\UrlHelper::route('user', 'activation-proxy') ?>">I haven't received activation e-mail</a></li>
|
||||
<li><a href="<?php echo \Chibi\UrlHelper::route('user', 'registration') ?>">I don't have an account</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1 +0,0 @@
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorHtml ?><br><a href="javascript:history.go(-1)">Go back</a></p>
|
@ -11,6 +11,7 @@
|
||||
<li>scroll up/down: <code>[W]</code><span class="comma">, </span><code>[S]</code></li>
|
||||
<li>go to newer/older post or page: <code>[A]</code><span class="comma">, </span><code>[D]</code></li>
|
||||
<li>edit post: <code>[E]</code></li>
|
||||
<li>focus first post in post list: <code>[P]</code></li>
|
||||
</ul>
|
||||
|
||||
<h1>Search syntax</h1>
|
||||
@ -22,6 +23,7 @@
|
||||
<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>having minimum score of 4: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'scoremin:4']) ?>"><code>scoremin:4</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>
|
||||
@ -40,7 +42,8 @@
|
||||
<li>newest to oldest: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:date']) ?>"><code>order:date</code></a> (pretty much default browse view)</li>
|
||||
<li>oldest to newest: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => '-order:date']) ?>"><code>-order:date</code></a></li>
|
||||
<li>most commented first: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:comments']) ?>"><code>order:comments</code></a></li>
|
||||
<li>loved by most: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:favs']) ?>"><code>order:favs</code></a></li>
|
||||
<li>loved by most: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:favs']) ?>"><code>order:favs</code></a></li>
|
||||
<li>highest scored: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:score']) ?>"><code>order:score</code></a></li>
|
||||
</ul>
|
||||
|
||||
<p>As shown with <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => '-order:date']) ?>"><code>-order:date</code></a><span class="comma">, </span>any of them can be reversed in the same way as negating other tags: by placing a dash before the tag.</p>
|
||||
|
@ -6,38 +6,41 @@
|
||||
</div>
|
||||
|
||||
<?php if (!empty($this->context->featuredPost)): ?>
|
||||
<div class="header">
|
||||
Featured image
|
||||
|
||||
<ul class="tags">
|
||||
<?php foreach ($this->context->featuredPost->sharedTag as $tag): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>">
|
||||
<?php echo $tag->name ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
|
||||
<span class="favs-comments">
|
||||
<?php printf('%d fav%s', $x = $this->context->featuredPost->countOwn('favoritee'), $x == 1 ? '' : 's') ?>, 
|
||||
<?php printf('%d comment%s', $x = $this->context->featuredPost->countOwn('comment'), $x == 1 ? '' : 's') ?>
|
||||
</span>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->featuredPost->id]) ?>">
|
||||
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->featuredPost->name]) ?>" alt="<?php echo $this->context->featuredPost->name ?>"/>
|
||||
<img title="Featured image" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->featuredPost->name]) ?>" alt="<?php echo $this->context->featuredPost->name ?>"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
Featured 
|
||||
<?php if ($this->context->featuredPostUser): ?>
|
||||
by <a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->featuredPostUser->name]) ?>"><?php echo $this->context->featuredPostUser->name ?></a>, 
|
||||
<?php endif ?>
|
||||
<?php printf('%d day%s', $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)), $x == 1 ? '' : 's') ?> ago
|
||||
<span class="left">
|
||||
Tags:
|
||||
<ul class="tags">
|
||||
<?php foreach ($this->context->featuredPost->sharedTag as $tag): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>">
|
||||
<?php echo $tag->name ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
<span class="right">
|
||||
Featured 
|
||||
<?php if ($this->context->featuredPostUser): ?>
|
||||
by <a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->featuredPostUser->name]) ?>"><?php echo $this->context->featuredPostUser->name ?></a>, 
|
||||
<?php endif ?>
|
||||
<?php $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)) ?>
|
||||
<?php if ($x == 0): ?>
|
||||
today
|
||||
<?php elseif ($x == 1):?>
|
||||
yesterday
|
||||
<?php else: ?>
|
||||
<?php printf('%d days ago', $x) ?>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
|
@ -96,12 +96,15 @@
|
||||
|
||||
<footer>
|
||||
<div class="main-wrapper">
|
||||
<span>Load: <?php echo sprintf('%.05f', microtime(true) - trueStartTime()) ?>s</span>
|
||||
<span>Load: <?php echo sprintf('%.05f', microtime(true) - $this->context->startTime) ?>s</span>
|
||||
<span>Queries: <?php echo count(queryLogger()->getLogs()) ?></span>
|
||||
<?php if ($this->config->main->debugQueries): ?>
|
||||
<?php if ($this->config->misc->debugQueries): ?>
|
||||
<pre class="debug"><?php echo join('<br>', array_map(function($x) { return preg_replace('/\s+/', ' ', $x); }, queryLogger()->getLogs())) ?></pre>
|
||||
<?php endif ?>
|
||||
<span><a href="<?php echo SZURU_LINK ?>">szurubooru v<?php echo SZURU_VERSION ?></a></span>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListLogs)): ?>
|
||||
<span><a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Logs</span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
13
src/Views/log-list.phtml
Normal file
13
src/Views/log-list.phtml
Normal file
@ -0,0 +1,13 @@
|
||||
<?php if (empty($this->context->transport->logs)): ?>
|
||||
<p class="alert alert-warning">No logs to show.</p>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->logs as $log): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('log', 'view', ['name' => $log]) ?>">
|
||||
<?php echo $log ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
11
src/Views/log-view.phtml
Normal file
11
src/Views/log-view.phtml
Normal file
@ -0,0 +1,11 @@
|
||||
<?php if (empty($this->context->transport->log)): ?>
|
||||
<p class="alert alert-warning">This log is empty. <a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Go back</a></p>
|
||||
<?php else: ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('log', 'view', ['name' => $this->context->transport->name]) ?>" method="get">
|
||||
Keep only lines that contain:
|
||||
|
||||
<input type="text" name="filter" value="<?php echo $this->context->transport->filter ?>" placeholder="any text…"/>
|
||||
</form>
|
||||
|
||||
<pre><?php echo $this->context->transport->log ?></pre>
|
||||
<?php endif ?>
|
5
src/Views/message.phtml
Normal file
5
src/Views/message.phtml
Normal file
@ -0,0 +1,5 @@
|
||||
<?php if (!empty($this->context->transport->message)): ?>
|
||||
<p class="alert <?php echo $this->context->transport->success ? 'alert-success' : 'alert-error'; ?>">
|
||||
<?php echo $this->context->transport->messageHtml ?>
|
||||
</p>
|
||||
<?php endif ?>
|
@ -14,9 +14,27 @@
|
||||
<?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>
|
||||
<?php
|
||||
$x =
|
||||
[
|
||||
'score' => $this->context->post->score,
|
||||
'comments' => $this->context->post->countOwn('comment'),
|
||||
'favs' => $this->context->post->countOwn('favoritee'),
|
||||
];
|
||||
?>
|
||||
<?php if (!empty($x)): ?>
|
||||
<div class="info-bar">
|
||||
<?php foreach ($x as $key => $val): ?>
|
||||
<?php if ($val == 0): ?>
|
||||
<span class="inactive">
|
||||
<?php else: ?>
|
||||
<span>
|
||||
<?php endif ?>
|
||||
<i class="icon-<?php echo $key ?>"></i>
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -1,128 +1,122 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p>Post created!</p>
|
||||
<?php else: ?>
|
||||
<div id="sidebar">
|
||||
<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-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>
|
||||
|
||||
<div id="sidebar">
|
||||
<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-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 id="inner-content">
|
||||
<div id="upload-step1">
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="selected file">
|
||||
<a href="#">
|
||||
Upload from file
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="url">
|
||||
<a href="#">
|
||||
Upload from URL
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="tab file">
|
||||
<input type=file multiple style="display: none"/>
|
||||
<div id="file-handler-wrapper">
|
||||
<div id="file-handler">
|
||||
Drop files here!<br>
|
||||
Or just click on this box.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab url">
|
||||
<div id="url-handler-wrapper">
|
||||
<div id="url-handler">
|
||||
<div class="input-wrapper"><textarea placeholder="Paste some URLs here, one per line." name="urls"></textarea></div>
|
||||
</div>
|
||||
<button type="submit">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div id="upload-step2" data-redirect-url="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">
|
||||
<hr>
|
||||
|
||||
<div class="posts">
|
||||
</div>
|
||||
|
||||
<div class="submit-wrapper">
|
||||
<button id="the-submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="inner-content">
|
||||
<div id="upload-step1">
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="selected file">
|
||||
<a href="#">
|
||||
Upload from file
|
||||
</a>
|
||||
</li>
|
||||
<div id="post-template" class="post">
|
||||
<p class="alert alert-error">Some kind of error</p>
|
||||
|
||||
<li class="url">
|
||||
<a href="#">
|
||||
Upload from URL
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<img class="thumbnail" src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="Thumbnail"/>
|
||||
|
||||
<div class="form-wrapper">
|
||||
<div class="ops">
|
||||
<a class="move-up-trigger">
|
||||
move up <span>↑</span>
|
||||
</a>
|
||||
|
||||
<a class="move-down-trigger">
|
||||
move down <span>↓</span>
|
||||
</a>
|
||||
|
||||
<a class="remove-trigger">
|
||||
remove <span>×</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="tab file">
|
||||
<input type=file multiple style="display: none"/>
|
||||
<div id="file-handler-wrapper">
|
||||
<div id="file-handler">
|
||||
Drop files here!<br>
|
||||
Or just click on this box.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab url">
|
||||
<div id="url-handler-wrapper">
|
||||
<div id="url-handler">
|
||||
<div class="input-wrapper"><textarea placeholder="Paste some URLs here, one per line." name="urls"></textarea></div>
|
||||
</div>
|
||||
<button type="submit">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div id="upload-step2" data-redirect-url="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">
|
||||
<hr>
|
||||
|
||||
<div class="posts">
|
||||
</div>
|
||||
|
||||
<div class="submit-wrapper">
|
||||
<button id="the-submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="post-template" class="post">
|
||||
<p class="alert alert-error">Some kind of error</p>
|
||||
|
||||
<img class="thumbnail" src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="Thumbnail"/>
|
||||
|
||||
<div class="form-wrapper">
|
||||
<div class="ops">
|
||||
<a class="move-up-trigger">
|
||||
move up <span>↑</span>
|
||||
</a>
|
||||
|
||||
<a class="move-down-trigger">
|
||||
move down <span>↓</span>
|
||||
</a>
|
||||
|
||||
<a class="remove-trigger">
|
||||
remove <span>×</span>
|
||||
</a>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>" method="post" class="aligned">
|
||||
<div class="file-name">
|
||||
<label class="left">File:</label>
|
||||
<strong>filename.jpg</strong>
|
||||
</div>
|
||||
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>" method="post" class="aligned">
|
||||
<div class="file-name">
|
||||
<label class="left">File:</label>
|
||||
<strong>filename.jpg</strong>
|
||||
</div>
|
||||
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<?php $checked = false ?>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<label>
|
||||
<input type="radio" name="safety" value="<?php echo $safety ?>"<?php if (!$checked) echo ' checked="checked"' ?>/>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
|
||||
<?php $checked = true ?>
|
||||
</label>
|
||||
<?php endforeach ?>
|
||||
<input type="hidden" name="anonymous" value="0"/>
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<?php $checked = false ?>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<label>
|
||||
<input type="checkbox" name="anonymous" value="1"/>
|
||||
Upload anonymously
|
||||
<input type="radio" name="safety" value="<?php echo $safety ?>"<?php if (!$checked) echo ' checked="checked"' ?>/>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
|
||||
<?php $checked = true ?>
|
||||
</label>
|
||||
<?php endforeach ?>
|
||||
<input type="hidden" name="anonymous" value="0"/>
|
||||
<label>
|
||||
<input type="checkbox" name="anonymous" value="1"/>
|
||||
Upload anonymously
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tags">
|
||||
<label class="left">Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" placeholder="enter some tags…"/></div>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<label class="left">Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" placeholder="enter some tags…"/></div>
|
||||
</div>
|
||||
|
||||
<div class="source">
|
||||
<label class="left">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" placeholder="where did you get this from? (optional)"/></div>
|
||||
</div>
|
||||
<div class="source">
|
||||
<label class="left">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" placeholder="where did you get this from? (optional)"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
</form>
|
||||
</div>
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
@ -70,6 +70,36 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value score">
|
||||
<span class="key">Score:</span>
|
||||
<span class="value">
|
||||
<?php echo $this->context->transport->post->score ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ScorePost)): ?>
|
||||
[
|
||||
<?php $scoreLink = \Chibi\UrlHelper::route('post', 'score', ['id' => $this->context->transport->post->id, 'score' => '{score}']) ?>
|
||||
|
||||
<?php if ($this->context->score === 1): ?>
|
||||
<a class="simple-action selected" href="<?php echo TextHelper::replaceTokens($scoreLink, ['score' => 0]) ?>">
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo TextHelper::replaceTokens($scoreLink, ['score' => 1]) ?>">
|
||||
<?php endif ?>
|
||||
vote up
|
||||
</a>
|
||||
|
||||
,
|
||||
|
||||
<?php if ($this->context->score === -1): ?>
|
||||
<a class="simple-action selected" href="<?php echo TextHelper::replaceTokens($scoreLink, ['score' => 0]) ?>">
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo TextHelper::replaceTokens($scoreLink, ['score' => -1]) ?>">
|
||||
<?php endif ?>
|
||||
down
|
||||
</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value date">
|
||||
<span class="key">Date:</span>
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d H:i', $this->context->transport->post->upload_date) ?>">
|
||||
@ -217,6 +247,20 @@
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::FlagPost)): ?>
|
||||
<li class="flag">
|
||||
<?php if ($this->context->flagged): ?>
|
||||
<a class="simple-action inactive" href="#">
|
||||
Flagged
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo \Chibi\UrlHelper::route('post', 'flag', ['id' => $this->context->transport->post->id]) ?>" data-confirm-text="Are you sure you want to flag this post?">
|
||||
Flag for moderator attention
|
||||
</a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<li class="delete">
|
||||
<a class="simple-action" href="<?php echo \Chibi\UrlHelper::route('post', 'delete', ['id' => $this->context->transport->post->id]) ?>" data-confirm-text="Are you sure you want to delete this post?" data-redirect-url="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">
|
||||
@ -277,6 +321,8 @@
|
||||
<div class="input-wrapper"><textarea name="text" cols="50" rows="3"></textarea></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<button name="sender" type="submit" value="preview">Preview</button>
|
||||
<button name="sender" type="submit" value="submit">Submit</button>
|
||||
|
@ -1,6 +0,0 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Activation completed successfully.
|
||||
<?php if ($this->config->registration->staffActivation): ?>
|
||||
<br>However, your account still must be confirmed by staff.
|
||||
<?php endif ?></p>
|
||||
<?php endif ?>
|
@ -8,11 +8,7 @@
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Account settings updated!</p>
|
||||
<?php elseif (isset($this->context->transport->errorMessage)): ?>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php endif ?>
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
|
@ -52,11 +52,7 @@
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Account settings updated! <?php if (!empty($this->context->mailSent)) echo 'You will be sent new e-mail address confirmation message soon.' ?></p>
|
||||
<?php elseif (isset($this->context->transport->errorMessage)): ?>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php endif ?>
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
|
@ -1,13 +1,5 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Congratulations, your account was created.
|
||||
<?php if (!empty($this->context->mailSent)): ?>
|
||||
<br>Please wait for activation e-mail.
|
||||
<?php if ($this->config->registration->staffActivation): ?>
|
||||
<br>After this, your registration must be confirmed by staff.
|
||||
<?php endif ?>
|
||||
<?php elseif ($this->config->registration->staffActivation): ?>
|
||||
<br>Your registration must be now confirmed by staff.
|
||||
<?php endif ?></p>
|
||||
<?php $this->renderFile('message') ?>
|
||||
<?php else: ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'register') ?>" class="auth aligned" method="post">
|
||||
<div>
|
||||
@ -38,11 +30,9 @@
|
||||
<p id="email-info">Your e-mail will be used to show your <a href="http://gravatar.com/">Gravatar</a>.<br/>Leave blank for random Gravatar.</p>
|
||||
</div>
|
||||
|
||||
<?php if (isset($this->context->transport->errorMessage)): ?>
|
||||
<div>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div>
|
||||
<?php $this->renderFile('message') ?>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
|
21
src/Views/user-select.phtml
Normal file
21
src/Views/user-select.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<?php $this->renderFile('message') ?>
|
||||
<?php else: ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route($this->context->route->simpleControllerName, $this->context->route->simpleActionName) ?>" method="post" class="auth aligned" autocomplete="off">
|
||||
<div>
|
||||
<label class="left">User:</label>
|
||||
<div class="input-wrapper">
|
||||
<input name="name" placeholder="Name or e-mail address" type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Continue</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif ?>
|
@ -23,11 +23,7 @@
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Browsing settings updated!</p>
|
||||
<?php elseif (isset($this->context->transport->errorMessage)): ?>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php endif ?>
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
|
@ -65,6 +65,20 @@
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::FlagUser)): ?>
|
||||
<li class="flag">
|
||||
<?php if ($this->context->flagged): ?>
|
||||
<a class="simple-action inactive" href="#">
|
||||
Flagged
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo \Chibi\UrlHelper::route('user', 'flag', ['name' => $this->context->transport->user->name]) ?>" data-confirm-text="Are you sure you want to flag this user?">
|
||||
Flag for moderator attention
|
||||
</a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<?php if (!$this->context->transport->user->banned): ?>
|
||||
<li class="ban">
|
||||
|
54
src/core.php
54
src/core.php
@ -1,56 +1,36 @@
|
||||
<?php
|
||||
define('SZURU_VERSION', '0.3.0');
|
||||
define('SZURU_VERSION', '0.4.0');
|
||||
define('SZURU_LINK', 'http://github.com/rr-/szurubooru');
|
||||
|
||||
function trueStartTime()
|
||||
{
|
||||
static $time = null;
|
||||
if ($time === null)
|
||||
$time = microtime(true);
|
||||
return $time;
|
||||
}
|
||||
trueStartTime();
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
$startTime = microtime(true);
|
||||
$rootDir = __DIR__ . DS . '..' . DS;
|
||||
|
||||
require_once __DIR__ . '/../lib/php-markdown/Michelf/Markdown.php';
|
||||
require_once __DIR__ . '/../lib/redbean/RedBean/redbean.inc.php';
|
||||
require_once __DIR__ . '/../lib/chibi-core/Facade.php';
|
||||
require_once __DIR__ . '/../lib/chibi-core/Registry.php';
|
||||
$requiredExtensions = ['pdo', 'pdo_sqlite', 'gd', 'openssl'];
|
||||
foreach ($requiredExtensions as $ext)
|
||||
if (!extension_loaded($ext))
|
||||
die('PHP extension "' . $ext . '" must be enabled to continue.' . PHP_EOL);
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
setlocale(LC_CTYPE, 'en_US.UTF-8');
|
||||
ini_set('memory_limit', '128M');
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
|
||||
function configFactory()
|
||||
{
|
||||
static $config = null;
|
||||
require_once $rootDir . 'lib' . DS . 'php-markdown' . DS . 'Michelf' . DS . 'Markdown.php';
|
||||
require_once $rootDir . 'lib' . DS . 'redbean' . DS . 'RedBean' . DS . 'redbean.inc.php';
|
||||
require_once $rootDir . 'lib' . DS . 'chibi-core' . DS . 'Facade.php';
|
||||
|
||||
if ($config === null)
|
||||
{
|
||||
$config = new \Chibi\Config();
|
||||
$configPaths =
|
||||
[
|
||||
__DIR__ . DS . '../config.ini',
|
||||
__DIR__ . DS . '../local.ini',
|
||||
];
|
||||
$configPaths = array_filter($configPaths, 'file_exists');
|
||||
\Chibi\AutoLoader::init(__DIR__);
|
||||
\Chibi\Facade::init();
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$context->startTime = $startTime;
|
||||
$context->rootDir = $rootDir;
|
||||
|
||||
foreach ($configPaths as $path)
|
||||
{
|
||||
$config->loadIni($path);
|
||||
}
|
||||
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
$config = configFactory();
|
||||
R::setup('sqlite:' . $config->main->dbPath);
|
||||
R::freeze(true);
|
||||
R::dependencies(['tag' => ['post'], 'favoritee' => ['post', 'user'], 'comment' => ['post', 'user']]);
|
||||
|
||||
//wire models
|
||||
\Chibi\AutoLoader::init([__DIR__ . '/../' . $config->chibi->userCodeDir, __DIR__]);
|
||||
foreach (\Chibi\AutoLoader::getAllIncludablePaths() as $path)
|
||||
if (preg_match('/Model/', $path))
|
||||
\Chibi\AutoLoader::safeInclude($path);
|
||||
|
@ -17,6 +17,7 @@ foreach ($upgrades as $upgradePath)
|
||||
{
|
||||
printf('Executing %s...' . PHP_EOL, $upgradePath);
|
||||
$upgradeSql = file_get_contents($upgradePath);
|
||||
$upgradeSql = preg_replace('/^[ \t]+(.*);/m', '\0--', $upgradeSql);
|
||||
$queries = preg_split('/;\s*[\r\n]+/s', $upgradeSql);
|
||||
$queries = array_map('trim', $queries);
|
||||
foreach ($queries as $query)
|
||||
|
Reference in New Issue
Block a user