26 Commits
0.3.0 ... 0.4.0

Author SHA1 Message Date
cc30829c63 Version upgrade (0.4.0) 2013-11-18 00:01:47 +01:00
9ab961985d Refactor to logging
- Centralized use of TextHelper::repr..() instead of hardcoded markdown
- Centralized processing of highlighting instead of hardcoded markdown
- Highlighted items are marked with color, not just bold
2013-11-17 23:46:31 +01:00
fdee23af99 Small changes
- Changed: rating posts - [up | down] --> [vote up, down]
- Fixed: logging of e-mail subject
- Improved: flagging posts/users provides visual feedback ("flagged")
- Improved: grammar in login screen
- Fixed: typo in password reset message
- Added: SessionHelper for handy management of user session data
2013-11-17 20:32:35 +01:00
3c41940142 Closed #57 2013-11-17 14:53:21 +01:00
da63c0fd19 Closed #61 2013-11-17 14:53:17 +01:00
4fd25b10c6 Fixed logging of post previews 2013-11-17 14:25:13 +01:00
210342a5bf Fixes to Markdown
- aa_bb cc_dd doesn't produce italics anymore
- asd@5.com doesn't produce link to post 5
- asd.com#anchor doesn't produce link to tag "anchor"
2013-11-17 14:25:05 +01:00
69a993c5af Fixed sending empty comments 2013-11-17 14:24:39 +01:00
7b473ba06f Low-level refactor to core.php 2013-11-17 14:24:39 +01:00
4166200dbc Post view actions don't reload the page anymore 2013-11-17 14:24:35 +01:00
4e64431a96 Changes to infobar in post thumbnails 2013-11-16 22:40:19 +01:00
6582b395d2 Added [P] hotkey for selecting first post on page 2013-11-16 22:02:18 +01:00
04e9bad79e Added logging engine for #61 2013-11-16 21:21:43 +01:00
45e9d32f58 Fixes to bugs introduced in 76a60ed 2013-11-16 21:14:27 +01:00
bb01ae7fca Closed #62 2013-11-16 19:24:50 +01:00
039d56c260 Further work on #62
Added ability to resend activation mail
2013-11-16 18:57:08 +01:00
76a60ed5d7 Refactoring of error/success messages 2013-11-16 18:44:40 +01:00
fb02feeed3 Preparation for #62 2013-11-16 17:32:43 +01:00
9ec269330c Dependancy extensions safety checks 2013-11-13 23:36:58 +01:00
8cd457848c Removed need for strict typing 2013-11-13 22:14:32 +01:00
70a4b46cf1 Foreign key fix 2013-11-13 19:54:36 +01:00
202c820a9a Closed #59 2013-11-13 19:44:36 +01:00
5e30253789 Closed #58 2013-11-13 19:42:22 +01:00
6fadc612fd Changed feature image style 2013-11-10 12:23:59 +01:00
7faf46beb9 Changed .ini a bit 2013-11-10 11:18:00 +01:00
7b014f036b Fixes to mass-tag 2013-11-06 00:51:16 +01:00
59 changed files with 1244 additions and 372 deletions

View File

@ -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

View File

@ -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');

2
logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -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());

View File

@ -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;
}

View File

@ -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: ', ';
}

View File

@ -0,0 +1,13 @@
#content input {
margin: 0 1em;
height: 25px;
vertical-align: middle;
}
pre {
font-size: 11pt;
}
pre strong {
background: #fee;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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');
});

View File

@ -0,0 +1,4 @@
$(function()
{
$('#content form input').eq(0).focus().select();
});

View File

@ -28,10 +28,11 @@ $(function()
}
else
{
alert(data['errorMessage']);
alert(data['message']);
aDom.removeClass('inactive');
}
});
});
});
$('body').trigger('dom-update');
});

View File

@ -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');
}

View File

@ -116,7 +116,7 @@ $(function()
}
else
{
postDom.find('.alert').html(data['errorHtml']).slideDown();
postDom.find('.alert').html(data['messageHtml']).slideDown();
enableUpload();
}
}

View File

@ -1,6 +1,5 @@
<?php
require_once __DIR__ . '/../src/core.php';
\Chibi\Registry::setConfig(configFactory());
function usage()
{

View File

@ -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)
{

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);

View 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;
}
}

View File

@ -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 . ' &ndash; ' . 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;

View File

@ -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();
}
}

View File

@ -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.');
}
}
}

View File

@ -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
View 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();

View 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;
}
}

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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':

View 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;
}
}

View File

@ -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)

View File

@ -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
View 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;

View File

@ -0,0 +1 @@
ALTER TABLE post_score RENAME TO postscore

53
src/Upgrades/Upgrade6.sql Normal file
View 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);

View File

@ -16,6 +16,8 @@
<div>
<label class="left">&nbsp;</label>
<div class="input-wrapper">
<button type="submit">Log in</button>
&nbsp;
<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">&nbsp;</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>

View File

@ -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>

View File

@ -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>

View File

@ -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&nbsp;fav%s', $x = $this->context->featuredPost->countOwn('favoritee'), $x == 1 ? '' : 's') ?>,&#32;
<?php printf('%d&nbsp;comment%s', $x = $this->context->featuredPost->countOwn('comment'), $x == 1 ? '' : 's') ?>
</span>
<div class="clear"></div>
</div>
<div class="body">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->featuredPost->id]) ?>">
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->featuredPost->name]) ?>" alt="<?php echo $this->context->featuredPost->name ?>"/>
<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&#32;
<?php if ($this->context->featuredPostUser): ?>
by <a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->featuredPostUser->name]) ?>"><?php echo $this->context->featuredPostUser->name ?></a>,&#32;
<?php endif ?>
<?php printf('%d day%s', $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)), $x == 1 ? '' : 's') ?> ago
<span class="left">
Tags:&nbsp;
<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&#32;
<?php if ($this->context->featuredPostUser): ?>
by <a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->featuredPostUser->name]) ?>"><?php echo $this->context->featuredPostUser->name ?></a>,&#32;
<?php endif ?>
<?php $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>

View File

@ -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
View 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
View 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&hellip;"/>
</form>
<pre><?php echo $this->context->transport->log ?></pre>
<?php endif ?>

5
src/Views/message.phtml Normal file
View 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 ?>

View File

@ -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>
&nbsp;<?php echo $val ?>
</span>
<?php endforeach ?>
</div>
<?php endif ?>
</a>
</div>

View File

@ -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&rsquo;t safe for work or you&rsquo;re not sure it&rsquo;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&rsquo;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&rsquo;t safe for work or you&rsquo;re not sure it&rsquo;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&rsquo;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>&uarr;</span>
</a>
<a class="move-down-trigger">
move down <span>&darr;</span>
</a>
<a class="remove-trigger">
remove <span>&times;</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>&uarr;</span>
</a>
<a class="move-down-trigger">
move down <span>&darr;</span>
</a>
<a class="remove-trigger">
remove <span>&times;</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&hellip;"/></div>
</div>
<div class="tags">
<label class="left">Tags:</label>
<div class="input-wrapper"><input type="text" name="tags" placeholder="enter some tags&hellip;"/></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>

View File

@ -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)): ?>
&nbsp;[
<?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>
,&nbsp;
<?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>&nbsp;
<button name="sender" type="submit" value="submit">Submit</button>

View File

@ -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 ?>

View File

@ -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">&nbsp;</label>

View File

@ -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">&nbsp;</label>

View File

@ -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"/>

View 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">&nbsp;</label>
<button type="submit">Continue</button>
</div>
</form>
<?php endif ?>

View File

@ -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">&nbsp;</label>

View File

@ -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">

View File

@ -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);

View File

@ -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)