mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
89a1b1acf7 | |||
7820417188 | |||
f226c3eb0c | |||
c683fa3b0f | |||
505fe1bac3 | |||
9819416f35 | |||
aa37ee66ff | |||
20022ea4ab | |||
d461a88001 | |||
0ef5f1b46d | |||
75775cdc15 | |||
0b22f2621e | |||
c5292580ce | |||
f8e19779a0 | |||
3b532532d1 | |||
ee224b84db | |||
d274f1c044 | |||
7e56fce470 | |||
fdd49d783a | |||
18f7fff21f | |||
81017e18cb | |||
7fccbd5e02 | |||
618f9e3d77 | |||
676c3a41e2 | |||
db602f08d3 | |||
ecc72e52e6 | |||
d8997edc57 | |||
0a5169a7d6 | |||
c1e763316a | |||
95b2eec461 | |||
6e9a18c0ae | |||
467f0c6b93 | |||
0bbeb4604f | |||
007e797d3a | |||
d636fe1c0a | |||
6e62a46110 | |||
9c0ed1e930 | |||
909026ae0f | |||
c8fb9c20c6 | |||
6549237dda | |||
601bdab8e1 | |||
1f2ce725ff | |||
cab63895c2 | |||
a98c61ebf3 | |||
fb5e851a13 | |||
77c0ea7f57 | |||
6e229bf53c | |||
5780917e82 | |||
a892410f5d | |||
b2fdbb914e | |||
e336d04347 | |||
aff68e88cf | |||
bf0e40683c | |||
17bd7a7572 | |||
a5d0a3f9ef | |||
5eb5e18b77 | |||
19a8b90ca2 | |||
e7ec8ea49f | |||
0286e11c30 | |||
7605177a6b | |||
52ceb8d962 |
0
.gitignore → data/.gitignore
vendored
0
.gitignore → data/.gitignore
vendored
@ -2,17 +2,27 @@
|
||||
prettyPrint=1
|
||||
|
||||
[main]
|
||||
dbPath=./db.sqlite
|
||||
filesPath=./files/
|
||||
thumbsPath=./thumbs/
|
||||
mediaPath=./public_html/media/
|
||||
title=szurubooru
|
||||
salt = "1A2/$_4xVa"
|
||||
logsPath=./logs/
|
||||
dbPath = "./data/db.sqlite"
|
||||
filesPath = "./data/files/"
|
||||
thumbsPath = "./data/thumbs/"
|
||||
logsPath = "./data/logs/"
|
||||
mediaPath = "./public_html/media/"
|
||||
title = "szurubooru"
|
||||
salt = "1A2/$_4xVa"
|
||||
|
||||
[misc]
|
||||
featuredPostMaxDays=7
|
||||
debugQueries=0
|
||||
logAnonymousUploads=1
|
||||
|
||||
[help]
|
||||
title=Help
|
||||
subTitles[help]=Help
|
||||
subTitles[rules]=Rules
|
||||
subTitles[privacy]=Privacy policy
|
||||
paths[help]=./data/help.md
|
||||
paths[rules]=./data/rules.md
|
||||
paths[privacy]=./data/privacy.md
|
||||
|
||||
[browsing]
|
||||
usersPerPage=8
|
||||
@ -21,6 +31,7 @@ thumbWidth=150
|
||||
thumbHeight=150
|
||||
thumbStyle=outside
|
||||
endlessScrollingDefault=1
|
||||
showPostTagTitlesDefault=0
|
||||
maxSearchTokens=4
|
||||
maxRelatedPosts=50
|
||||
|
||||
@ -80,6 +91,8 @@ editPostThumb=moderator
|
||||
editPostSource=moderator
|
||||
editPostRelations.own=registered
|
||||
editPostRelations.all=moderator
|
||||
editPostFile.all=moderator
|
||||
editPostFile.own=moderator
|
||||
hidePost.own=moderator
|
||||
hidePost.all=moderator
|
||||
deletePost.own=moderator
|
64
data/help.md
Normal file
64
data/help.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Browsing
|
||||
|
||||
Clicking the Browse button at the top will take you to the list of recent posts. Use the search box in the top right corner to find posts you want to see.
|
||||
|
||||
If you’re not a registered user, you will only see public (Safe) posts. Logging in to your account will enable you to filter content by its rating: Safe, Sketchy, and NSFW.
|
||||
|
||||
You can use your keyboard to navigate around the site. There are a few shortcuts:
|
||||
|
||||
- focus search field: `[Q]`
|
||||
- scroll up/down: `[W]` and `[S]`
|
||||
- go to newer/older post or page: `[A]` and `[D]`
|
||||
- edit post: `[E]`
|
||||
- focus first post in post list: `[P]`
|
||||
|
||||
# Search syntax
|
||||
|
||||
- contatining tag "Haruhi": [search]Haruhi[/search]
|
||||
- **not** contatining tag "Kyon": [search]-Kyon[/search]
|
||||
- uploaded by David: [search]submit:David[/search] (note no spaces)
|
||||
- favorited by David: [search]fav:David[/search]
|
||||
- favorited by at least four users: [search]favmin:4[/search]
|
||||
- commented by David: [search]comment:David[/search]
|
||||
- having at least three comments: [search]commentmin:3[/search]
|
||||
- having minimum score of 4: [search]scoremin:4[/search]
|
||||
- tagged with at least seven tags: [search]tagmin:7[/search]
|
||||
- exactly from the specified date: [search]date:2001[/search], [search]date:2012-09-29[/search] (yyyy-mm-dd format)
|
||||
- from the specified date onwards: [search]datemin:2001-01-01[/search]
|
||||
- up to the specified date: [search]datemax:2004-07[/search]
|
||||
- having specific ID: [search]id:1,2,3,8[/search]
|
||||
- having ID no less than specified value: [search]idmin:28[/search]
|
||||
- by content type: [search]type:img[/search], [search]type:swf[/search], [search]type:yt[/search] (images, flash files and YouTube videos, respectively)
|
||||
- scored up/down by currently logged in user: [search]special:likes[/search] and [search]special:dislikes[/search]
|
||||
|
||||
You can combine tags and negate any of them for interesting results. [search]sea -favmin:8 type:swf submit:Pirate[/search] will show you **flash files** tagged as **sea**, that were **liked by seven people** at most, uploaded by user **Pirate**.
|
||||
|
||||
All of the above can be sorted using additional sorting tags:
|
||||
|
||||
- as random as it can get: [search]order:random[/search]
|
||||
- newest to oldest: [search]order:date[/search] (pretty much default browse view)
|
||||
- oldest to newest: [search]-order:date[/search]
|
||||
- most commented first: [search]order:comments[/search]
|
||||
- loved by most: [search]order:favs[/search]
|
||||
- highest scored: [search]order:score[/search]
|
||||
- with most tags: [search]order:tags[/search]
|
||||
|
||||
As shown with [search]-order:date[/search], any of them can be reversed in the same way as negating other tags: by placing a dash before the tag. If there is a "min" tag, there’s also its "max" counterpart, e.g. [search]favmax:7[/search].
|
||||
|
||||
# Registration
|
||||
|
||||
The e-mail you enter during account creation is only used to retrieve your [Gravatar](http://gravatar.com) and activate your account. Only you can see it (well, except the database staff… we won’t spam your mailbox anyway).
|
||||
|
||||
Oh, and you can delete your account at any time. Posts you uploaded will stay, unless some angry admin removes them.
|
||||
|
||||
# Comments
|
||||
|
||||
Registered users can post comments. Comments support [Markdown syntax](http://daringfireball.net/projects/markdown/syntax), extended by some handy tags:
|
||||
|
||||
- permalink to post number 426: @426
|
||||
- link to tag "Dragon_Ball": #Dragon_Ball
|
||||
- mark text as spoiler and hide it: [spoiler][spoiler]There is no spoon.[/spoiler][/spoiler]
|
||||
|
||||
# Uploads
|
||||
|
||||
After registering and activating your account, you gain the power to upload files to the service for everyone else to see.
|
0
logs/.gitignore → data/logs/.gitignore
vendored
0
logs/.gitignore → data/logs/.gitignore
vendored
11
data/privacy.md
Normal file
11
data/privacy.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Stored information
|
||||
|
||||
Posts, comments, favorites and ratings linked to your account will be stored in the site’s database and publicly available.
|
||||
|
||||
The "Upload anonymously" option allows you to post content without linking it to your account - meaning your nickname will not be stored in the database nor shown in the "Uploader" field.
|
||||
|
||||
Your actions related to posts (uploading, tagging, etc.) are logged, along with your nickname and IP.
|
||||
|
||||
# Cookies
|
||||
|
||||
Cookies are used to store your session data and browsing preferences, such as endless scrolling or visibility of NSFW posts.
|
8
data/rules.md
Normal file
8
data/rules.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Site rules
|
||||
|
||||
- Don’t spam, [troll](http://www.urbandictionary.com/define.php?term=troll) or offend anyone. Be nice.
|
||||
- You are not allowed to post any form of [cp](http://www.urbandictionary.com/define.php?term=cp). If you possess it, we ask you to leave immediately and never come back.
|
||||
- Don’t use the site to host your personal images (e.g. forum avatars).
|
||||
- This kind of content is not welcome and will be removed.
|
||||
- If you notice such content, please flag it for moderation.
|
||||
- Owners of the site are not responsible for content uploaded by users.
|
22
init.php
22
init.php
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
require_once 'src/core.php';
|
||||
$config = configFactory();
|
||||
$fontsPath = $config->main->mediaPath . DS . 'fonts' . DS;
|
||||
$libPath = $config->main->mediaPath . DS . 'lib' . DS;
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$fontsPath = TextHelper::absolutePath($config->main->mediaPath . DS . 'fonts');
|
||||
$libPath = TextHelper::absolutePath($config->main->mediaPath . DS . 'lib');
|
||||
|
||||
|
||||
|
||||
@ -29,10 +29,10 @@ function download($source, $destination = null)
|
||||
|
||||
|
||||
//jQuery
|
||||
download('http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js', $libPath . 'jquery' . DS . 'jquery.min.js');
|
||||
download('http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js', $libPath . DS . 'jquery' . DS . 'jquery.min.js');
|
||||
|
||||
//jQuery UI
|
||||
download('http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js', $libPath . 'jquery-ui' . DS . 'jquery-ui.min.js');
|
||||
download('http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js', $libPath . DS . 'jquery-ui' . DS . 'jquery-ui.min.js');
|
||||
$manifest = download('http://ajax.googleapis.com/ajax/libs/jqueryui/1/MANIFEST');
|
||||
$lines = explode("\n", str_replace("\r", '', $manifest));
|
||||
foreach ($lines as $line)
|
||||
@ -40,21 +40,21 @@ foreach ($lines as $line)
|
||||
if (preg_match('/themes\/flick\/(.*?) /', $line, $matches))
|
||||
{
|
||||
$srcUrl = 'http://ajax.googleapis.com/ajax/libs/jqueryui/1/' . $matches[0];
|
||||
$dstUrl = $libPath . 'jquery-ui' . DS . $matches[1];
|
||||
$dstUrl = $libPath . DS . 'jquery-ui' . DS . $matches[1];
|
||||
download($srcUrl, $dstUrl);
|
||||
}
|
||||
}
|
||||
|
||||
//jQuery Tag-it!
|
||||
download('http://raw.github.com/aehlke/tag-it/master/css/jquery.tagit.css', $libPath . 'tagit' . DS . 'jquery.tagit.css');
|
||||
download('http://raw.github.com/aehlke/tag-it/master/js/tag-it.min.js', $libPath . 'tagit' . DS . 'jquery.tagit.js');
|
||||
download('http://raw.github.com/aehlke/tag-it/master/css/jquery.tagit.css', $libPath . DS . 'tagit' . DS . 'jquery.tagit.css');
|
||||
download('http://raw.github.com/aehlke/tag-it/master/js/tag-it.min.js', $libPath . DS . 'tagit' . DS . 'jquery.tagit.js');
|
||||
|
||||
//Mousetrap
|
||||
download('http://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 . DS . 'mousetrap' . DS . 'mousetrap.min.js');
|
||||
|
||||
//fonts
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans.ttf', $fontsPath . 'DroidSans.ttf');
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans-Bold.ttf', $fontsPath . 'DroidSans-Bold.ttf');
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans.ttf', $fontsPath . DS . 'DroidSans.ttf');
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans-Bold.ttf', $fontsPath . DS . 'DroidSans-Bold.ttf');
|
||||
|
||||
|
||||
|
||||
|
Submodule lib/chibi-core updated: bf824e781c...59f80280ba
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
chdir('..');
|
||||
require_once 'src/core.php';
|
||||
require_once '../src/core.php';
|
||||
|
||||
$query = $_SERVER['REQUEST_URI'];
|
||||
\Chibi\Facade::run($query, new Bootstrap());
|
||||
|
@ -328,7 +328,7 @@ button:hover {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
margin: 2em auto !important;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
|
@ -1,9 +1,3 @@
|
||||
code {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
span.comma {
|
||||
margin-left: -0.5em;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
@ -10,12 +10,12 @@
|
||||
|
||||
.paginator li {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.paginator li a {
|
||||
display: inline-block;
|
||||
padding: 0.2em 0.5em;
|
||||
margin-right: 0.5em;
|
||||
background: #eee;
|
||||
border: 1px solid silver;
|
||||
color: black;
|
||||
|
@ -82,12 +82,17 @@
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.post .info-bar:before {
|
||||
border-top: 1px solid firebrick;
|
||||
margin-bottom: -1px;
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
.post .info-bar {
|
||||
display: none;
|
||||
border-top: 1px solid firebrick;
|
||||
background: rgba(255, 128, 128, 0.75);
|
||||
position: absolute;
|
||||
z-indeX: 3;
|
||||
z-index: 3;
|
||||
left: 1px; /* border */
|
||||
right: 1px; /* border */
|
||||
bottom: 1px; /* border */
|
||||
|
@ -12,8 +12,9 @@ embed {
|
||||
.post-type-image img {
|
||||
/*background: url('../img/bk-image.png') lemonchiffon;*/
|
||||
}
|
||||
.post-type-flash embed {
|
||||
background: url('../img/bk-swf.png') lemonchiffon;
|
||||
.post-type-flash iframe {
|
||||
border: 0;
|
||||
/*background: url('../img/bk-swf.png') lemonchiffon;*/
|
||||
}
|
||||
|
||||
#sidebar .relations ul,
|
||||
@ -31,19 +32,26 @@ embed {
|
||||
color: silver;
|
||||
}
|
||||
|
||||
#sidebar nav {
|
||||
#around {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
#sidebar nav .left {
|
||||
#around .text {
|
||||
font-size: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
#around .left {
|
||||
float: left;
|
||||
}
|
||||
#sidebar nav .right {
|
||||
#around .right {
|
||||
float: right;
|
||||
}
|
||||
#sidebar nav a.disabled {
|
||||
#around a.disabled {
|
||||
color: silver;
|
||||
}
|
||||
#sidebar nav a.disabled i[class*='icon-'] {
|
||||
#around a.disabled i[class*='icon-'] {
|
||||
background-color: silver;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
.tabs ul {
|
||||
list-style-type: none;
|
||||
margin: 0 0 1em 0;
|
||||
margin: -4px 0 1em 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
@ -11,18 +11,17 @@
|
||||
.tabs li a {
|
||||
display: inline-block;
|
||||
padding: 0.5em 1em;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.tabs li a {
|
||||
border: 1px solid white;
|
||||
margin: 5px 0 -1px 0;
|
||||
vertical-align: middle;
|
||||
border: 1px none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: silver;
|
||||
}
|
||||
.tabs li.selected a {
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: 1px solid white;
|
||||
border-bottom: none;
|
||||
color: inherit;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.tabs li a:focus {
|
||||
|
@ -6,9 +6,6 @@
|
||||
-moz-column-width: 14em;
|
||||
-webkit-column-width: 14em;
|
||||
}
|
||||
.tags li a:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.tags li {
|
||||
margin: 0.2em 0.5em;
|
||||
text-align: top;
|
||||
@ -38,3 +35,31 @@ form.aligned label.left {
|
||||
form h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.frequency0 { color: #ecc8c8; }
|
||||
.frequency1 { color: #e6b7b7; }
|
||||
.frequency2 { color: #e0a7a7; }
|
||||
.frequency3 { color: #db9696; }
|
||||
.frequency4 { color: #d58686; }
|
||||
.frequency5 { color: #cf7575; }
|
||||
.frequency6 { color: #c96464; }
|
||||
.frequency7 { color: #c35454; }
|
||||
.frequency8 { color: #be4343; }
|
||||
.frequency9 { color: #b83333; }
|
||||
.frequency10 { color: #b22222; }
|
||||
|
||||
nav.sort-styles ul {
|
||||
list-style-type: none;
|
||||
margin: 0 0 2.5em 0;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
nav.sort-styles li {
|
||||
display: inline-block;
|
||||
font-size: 105%;
|
||||
margin: 0 1em;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
nav.sort-styles li.active {
|
||||
border-bottom: 3px solid firebrick;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
nav.sort-styles ul {
|
||||
list-style-type: none;
|
||||
margin: 0 0 1em 0;
|
||||
margin: 0 0 2.5em 0;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -1,3 +1,37 @@
|
||||
function setCookie(name, value, exdays)
|
||||
{
|
||||
var exdate = new Date();
|
||||
exdate.setDate(exdate.getDate() + exdays);
|
||||
value = escape(value) + '; path=/' + ((exdays == null) ? '' : '; expires=' + exdate.toUTCString());
|
||||
document.cookie = name + '=' + value;
|
||||
}
|
||||
|
||||
function getCookie(name)
|
||||
{
|
||||
console.log(document.cookie);
|
||||
var value = document.cookie;
|
||||
var start = value.indexOf(' ' + name + '=');
|
||||
|
||||
if (start == -1)
|
||||
start = value.indexOf(name + '=');
|
||||
|
||||
if (start == -1)
|
||||
return null;
|
||||
|
||||
start = value.indexOf('=', start) + 1;
|
||||
var end = value.indexOf(';', start);
|
||||
if (end == -1)
|
||||
end = value.length;
|
||||
|
||||
return unescape(value.substring(start, end));
|
||||
}
|
||||
|
||||
function rememberLastSearchQuery()
|
||||
{
|
||||
//lastSearchQuery variable is obtained from layout
|
||||
setCookie('last-search-query', lastSearchQuery);
|
||||
}
|
||||
|
||||
//core functionalities, prototypes
|
||||
$.fn.hasAttr = function(name)
|
||||
{
|
||||
@ -36,12 +70,15 @@ $(function()
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url, function(data)
|
||||
$.get(url).always(function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
window.location.reload();
|
||||
else
|
||||
alert(data['message']);
|
||||
{
|
||||
alert(data['message'] ? data['message'] : 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -53,6 +90,7 @@ $(function()
|
||||
{
|
||||
$('body').bind('dom-update', function()
|
||||
{
|
||||
//event confirmations
|
||||
function confirmEvent(e)
|
||||
{
|
||||
if (!confirm($(this).attr('data-confirm-text')))
|
||||
@ -65,12 +103,15 @@ $(function()
|
||||
$('form[data-confirm-text]').submit(confirmEvent);
|
||||
$('a[data-confirm-text]').click(confirmEvent);
|
||||
|
||||
|
||||
//simple action buttons
|
||||
$('a.simple-action').click(function(e)
|
||||
{
|
||||
if(e.isPropagationStopped())
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
rememberLastSearchQuery();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
@ -78,7 +119,7 @@ $(function()
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url, {submit: 1}, function(data)
|
||||
$.get(url, {submit: 1}).always(function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
@ -91,7 +132,7 @@ $(function()
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['message']);
|
||||
alert(data['message'] ? data['message'] : 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
@ -113,6 +154,10 @@ $(function()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//try to remember last search query
|
||||
window.onbeforeunload = rememberLastSearchQuery;
|
||||
});
|
||||
|
||||
|
||||
@ -133,7 +178,6 @@ function processSidebar()
|
||||
$('#sidebar').insertBefore($('#inner-content'));
|
||||
$('#sidebar .unit').removeClass('bottom-unit').addClass('left-unit');
|
||||
}
|
||||
$('body').data('last-width', $('body').width());
|
||||
}
|
||||
$(function()
|
||||
{
|
||||
@ -141,6 +185,8 @@ $(function()
|
||||
{
|
||||
if ($('body').width() == $('body').data('last-width'))
|
||||
return;
|
||||
$('body').data('last-width', $('body').width());
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
$('body').bind('dom-update', processSidebar);
|
||||
});
|
||||
@ -160,26 +206,31 @@ function extractLast(term)
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('[data-autocomplete-url]').each(function()
|
||||
$('.autocomplete').each(function()
|
||||
{
|
||||
var searchInput = $(this);
|
||||
searchInput
|
||||
// don't navigate away from the field on tab when selecting an item
|
||||
.bind("keydown", function(event)
|
||||
.bind('keydown', function(event)
|
||||
{
|
||||
if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active)
|
||||
if (event.keyCode === $.ui.keyCode.TAB && $(this).data('autocomplete').menu.active)
|
||||
{
|
||||
event.preventDefault();
|
||||
}
|
||||
}).autocomplete({
|
||||
minLength: 1,
|
||||
position:
|
||||
{
|
||||
my: 'right top',
|
||||
at: 'right bottom'
|
||||
},
|
||||
source: function(request, response)
|
||||
{
|
||||
var term = extractLast(request.term);
|
||||
if (term != '')
|
||||
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term}, function(data)
|
||||
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term + ' order:popularity,desc'}, function(data)
|
||||
{
|
||||
response($.map(data.tags, function(tag) { return { label: tag.name, value: tag.name }; }));
|
||||
response($.map(data.tags, function(tag) { return { label: tag.name + ' (' + tag.count + ')', value: tag.name }; }));
|
||||
});
|
||||
},
|
||||
focus: function()
|
||||
@ -242,9 +293,3 @@ $(function()
|
||||
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');
|
||||
});
|
||||
|
@ -15,24 +15,27 @@ $(function()
|
||||
return;
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var enable = !aDom.parents('.post').hasClass('tagged');
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url, {submit: 1}, function(data)
|
||||
url = url.replace('_enable_', enable ? '1' : '0');
|
||||
$.get(url, {submit: 1}).always(function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
aDom.parents('.post').toggleClass('tagged');
|
||||
aDom.text(aDom.parents('.post').hasClass('tagged')
|
||||
aDom.parents('.post').removeClass('tagged');
|
||||
if (enable)
|
||||
aDom.parents('.post').addClass('tagged');
|
||||
aDom.text(enable
|
||||
? aDom.attr('data-text-tagged')
|
||||
: aDom.attr('data-text-untagged'));
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['message']);
|
||||
alert(data['message'] ? data['message'] : 'Fatal error');
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('body').trigger('dom-update');
|
||||
});
|
@ -10,7 +10,7 @@ function onDomUpdate()
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var tags = [];
|
||||
$.getJSON('/tags?json', function(data)
|
||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
var formDom = $('form.edit-post');
|
||||
@ -34,7 +34,7 @@ function onDomUpdate()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('.comments.unit').replaceWith($(data).find('.comments.unit'));
|
||||
$('.comments-wrapper').replaceWith($(data).find('.comments-wrapper'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
});
|
||||
@ -52,11 +52,11 @@ function onDomUpdate()
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', onDomUpdate);
|
||||
onDomUpdate();
|
||||
|
||||
$('form.edit-post').submit(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
rememberLastSearchQuery();
|
||||
|
||||
var formDom = $(this);
|
||||
if (formDom.hasClass('inactive'))
|
||||
@ -87,6 +87,12 @@ $(function()
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
},
|
||||
error: function()
|
||||
{
|
||||
alert('Fatal error');
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,6 +102,7 @@ $(function()
|
||||
$('form.add-comment').submit(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
rememberLastSearchQuery();
|
||||
|
||||
var formDom = $(this);
|
||||
if (formDom.hasClass('inactive'))
|
||||
@ -131,9 +138,10 @@ $(function()
|
||||
}
|
||||
else
|
||||
{
|
||||
formDom.find('.preview').hide();
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('.comments.unit').replaceWith($(data).find('.comments.unit'));
|
||||
$('.comments-wrapper').replaceWith($(data).find('.comments-wrapper'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
formDom.find('textarea').val('');
|
||||
@ -147,13 +155,19 @@ $(function()
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
},
|
||||
error: function()
|
||||
{
|
||||
alert('Fatal error');
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
};
|
||||
|
||||
$.ajax(ajaxData);
|
||||
});
|
||||
|
||||
Mousetrap.bind('a', function() { var url = $('#sidebar .left a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('d', function() { var url = $('#sidebar .right a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('a', function() { var a = $('#sidebar .left a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
||||
Mousetrap.bind('d', function() { var a = $('#sidebar .right a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
||||
Mousetrap.bind('e', function() { $('li.edit a').trigger('click'); return false; }, 'keyup');
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ $(function()
|
||||
});
|
||||
|
||||
var tags = [];
|
||||
$.getJSON('/tags?json', function(data)
|
||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
||||
{
|
||||
tags = data['tags'];
|
||||
});
|
||||
@ -60,6 +60,8 @@ $(function()
|
||||
|
||||
$('.post .move-down-trigger, .post .move-up-trigger').on('click', function()
|
||||
{
|
||||
if ($('#the-submit').hasClass('inactive'))
|
||||
return;
|
||||
var dir = $(this).hasClass('move-down-trigger') ? 'd' : 'u';
|
||||
var post = $(this).parents('.post');
|
||||
if (dir == 'u')
|
||||
@ -69,6 +71,8 @@ $(function()
|
||||
});
|
||||
$('.post .remove-trigger').on('click', function()
|
||||
{
|
||||
if ($('#the-submit').hasClass('inactive'))
|
||||
return;
|
||||
$(this).parents('.post').slideUp(function()
|
||||
{
|
||||
$(this).remove();
|
||||
@ -119,6 +123,11 @@ $(function()
|
||||
postDom.find('.alert').html(data['messageHtml']).slideDown();
|
||||
enableUpload();
|
||||
}
|
||||
},
|
||||
error: function(data)
|
||||
{
|
||||
postDom.find('.alert').html('Fatal error').slideDown();
|
||||
enableUpload();
|
||||
}
|
||||
};
|
||||
|
||||
@ -152,6 +161,8 @@ $(function()
|
||||
{
|
||||
e.preventDefault();
|
||||
var theSubmit = $(this);
|
||||
if (theSubmit.hasClass('inactive'))
|
||||
return;
|
||||
disableUpload();
|
||||
sendNextPost();
|
||||
});
|
||||
|
@ -12,7 +12,6 @@ array_shift($argv);
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
|
||||
$filesPath = rtrim(\Chibi\Registry::getConfig()->main->filesPath, DS);
|
||||
$query = array_shift($argv);
|
||||
$posts = Model_Post::getEntities($query, null, null);
|
||||
foreach ($posts as $post)
|
||||
@ -21,7 +20,7 @@ foreach ($posts as $post)
|
||||
[
|
||||
$post->id,
|
||||
$post->name,
|
||||
$filesPath . DS . $post->name,
|
||||
Model_Post::getFullPath($post->name),
|
||||
$post->mimeType,
|
||||
]). PHP_EOL;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
function usage()
|
||||
{
|
||||
@ -31,24 +30,21 @@ switch ($action)
|
||||
mkdir($dir, 0755, true);
|
||||
if (!is_dir($dir))
|
||||
die($dir . ' is not a dir' . PHP_EOL);
|
||||
$func = function($name) use ($dir, $config)
|
||||
$func = function($name) use ($dir)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
static $filesPath = null;
|
||||
if ($filesPath == null)
|
||||
$filesPath = $config->main->filesPath;
|
||||
rename($filesPath . DS . $name, $dir . DS . $name);
|
||||
$srcPath = Model_Post::getFullPath($name);
|
||||
$dstPath = $dir . DS . $name;
|
||||
rename($srcPath, $dstPath);
|
||||
};
|
||||
break;
|
||||
|
||||
case '-purge':
|
||||
$func = function($name) use ($dir, $config)
|
||||
$func = function($name)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
static $filesPath = null;
|
||||
if ($filesPath == null)
|
||||
$filesPath = $config->main->filesPath;
|
||||
unlink($filesPath . DS . $name);
|
||||
$srcPath = Model_Post::getFullPath($name);
|
||||
unlink($srcPath);
|
||||
};
|
||||
break;
|
||||
|
||||
@ -63,8 +59,8 @@ foreach (R::findAll('post') as $post)
|
||||
}
|
||||
$names = array_flip($names);
|
||||
|
||||
$filesPath = $config->main->filesPath;
|
||||
foreach (glob($filesPath . DS . '*') as $name)
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
foreach (glob(TextHelper::absolutePath($config->main->filesPath) . DS . '*') as $name)
|
||||
{
|
||||
$name = basename($name);
|
||||
if (!isset($names[$name]))
|
||||
|
@ -32,7 +32,7 @@ switch ($action)
|
||||
$func = function($user)
|
||||
{
|
||||
printUser($user);
|
||||
R::trash($user);
|
||||
Model_User::remove($user);
|
||||
};
|
||||
break;
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
process () {
|
||||
x="$1";
|
||||
echo "$x";
|
||||
convert "$x" -fuzz 5% -trim +repage tmp && mv tmp "$x"
|
||||
convert "$x" -fuzz 7% -trim +repage tmp && mv tmp "$x"
|
||||
}
|
||||
|
||||
while read x; do
|
||||
|
@ -17,7 +17,7 @@ class AuthController
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$context = \Chibi\Registry::getContext();
|
||||
|
||||
$dbUser = R::findOne('user', 'name = ?', [$name]);
|
||||
$dbUser = Model_User::locate($name, false);
|
||||
if ($dbUser === null)
|
||||
throw new SimpleException('Invalid username');
|
||||
|
||||
@ -111,7 +111,7 @@ class AuthController
|
||||
else
|
||||
{
|
||||
$dummy = R::dispense('user');
|
||||
$dummy->name = 'Anonymous';
|
||||
$dummy->name = Model_User::getAnonymousName();
|
||||
$dummy->access_rank = AccessRank::Anonymous;
|
||||
$dummy->anonymous = true;
|
||||
$_SESSION['user'] = serialize($dummy);
|
||||
|
@ -25,7 +25,6 @@ class CommentController
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$comments = Model_Comment::getEntities(null, $commentsPerPage, $page);
|
||||
|
||||
R::preload($comments, ['commenter' => 'user', 'post', 'post.uploader' => 'user']);
|
||||
$this->context->postGroups = true;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
@ -55,7 +54,7 @@ class CommentController
|
||||
$text = InputHelper::get('text');
|
||||
$text = Model_Comment::validateText($text);
|
||||
|
||||
$comment = R::dispense('comment');
|
||||
$comment = Model_Comment::create();
|
||||
$comment->post = $post;
|
||||
if ($this->context->loggedIn)
|
||||
$comment->commenter = $this->context->user;
|
||||
@ -63,8 +62,8 @@ class CommentController
|
||||
$comment->text = $text;
|
||||
if (InputHelper::get('sender') != 'preview')
|
||||
{
|
||||
R::store($comment);
|
||||
LogHelper::logEvent('comment-add', '{user} commented on {post}', ['post' => TextHelper::reprPost($post->id)]);
|
||||
Model_Comment::save($comment);
|
||||
LogHelper::log('{user} commented on {post}', ['post' => TextHelper::reprPost($post->id)]);
|
||||
}
|
||||
$this->context->transport->textPreview = $comment->getText();
|
||||
StatusHelper::success();
|
||||
@ -80,10 +79,10 @@ class CommentController
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$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);
|
||||
Model_Comment::remove($comment);
|
||||
|
||||
LogHelper::log('{user} removed comment from {post}', ['post' => TextHelper::reprPost($comment->post)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
@ -9,47 +9,71 @@ class IndexController
|
||||
{
|
||||
$this->context->subTitle = 'home';
|
||||
$this->context->stylesheets []= 'index-index.css';
|
||||
$this->context->transport->postCount = R::$f->begin()->select('count(1)')->as('count')->from('post')->get('row')['count'];
|
||||
$this->context->transport->postCount = Model_Post::getAllPostCount();
|
||||
|
||||
$featuredPostRotationTime = $this->config->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$featuredPostId = Model_Property::get(Model_Property::FeaturedPostId);
|
||||
$featuredPostUserId = Model_Property::get(Model_Property::FeaturedPostUserId);
|
||||
$featuredPostDate = Model_Property::get(Model_Property::FeaturedPostDate);
|
||||
if (!$featuredPostId or $featuredPostDate + $featuredPostRotationTime < time())
|
||||
$featuredPost = $this->getFeaturedPost();
|
||||
if ($featuredPost)
|
||||
{
|
||||
$featuredPostId = R::$f->begin()
|
||||
->select('id')
|
||||
->from('post')
|
||||
->where('type = ?')->put(PostType::Image)
|
||||
->and('safety = ?')->put(PostSafety::Safe)
|
||||
->orderBy('random()')
|
||||
->desc()
|
||||
->get('row')['id'];
|
||||
$featuredPostUserId = null;
|
||||
$featuredPostDate = time();
|
||||
Model_Property::set(Model_Property::FeaturedPostId, $featuredPostId);
|
||||
Model_Property::set(Model_Property::FeaturedPostUserId, $featuredPostUserId);
|
||||
Model_Property::set(Model_Property::FeaturedPostDate, $featuredPostDate);
|
||||
}
|
||||
|
||||
if ($featuredPostId !== null)
|
||||
{
|
||||
$featuredPost = Model_Post::locate($featuredPostId);
|
||||
R::preload($featuredPost, ['user', 'comment', 'favoritee']);
|
||||
$featuredPostUser = R::findOne('user', 'id = ?', [$featuredPostUserId]);
|
||||
$this->context->featuredPost = $featuredPost;
|
||||
$this->context->featuredPostUser = $featuredPostUser;
|
||||
$this->context->featuredPostDate = $featuredPostDate;
|
||||
$this->context->featuredPostDate = Model_Property::get(Model_Property::FeaturedPostDate);
|
||||
$this->context->featuredPostUser = Model_User::locate(Model_Property::get(Model_Property::FeaturedPostUserName), false);
|
||||
$this->context->pageThumb = \Chibi\UrlHelper::route('post', 'thumb', ['name' => $featuredPost->name]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /help
|
||||
* @route /help/{tab}
|
||||
*/
|
||||
public function helpAction()
|
||||
public function helpAction($tab = null)
|
||||
{
|
||||
if (empty($this->config->help->paths) or empty($this->config->help->title))
|
||||
throw new SimpleException('Help is disabled');
|
||||
$tab = $tab ?: array_keys($this->config->help->subTitles)[0];
|
||||
if (!isset($this->config->help->paths[$tab]))
|
||||
throw new SimpleException('Invalid tab');
|
||||
$this->context->path = TextHelper::absolutePath($this->config->help->paths[$tab]);
|
||||
$this->context->stylesheets []= 'index-help.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'help';
|
||||
$this->context->tab = $tab;
|
||||
}
|
||||
|
||||
private function getFeaturedPost()
|
||||
{
|
||||
$featuredPostRotationTime = $this->config->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$featuredPostId = Model_Property::get(Model_Property::FeaturedPostId);
|
||||
$featuredPostDate = Model_Property::get(Model_Property::FeaturedPostDate);
|
||||
|
||||
//check if too old
|
||||
if (!$featuredPostId or $featuredPostDate + $featuredPostRotationTime < time())
|
||||
return $this->featureNewPost();
|
||||
|
||||
//check if post was deleted
|
||||
$featuredPost = Model_Post::locate($featuredPostId, false, false);
|
||||
if (!$featuredPost)
|
||||
return $this->featureNewPost();
|
||||
|
||||
return $featuredPost;
|
||||
}
|
||||
|
||||
private function featureNewPost()
|
||||
{
|
||||
$featuredPostId = R::$f->begin()
|
||||
->select('id')
|
||||
->from('post')
|
||||
->where('type = ?')->put(PostType::Image)
|
||||
->and('safety = ?')->put(PostSafety::Safe)
|
||||
->orderBy('random()')
|
||||
->desc()
|
||||
->get('row')['id'];
|
||||
if (!$featuredPostId)
|
||||
return null;
|
||||
|
||||
Model_Property::set(Model_Property::FeaturedPostId, $featuredPostId);
|
||||
Model_Property::set(Model_Property::FeaturedPostDate, time());
|
||||
Model_Property::set(Model_Property::FeaturedPostUserName, null);
|
||||
return Model_Post::locate($featuredPostId);
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,7 @@ class LogController
|
||||
$this->context->subTitle = 'latest logs';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListLogs);
|
||||
|
||||
$path = $this->context->rootDir . DS . $this->config->main->logsPath;
|
||||
$path = TextHelper::cleanPath($path);
|
||||
$path = TextHelper::absolutePath($this->config->main->logsPath);
|
||||
|
||||
$logs = [];
|
||||
foreach (glob($path . DS . '*.log') as $log)
|
||||
@ -36,8 +35,7 @@ class LogController
|
||||
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);
|
||||
$path = TextHelper::absolutePath($this->config->main->logsPath . DS . $name);
|
||||
if (!file_exists($path))
|
||||
throw new SimpleException('Specified log doesn\'t exist');
|
||||
|
||||
@ -57,7 +55,7 @@ class LogController
|
||||
unset($line);
|
||||
|
||||
$lines = join(PHP_EOL, $lines);
|
||||
$lines = TextHelper::parseMarkdown($lines);
|
||||
$lines = TextHelper::parseMarkdown($lines, true);
|
||||
$lines = trim($lines);
|
||||
|
||||
$this->context->transport->filter = $filter;
|
||||
|
@ -8,13 +8,18 @@ class PostController
|
||||
$callback();
|
||||
}
|
||||
|
||||
private static function serializeTags($post)
|
||||
private static function serializePost($post)
|
||||
{
|
||||
$x = [];
|
||||
foreach ($post->sharedTag as $tag)
|
||||
$x []= $tag->name;
|
||||
$x []= TextHelper::reprTag($tag->name);
|
||||
foreach ($post->via('crossref')->sharedPost as $relatedPost)
|
||||
$x []= TextHelper::reprPost($relatedPost);
|
||||
$x []= $post->safety;
|
||||
$x []= $post->source;
|
||||
$x []= $post->file_hash;
|
||||
natcasesort($x);
|
||||
$x = join('', $x);
|
||||
$x = join(' ', $x);
|
||||
return md5($x);
|
||||
}
|
||||
|
||||
@ -61,15 +66,14 @@ class PostController
|
||||
*/
|
||||
public function listAction($query = null, $page = 1, $source = 'posts', $additionalInfo = null)
|
||||
{
|
||||
$this->context->viewName = 'post-list-wrapper';
|
||||
$this->context->stylesheets []= 'post-small.css';
|
||||
$this->context->stylesheets []= 'post-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->stylesheets []= 'paginator.css';
|
||||
$this->context->viewName = 'post-list-wrapper';
|
||||
$this->context->scripts []= 'post-list.js';
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
if ($source == 'mass-tag')
|
||||
$this->context->scripts []= 'mass-tag.js';
|
||||
$this->context->source = $source;
|
||||
$this->context->additionalInfo = $additionalInfo;
|
||||
|
||||
@ -78,18 +82,20 @@ class PostController
|
||||
if ($formQuery !== null)
|
||||
{
|
||||
$this->context->transport->searchQuery = $formQuery;
|
||||
$this->context->transport->lastSearchQuery = $formQuery;
|
||||
if (strpos($formQuery, '/') !== false)
|
||||
throw new SimpleException('Search query contains invalid characters');
|
||||
$url = \Chibi\UrlHelper::route('post', 'list', ['source' => $source, 'additionalInfo' => $additionalInfo, 'query' => urlencode($formQuery)]);
|
||||
$url = \Chibi\UrlHelper::route('post', 'list', ['source' => $source, 'additionalInfo' => $additionalInfo, 'query' => $formQuery]);
|
||||
\Chibi\UrlHelper::forward($url);
|
||||
return;
|
||||
}
|
||||
|
||||
$query = trim(urldecode($query));
|
||||
$query = trim($query);
|
||||
$page = intval($page);
|
||||
$postsPerPage = intval($this->config->browsing->postsPerPage);
|
||||
$this->context->subTitle = 'posts';
|
||||
$this->context->transport->searchQuery = $query;
|
||||
$this->context->transport->lastSearchQuery = $query;
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
||||
if ($source == 'mass-tag')
|
||||
{
|
||||
@ -114,36 +120,39 @@ class PostController
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{id}/toggle-tag/{tag}
|
||||
* @route /post/{id}/toggle-tag/{tag}/{enable}
|
||||
* @validate tag [^\/]*
|
||||
* @validate enable 0|1
|
||||
*/
|
||||
public function toggleTagAction($id, $tag)
|
||||
public function toggleTagAction($id, $tag, $enable)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
$this->context->transport->post = $post;
|
||||
$tag = Model_Tag::validateTag($tag);
|
||||
|
||||
$tagRow = Model_Tag::locate($tag, false);
|
||||
if ($tagRow !== null)
|
||||
$tag = $tagRow->name;
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
$tags = array_map(function($x) { return $x->name; }, $post->sharedTag);
|
||||
|
||||
if (in_array($tag, $tags))
|
||||
if (!$enable and 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)]);
|
||||
LogHelper::log('{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
else
|
||||
elseif ($enable)
|
||||
{
|
||||
$tags += [$tag];
|
||||
LogHelper::logEvent('post-tag-add', '{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
LogHelper::log('{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
|
||||
$dbTags = Model_Tag::insertOrUpdate($tags);
|
||||
$post->sharedTag = $dbTags;
|
||||
|
||||
R::store($post);
|
||||
Model_Post::save($post);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -189,166 +198,44 @@ class PostController
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
/* file contents */
|
||||
if (isset($_FILES['file']))
|
||||
R::transaction(function()
|
||||
{
|
||||
$suppliedFile = $_FILES['file'];
|
||||
self::handleUploadErrors($suppliedFile);
|
||||
$origName = basename($suppliedFile['name']);
|
||||
$sourcePath = $suppliedFile['tmp_name'];
|
||||
}
|
||||
elseif (InputHelper::get('url'))
|
||||
{
|
||||
$url = InputHelper::get('url');
|
||||
$origName = $url;
|
||||
if (!preg_match('/^https?:\/\//', $url))
|
||||
throw new SimpleException('Invalid URL "' . $url . '"');
|
||||
$post = Model_Post::create();
|
||||
LogHelper::bufferChanges();
|
||||
|
||||
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $url, $matches))
|
||||
{
|
||||
$origName = $matches[1];
|
||||
$postType = PostType::Youtube;
|
||||
$sourcePath = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sourcePath = tempnam(sys_get_temp_dir(), 'upload') . '.dat';
|
||||
//basic stuff
|
||||
$anonymous = InputHelper::get('anonymous');
|
||||
if ($this->context->loggedIn and !$anonymous)
|
||||
$post->uploader = $this->context->user;
|
||||
|
||||
//warning: low level sh*t ahead
|
||||
//download the URL $url into $sourcePath
|
||||
$maxBytes = TextHelper::stripBytesUnits(ini_get('upload_max_filesize'));
|
||||
set_time_limit(0);
|
||||
$urlFP = fopen($url, 'rb');
|
||||
if (!$urlFP)
|
||||
throw new SimpleException('Cannot open URL for reading');
|
||||
$sourceFP = fopen($sourcePath, 'w+b');
|
||||
if (!$sourceFP)
|
||||
{
|
||||
fclose($urlFP);
|
||||
throw new SimpleException('Cannot open file for writing');
|
||||
}
|
||||
try
|
||||
{
|
||||
while (!feof($urlFP))
|
||||
{
|
||||
$buffer = fread($urlFP, 4 * 1024);
|
||||
if (fwrite($sourceFP, $buffer) === false)
|
||||
throw new SimpleException('Cannot write into file');
|
||||
fflush($sourceFP);
|
||||
if (ftell($sourceFP) > $maxBytes)
|
||||
throw new SimpleException('File is too big (maximum allowed size: ' . TextHelper::useBytesUnits($maxBytes) . ')');
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
fclose($urlFP);
|
||||
fclose($sourceFP);
|
||||
}
|
||||
}
|
||||
}
|
||||
//store the post to get the ID in the logs
|
||||
Model_Post::save($post);
|
||||
|
||||
//do the edits
|
||||
$this->doEdit($post, true);
|
||||
|
||||
/* file details */
|
||||
$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)
|
||||
{
|
||||
case 'image/gif':
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
$postType = PostType::Image;
|
||||
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
|
||||
break;
|
||||
case 'application/x-shockwave-flash':
|
||||
$postType = PostType::Flash;
|
||||
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
|
||||
break;
|
||||
default:
|
||||
if (!isset($postType))
|
||||
throw new SimpleException('Invalid file type "' . $mimeType . '"');
|
||||
}
|
||||
//this basically means that user didn't specify file nor url
|
||||
if (empty($post->type))
|
||||
throw new SimpleException('No post type detected; upload faled');
|
||||
|
||||
if ($sourcePath)
|
||||
{
|
||||
$fileSize = filesize($sourcePath);
|
||||
$fileHash = md5_file($sourcePath);
|
||||
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$fileHash]);
|
||||
if ($duplicatedPost !== null)
|
||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||
}
|
||||
else
|
||||
{
|
||||
$fileSize = 0;
|
||||
$fileHash = null;
|
||||
if ($postType == PostType::Youtube)
|
||||
{
|
||||
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
|
||||
if ($duplicatedPost !== null)
|
||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||
}
|
||||
}
|
||||
//clean edit log
|
||||
LogHelper::setBuffer([]);
|
||||
|
||||
do
|
||||
{
|
||||
$name = md5(mt_rand() . uniqid());
|
||||
$path = $this->config->main->filesPath . DS . $name;
|
||||
}
|
||||
while (file_exists($path));
|
||||
//log
|
||||
$fmt = ($anonymous and !$this->config->misc->logAnonymousUploads)
|
||||
? '{anon}'
|
||||
: '{user}';
|
||||
$fmt .= ' added {post} (tags: {tags}, safety: {safety}, source: {source})';
|
||||
LogHelper::log($fmt, [
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'tags' => join(', ', array_map(['TextHelper', 'reprTag'], $post->sharedTag)),
|
||||
'safety' => PostSafety::toString($post->safety),
|
||||
'source' => $post->source]);
|
||||
|
||||
|
||||
/* safety */
|
||||
$suppliedSafety = InputHelper::get('safety');
|
||||
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
|
||||
|
||||
/* tags */
|
||||
$suppliedTags = InputHelper::get('tags');
|
||||
$suppliedTags = Model_Tag::validateTags($suppliedTags);
|
||||
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
|
||||
|
||||
/* source */
|
||||
$suppliedSource = InputHelper::get('source');
|
||||
$suppliedSource = Model_Post::validateSource($suppliedSource);
|
||||
|
||||
/* db storage */
|
||||
$dbPost = R::dispense('post');
|
||||
$dbPost->type = $postType;
|
||||
$dbPost->name = $name;
|
||||
$dbPost->orig_name = $origName;
|
||||
$dbPost->file_hash = $fileHash;
|
||||
$dbPost->file_size = $fileSize;
|
||||
$dbPost->mime_type = $mimeType;
|
||||
$dbPost->safety = $suppliedSafety;
|
||||
$dbPost->source = $suppliedSource;
|
||||
$dbPost->hidden = false;
|
||||
$dbPost->upload_date = time();
|
||||
$dbPost->image_width = $imageWidth;
|
||||
$dbPost->image_height = $imageHeight;
|
||||
if ($this->context->loggedIn and !InputHelper::get('anonymous'))
|
||||
$dbPost->uploader = $this->context->user;
|
||||
$dbPost->ownFavoritee = [];
|
||||
$dbPost->sharedTag = $dbTags;
|
||||
|
||||
if ($sourcePath)
|
||||
{
|
||||
if (is_uploaded_file($sourcePath))
|
||||
move_uploaded_file($sourcePath, $path);
|
||||
else
|
||||
rename($sourcePath, $path);
|
||||
}
|
||||
R::store($dbPost);
|
||||
|
||||
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)]);
|
||||
//finish
|
||||
LogHelper::flush();
|
||||
Model_Post::save($post);
|
||||
});
|
||||
|
||||
StatusHelper::success();
|
||||
}
|
||||
@ -362,111 +249,21 @@ class PostController
|
||||
public function editAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
$this->context->transport->post = $post;
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$editToken = InputHelper::get('edit-token');
|
||||
if ($editToken != self::serializePost($post))
|
||||
throw new SimpleException('This post was already edited by someone else in the meantime');
|
||||
|
||||
LogHelper::bufferChanges();
|
||||
$this->doEdit($post, false);
|
||||
LogHelper::flush();
|
||||
|
||||
/* safety */
|
||||
$suppliedSafety = InputHelper::get('safety');
|
||||
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)]);
|
||||
}
|
||||
|
||||
|
||||
/* tags */
|
||||
$suppliedTags = InputHelper::get('tags');
|
||||
if ($suppliedTags !== null)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$currentToken = self::serializeTags($post);
|
||||
if (InputHelper::get('tags-token') != $currentToken)
|
||||
throw new SimpleException('Someone else has changed the tags in the meantime');
|
||||
|
||||
$suppliedTags = Model_Tag::validateTags($suppliedTags);
|
||||
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
|
||||
|
||||
$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)]);
|
||||
}
|
||||
|
||||
|
||||
/* thumbnail */
|
||||
if (!empty($_FILES['thumb']['name']))
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$suppliedFile = $_FILES['thumb'];
|
||||
self::handleUploadErrors($suppliedFile);
|
||||
|
||||
$mimeType = mime_content_type($suppliedFile['tmp_name']);
|
||||
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
|
||||
throw new SimpleException('Invalid thumbnail type "' . $mimeType . '"');
|
||||
list ($imageWidth, $imageHeight) = getimagesize($suppliedFile['tmp_name']);
|
||||
if ($imageWidth != $this->config->browsing->thumbWidth)
|
||||
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbWidth . ')');
|
||||
if ($imageWidth != $this->config->browsing->thumbHeight)
|
||||
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbHeight . ')');
|
||||
|
||||
$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 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]);
|
||||
}
|
||||
|
||||
|
||||
/* relations */
|
||||
$suppliedRelations = InputHelper::get('relations');
|
||||
if ($suppliedRelations !== null)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$relatedIds = array_filter(preg_split('/\D/', $suppliedRelations));
|
||||
$relatedPosts = [];
|
||||
foreach ($relatedIds as $relatedId)
|
||||
{
|
||||
if ($relatedId == $post->id)
|
||||
continue;
|
||||
if (count($relatedPosts) > $this->config->browsing->maxRelatedPosts)
|
||||
throw new SimpleException('Too many related posts (maximum: ' . $this->config->browsing->maxRelatedPosts . ')');
|
||||
$relatedPosts []= Model_Post::locate($relatedId);
|
||||
}
|
||||
|
||||
$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_Post::save($post);
|
||||
Model_Tag::removeUnused();
|
||||
|
||||
LogHelper::flush();
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -491,7 +288,7 @@ class PostController
|
||||
$flagged []= $key;
|
||||
SessionHelper::set('flagged', $flagged);
|
||||
|
||||
LogHelper::logEvent('post-flag', '{user} flagged {post} for moderator attention', ['post' => TextHelper::reprPost($post)]);
|
||||
LogHelper::log('{user} flagged {post} for moderator attention', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -504,15 +301,14 @@ class PostController
|
||||
public function hideAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$post->hidden = true;
|
||||
R::store($post);
|
||||
$post->setHidden(true);
|
||||
Model_Post::save($post);
|
||||
|
||||
LogHelper::logEvent('post-hide', '{user} hidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
LogHelper::log('{user} hidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -525,15 +321,14 @@ class PostController
|
||||
public function unhideAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$post->hidden = false;
|
||||
R::store($post);
|
||||
$post->setHidden(false);
|
||||
Model_Post::save($post);
|
||||
|
||||
LogHelper::logEvent('post-unhide', '{user} unhidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
LogHelper::log('{user} unhidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -546,23 +341,13 @@ class PostController
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$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
|
||||
foreach ($post->ownComment as $comment)
|
||||
{
|
||||
$comment->post = null;
|
||||
R::store($comment);
|
||||
}
|
||||
$post->ownFavoritee = [];
|
||||
$post->sharedTag = [];
|
||||
R::store($post);
|
||||
R::trash($post);
|
||||
Model_Post::remove($post);
|
||||
|
||||
LogHelper::logEvent('post-delete', '{user} deleted {post}', ['post' => TextHelper::reprPost($id)]);
|
||||
LogHelper::log('{user} deleted {post}', ['post' => TextHelper::reprPost($id)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -576,7 +361,6 @@ class PostController
|
||||
public function addFavoriteAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['favoritee' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
@ -584,12 +368,8 @@ class PostController
|
||||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
|
||||
foreach ($post->via('favoritee')->sharedUser as $fav)
|
||||
if ($fav->id == $this->context->user->id)
|
||||
throw new SimpleException('Already in favorites');
|
||||
|
||||
$post->link('favoritee')->user = $this->context->user;
|
||||
R::store($post);
|
||||
$this->context->user->addToFavorites($post);
|
||||
Model_User::save($this->context->user);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -601,7 +381,6 @@ class PostController
|
||||
public function remFavoriteAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['favoritee' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
@ -609,16 +388,8 @@ class PostController
|
||||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
|
||||
$finalKey = null;
|
||||
foreach ($post->ownFavoritee as $key => $fav)
|
||||
if ($fav->user->id == $this->context->user->id)
|
||||
$finalKey = $key;
|
||||
|
||||
if ($finalKey === null)
|
||||
throw new SimpleException('Not in favorites');
|
||||
|
||||
unset ($post->ownFavoritee[$finalKey]);
|
||||
R::store($post);
|
||||
$this->context->user->remFromFavorites($post);
|
||||
Model_User::save($this->context->user);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -639,15 +410,8 @@ class PostController
|
||||
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);
|
||||
$this->context->user->score($post, $score);
|
||||
Model_User::save($this->context->user);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -662,10 +426,10 @@ class PostController
|
||||
$post = Model_Post::locate($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FeaturePost);
|
||||
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());
|
||||
Model_Property::set(Model_Property::FeaturedPostUserName, $this->context->user->name);
|
||||
StatusHelper::success();
|
||||
LogHelper::logEvent('post-feature', '{user} featured {post} on main page', ['post' => TextHelper::reprPost($post)]);
|
||||
LogHelper::log('{user} featured {post} on main page', ['post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
|
||||
|
||||
@ -678,59 +442,29 @@ class PostController
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, [
|
||||
'uploader' => 'user',
|
||||
'tag',
|
||||
'comment',
|
||||
'ownComment.commenter' => 'user']);
|
||||
|
||||
$this->context->transport->lastSearchQuery = InputHelper::get('last-search-query');
|
||||
|
||||
if ($post->hidden)
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost, 'hidden');
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
|
||||
|
||||
$buildNextPostQuery = function($dbQuery, $id, $next)
|
||||
{
|
||||
$dbQuery->select('id')
|
||||
->from('post')
|
||||
->where($next ? 'id > ?' : 'id < ?')
|
||||
->put($id);
|
||||
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
|
||||
{
|
||||
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
|
||||
$this->context->user->hasEnabledSafety($safety);
|
||||
});
|
||||
$dbQuery->and('safety')->in('(' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->andNot('hidden');
|
||||
$dbQuery->orderBy($next ? 'id asc' : 'id desc')
|
||||
->limit(1);
|
||||
};
|
||||
|
||||
$prevPostQuery = R::$f->begin();
|
||||
$buildNextPostQuery($prevPostQuery, $id, false);
|
||||
$prevPost = $prevPostQuery->get('row');
|
||||
|
||||
$nextPostQuery = R::$f->begin();
|
||||
$buildNextPostQuery($nextPostQuery, $id, true);
|
||||
$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);
|
||||
}
|
||||
Model_Post_QueryBuilder::enableTokenLimit(false);
|
||||
$prevPostQuery = $this->context->transport->lastSearchQuery . ' prev:' . $id;
|
||||
$nextPostQuery = $this->context->transport->lastSearchQuery . ' next:' . $id;
|
||||
$prevPost = current(Model_Post::getEntities($prevPostQuery, 1, 1));
|
||||
$nextPost = current(Model_Post::getEntities($nextPostQuery, 1, 1));
|
||||
Model_Post_QueryBuilder::enableTokenLimit(true);
|
||||
|
||||
$favorite = $this->context->user->hasFavorited($post);
|
||||
$score = $this->context->user->getScore($post);
|
||||
$flagged = in_array(TextHelper::reprPost($post), SessionHelper::get('flagged', []));
|
||||
|
||||
$this->context->pageThumb = \Chibi\UrlHelper::route('post', 'thumb', ['name' => $post->name]);
|
||||
$this->context->stylesheets []= 'post-view.css';
|
||||
$this->context->stylesheets []= 'comment-small.css';
|
||||
$this->context->scripts []= 'post-view.js';
|
||||
@ -741,7 +475,7 @@ class PostController
|
||||
$this->context->transport->post = $post;
|
||||
$this->context->transport->prevPostId = $prevPost ? $prevPost['id'] : null;
|
||||
$this->context->transport->nextPostId = $nextPost ? $nextPost['id'] : null;
|
||||
$this->context->transport->tagsToken = self::serializeTags($post);
|
||||
$this->context->transport->editToken = self::serializePost($post);
|
||||
}
|
||||
|
||||
|
||||
@ -750,97 +484,27 @@ class PostController
|
||||
* Action that renders the thumbnail of the requested file and sends it to user.
|
||||
* @route /post/{name}/thumb
|
||||
*/
|
||||
public function thumbAction($name)
|
||||
public function thumbAction($name, $width = null, $height = null)
|
||||
{
|
||||
$this->context->layoutName = 'layout-file';
|
||||
|
||||
$path = $this->config->main->thumbsPath . DS . $name . '.custom';
|
||||
if (!file_exists($path))
|
||||
$path = $this->config->main->thumbsPath . DS . $name . '.default';
|
||||
$path = Model_Post::getThumbCustomPath($name, $width, $height);
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$post = Model_Post::locate($name);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts, PostSafety::toString($post->safety));
|
||||
$srcPath = $this->config->main->filesPath . DS . $post->name;
|
||||
$dstWidth = $this->config->browsing->thumbWidth;
|
||||
$dstHeight = $this->config->browsing->thumbHeight;
|
||||
|
||||
if ($post->type == PostType::Youtube)
|
||||
$path = Model_Post::getThumbDefaultPath($name, $width, $height);
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
|
||||
$contents = file_get_contents('http://img.youtube.com/vi/' . $post->orig_name . '/mqdefault.jpg');
|
||||
file_put_contents($tmpPath, $contents);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefromjpeg($tmpPath);
|
||||
$post = Model_Post::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts, PostSafety::toString($post->safety));
|
||||
$post->makeThumb($width, $height);
|
||||
if (!file_exists($path))
|
||||
$path = TextHelper::absolutePath($this->config->main->mediaPath . DS . 'img' . DS . 'thumb.jpg');
|
||||
}
|
||||
else switch ($post->mime_type)
|
||||
{
|
||||
case 'image/jpeg':
|
||||
$srcImage = imagecreatefromjpeg($srcPath);
|
||||
break;
|
||||
case 'image/png':
|
||||
$srcImage = imagecreatefrompng($srcPath);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$srcImage = imagecreatefromgif($srcPath);
|
||||
break;
|
||||
case 'application/x-shockwave-flash':
|
||||
$srcImage = null;
|
||||
exec('which dump-gnash', $tmp, $exitCode);
|
||||
if ($exitCode == 0)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
||||
exec('dump-gnash --screenshot last --screenshot-file ' . $tmpPath . ' -1 -r1 --max-advances 15 ' . $srcPath);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefrompng($tmpPath);
|
||||
}
|
||||
if (!$srcImage)
|
||||
{
|
||||
exec('which swfrender', $tmp, $exitCode);
|
||||
if ($exitCode == 0)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
||||
exec('swfrender ' . $srcPath . ' -o ' . $tmpPath);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefrompng($tmpPath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($srcImage))
|
||||
{
|
||||
switch ($this->config->browsing->thumbStyle)
|
||||
{
|
||||
case 'outside':
|
||||
$dstImage = ThumbnailHelper::cropOutside($srcImage, $dstWidth, $dstHeight);
|
||||
break;
|
||||
case 'inside':
|
||||
$dstImage = ThumbnailHelper::cropInside($srcImage, $dstWidth, $dstHeight);
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown thumbnail crop style');
|
||||
}
|
||||
|
||||
imagejpeg($dstImage, $path);
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($dstImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb.jpg';
|
||||
}
|
||||
|
||||
if (isset($tmpPath))
|
||||
unlink($tmpPath);
|
||||
}
|
||||
|
||||
if (!is_readable($path))
|
||||
throw new SimpleException('Thumbnail file is not readable');
|
||||
|
||||
$this->context->layoutName = 'layout-file';
|
||||
$this->context->transport->cacheDaysToLive = 30;
|
||||
$this->context->transport->mimeType = 'image/jpeg';
|
||||
$this->context->transport->fileHash = 'thumb' . md5($name . filemtime($path));
|
||||
@ -857,12 +521,11 @@ class PostController
|
||||
{
|
||||
$this->context->layoutName = 'layout-file';
|
||||
$post = Model_Post::locate($name, true);
|
||||
R::preload($post, ['tag']);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::RetrievePost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::RetrievePost, PostSafety::toString($post->safety));
|
||||
|
||||
$path = $this->config->main->filesPath . DS . $post->name;
|
||||
$path = TextHelper::absolutePath($this->config->main->filesPath . DS . $post->name);
|
||||
if (!file_exists($path))
|
||||
throw new SimpleException('Post file does not exist');
|
||||
if (!is_readable($path))
|
||||
@ -873,7 +536,8 @@ class PostController
|
||||
$ext = '.dat';
|
||||
$fn = sprintf('%s_%s_%s.%s',
|
||||
$this->config->main->title,
|
||||
$post->id, join(',', array_map(function($tag) { return $tag->name; }, $post->sharedTag)),
|
||||
$post->id,
|
||||
join(',', array_map(function($tag) { return $tag->name; }, $post->sharedTag)),
|
||||
$ext);
|
||||
$fn = preg_replace('/[[:^print:]]/', '', $fn);
|
||||
|
||||
@ -885,4 +549,117 @@ class PostController
|
||||
$this->context->transport->fileHash = 'post' . $post->file_hash;
|
||||
$this->context->transport->filePath = $path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function doEdit($post, $isNew)
|
||||
{
|
||||
/* file contents */
|
||||
if (!empty($_FILES['file']['name']))
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
$suppliedFile = $_FILES['file'];
|
||||
self::handleUploadErrors($suppliedFile);
|
||||
|
||||
$srcPath = $suppliedFile['tmp_name'];
|
||||
$post->setContentFromPath($srcPath);
|
||||
|
||||
if (!$isNew)
|
||||
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
elseif (InputHelper::get('url'))
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
$url = InputHelper::get('url');
|
||||
$post->setContentFromUrl($url);
|
||||
|
||||
if (!$isNew)
|
||||
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
|
||||
/* safety */
|
||||
$suppliedSafety = InputHelper::get('safety');
|
||||
if ($suppliedSafety !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
$oldSafety = $post->safety;
|
||||
$post->setSafety($suppliedSafety);
|
||||
$newSafety = $post->safety;
|
||||
|
||||
if ($oldSafety != $newSafety)
|
||||
LogHelper::log('{user} changed safety of {post} to {safety}', ['post' => TextHelper::reprPost($post), 'safety' => PostSafety::toString($post->safety)]);
|
||||
}
|
||||
|
||||
/* tags */
|
||||
$suppliedTags = InputHelper::get('tags');
|
||||
if ($suppliedTags !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
$oldTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
|
||||
$post->setTagsFromText($suppliedTags);
|
||||
$newTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
|
||||
|
||||
foreach (array_diff($oldTags, $newTags) as $tag)
|
||||
LogHelper::log('{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
|
||||
foreach (array_diff($newTags, $oldTags) as $tag)
|
||||
LogHelper::log('{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
|
||||
/* source */
|
||||
$suppliedSource = InputHelper::get('source');
|
||||
if ($suppliedSource !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
$oldSource = $post->source;
|
||||
$post->setSource($suppliedSource);
|
||||
$newSource = $post->source;
|
||||
|
||||
if ($oldSource != $newSource)
|
||||
LogHelper::log('{user} changed source of {post} to {source}', ['post' => TextHelper::reprPost($post), 'source' => $post->source]);
|
||||
}
|
||||
|
||||
/* relations */
|
||||
$suppliedRelations = InputHelper::get('relations');
|
||||
if ($suppliedRelations !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
$oldRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
|
||||
$post->setRelationsFromText($suppliedRelations);
|
||||
$newRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
|
||||
|
||||
foreach (array_diff($oldRelatedIds, $newRelatedIds) as $post2id)
|
||||
LogHelper::log('{user} removed relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
|
||||
|
||||
foreach (array_diff($newRelatedIds, $oldRelatedIds) as $post2id)
|
||||
LogHelper::log('{user} added relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
|
||||
}
|
||||
|
||||
/* thumbnail */
|
||||
if (!empty($_FILES['thumb']['name']))
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
|
||||
$suppliedFile = $_FILES['thumb'];
|
||||
self::handleUploadErrors($suppliedFile);
|
||||
|
||||
$srcPath = $suppliedFile['tmp_name'];
|
||||
$post->setCustomThumbnailFromPath($srcPath);
|
||||
|
||||
LogHelper::log('{user} changed thumb of {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ class TagController
|
||||
{
|
||||
/**
|
||||
* @route /tags
|
||||
* @route /tags/{filter}
|
||||
* @validate filter [a-zA-Z\32:,_-]+
|
||||
*/
|
||||
public function listAction()
|
||||
public function listAction($filter = null)
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
@ -12,9 +14,10 @@ class TagController
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListTags);
|
||||
$suppliedFilter = InputHelper::get('filter');
|
||||
$suppliedFilter = $filter ?: InputHelper::get('filter') ?: 'order:alpha,asc';
|
||||
|
||||
$tags = Model_Tag::getEntitiesRows($suppliedFilter, null, null);
|
||||
$this->context->filter = $suppliedFilter;
|
||||
$this->context->transport->tags = $tags;
|
||||
|
||||
if ($this->context->json)
|
||||
@ -22,20 +25,11 @@ class TagController
|
||||
$this->context->transport->tags = array_values(array_map(function($tag) {
|
||||
return ['name' => $tag['name'], 'count' => $tag['post_count']];
|
||||
}, $this->context->transport->tags));
|
||||
usort($this->context->transport->tags, function($a, $b) {
|
||||
return $a['count'] > $b['count'] ? -1 : 1;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
uasort($this->context->transport->tags, function($a, $b) {
|
||||
return strnatcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /tags/merge
|
||||
* @route /tag/merge
|
||||
*/
|
||||
public function mergeAction()
|
||||
{
|
||||
@ -47,6 +41,8 @@ class TagController
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
Model_Tag::removeUnused();
|
||||
|
||||
$suppliedSourceTag = InputHelper::get('source-tag');
|
||||
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
@ -66,18 +62,18 @@ class TagController
|
||||
if ($postTag->id == $sourceTag->id)
|
||||
unset($post->sharedTag[$key]);
|
||||
$post->sharedTag []= $targetTag;
|
||||
R::store($post);
|
||||
Model_Post::save($post);
|
||||
}
|
||||
R::trash($sourceTag);
|
||||
Model_Tag::remove($sourceTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
LogHelper::logEvent('tag-merge', '{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
LogHelper::log('{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /tags/rename
|
||||
* @route /tag/rename
|
||||
*/
|
||||
public function renameAction()
|
||||
{
|
||||
@ -89,21 +85,24 @@ class TagController
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
Model_Tag::removeUnused();
|
||||
|
||||
$suppliedSourceTag = InputHelper::get('source-tag');
|
||||
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
|
||||
$suppliedTargetTag = InputHelper::get('target-tag');
|
||||
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
|
||||
$targetTag = Model_Tag::locate($suppliedTargetTag, false);
|
||||
if ($targetTag)
|
||||
|
||||
if ($targetTag and $targetTag->id != $sourceTag->id)
|
||||
throw new SimpleException('Target tag already exists');
|
||||
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
$sourceTag->name = $suppliedTargetTag;
|
||||
R::store($sourceTag);
|
||||
Model_Tag::save($sourceTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
LogHelper::logEvent('tag-rename', '{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
LogHelper::log('{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -125,8 +124,9 @@ class TagController
|
||||
if (!$suppliedQuery)
|
||||
$suppliedQuery = ' ';
|
||||
$suppliedTag = InputHelper::get('tag');
|
||||
$suppliedTag = Model_Tag::validateTag($suppliedTag);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', ['source' => 'mass-tag', 'query' => urlencode($suppliedQuery), 'additionalInfo' => $suppliedTag]));
|
||||
if (!empty($suppliedTag))
|
||||
$suppliedTag = Model_Tag::validateTag($suppliedTag);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', ['source' => 'mass-tag', 'query' => $suppliedQuery, 'additionalInfo' => $suppliedTag]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,18 @@
|
||||
<?php
|
||||
class UserController
|
||||
{
|
||||
private function loadUserView($user)
|
||||
{
|
||||
$flagged = in_array(TextHelper::reprUser($user), SessionHelper::get('flagged', []));
|
||||
$this->context->flagged = $flagged;
|
||||
$this->context->transport->user = $user;
|
||||
$this->context->handleExceptions = true;
|
||||
$this->context->viewName = 'user-view';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->stylesheets []= 'user-view.css';
|
||||
$this->context->subTitle = $user->name;
|
||||
}
|
||||
|
||||
private static function sendTokenizedEmail(
|
||||
$user,
|
||||
$body,
|
||||
@ -8,7 +20,7 @@ class UserController
|
||||
$senderName,
|
||||
$senderEmail,
|
||||
$recipientEmail,
|
||||
$tokens)
|
||||
$linkActionName)
|
||||
{
|
||||
//prepare unique user token
|
||||
do
|
||||
@ -24,8 +36,11 @@ class UserController
|
||||
R::store($token);
|
||||
|
||||
\Chibi\Registry::getContext()->mailSent = true;
|
||||
$tokens = [];
|
||||
$tokens['host'] = $_SERVER['HTTP_HOST'];
|
||||
$tokens['token'] = $tokenText;
|
||||
if ($linkActionName !== null)
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', $linkActionName, ['token' => $tokenText]);
|
||||
|
||||
$body = wordwrap(TextHelper::replaceTokens($body, $tokens), 70);
|
||||
$subject = TextHelper::replaceTokens($subject, $tokens);
|
||||
@ -50,7 +65,7 @@ class UserController
|
||||
$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]);
|
||||
LogHelper::log('Sending e-mail with subject "{subject}" to {mail}', ['subject' => $subject, 'mail' => $recipientEmail]);
|
||||
}
|
||||
|
||||
private static function sendEmailChangeConfirmation($user)
|
||||
@ -63,9 +78,6 @@ class UserController
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', 'activation', ['token' => '{token}']);
|
||||
|
||||
return self::sendTokenizedEmail(
|
||||
$user,
|
||||
$regConfig->confirmationEmailBody,
|
||||
@ -73,16 +85,13 @@ class UserController
|
||||
$regConfig->confirmationEmailSenderName,
|
||||
$regConfig->confirmationEmailSenderEmail,
|
||||
$user->email_unconfirmed,
|
||||
$tokens);
|
||||
'activation');
|
||||
}
|
||||
|
||||
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,
|
||||
@ -90,7 +99,7 @@ class UserController
|
||||
$regConfig->passwordResetEmailSenderName,
|
||||
$regConfig->passwordResetEmailSenderEmail,
|
||||
$user->email_confirmed,
|
||||
$tokens);
|
||||
'password-reset');
|
||||
}
|
||||
|
||||
|
||||
@ -156,7 +165,7 @@ class UserController
|
||||
$flagged []= $key;
|
||||
SessionHelper::set('flagged', $flagged);
|
||||
|
||||
LogHelper::logEvent('user-flag', '{user} flagged {subject} for moderator attention', ['subject' => TextHelper::reprUser($user)]);
|
||||
LogHelper::log('{user} flagged {subject} for moderator attention', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -175,9 +184,9 @@ class UserController
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = true;
|
||||
R::store($user);
|
||||
Model_User::save($user);
|
||||
|
||||
LogHelper::logEvent('ban', '{user} banned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
LogHelper::log('{user} banned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -196,9 +205,9 @@ class UserController
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = false;
|
||||
R::store($user);
|
||||
Model_User::save($user);
|
||||
|
||||
LogHelper::logEvent('unban', '{user} unbanned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
LogHelper::log('{user} unbanned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -216,8 +225,8 @@ class UserController
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->staff_confirmed = true;
|
||||
R::store($user);
|
||||
LogHelper::logEvent('reg-accept', '{user} confirmed account for {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
Model_User::save($user);
|
||||
LogHelper::log('{user} confirmed {subject}\'s account', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -234,13 +243,8 @@ class UserController
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeleteUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
$this->context->handleExceptions = true;
|
||||
$this->context->transport->user = $user;
|
||||
$this->loadUserView($user);
|
||||
$this->context->transport->tab = 'delete';
|
||||
$this->context->viewName = 'user-view';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->stylesheets []= 'user-view.css';
|
||||
$this->context->subTitle = $name;
|
||||
|
||||
$this->context->suppliedCurrentPassword = $suppliedCurrentPassword = InputHelper::get('current-password');
|
||||
|
||||
@ -253,24 +257,14 @@ class UserController
|
||||
if ($suppliedPasswordHash != $user->pass_hash)
|
||||
throw new SimpleException('Must supply valid password');
|
||||
}
|
||||
foreach ($user->alias('commenter')->ownComment as $comment)
|
||||
{
|
||||
$comment->commenter = null;
|
||||
R::store($comment);
|
||||
}
|
||||
foreach ($user->alias('uploader')->ownPost as $post)
|
||||
{
|
||||
$post->uploader = null;
|
||||
R::store($post);
|
||||
}
|
||||
$user->ownFavoritee = [];
|
||||
if ($user->id == $this->context->user->id)
|
||||
|
||||
$oldId = $user->id;
|
||||
Model_User::remove($user);
|
||||
if ($oldId == $this->context->user->id)
|
||||
AuthController::doLogOut();
|
||||
R::store($user);
|
||||
R::trash($user);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
LogHelper::logEvent('user-del', '{user} removed account for {subject}', ['subject' => TextHelper::reprUser($name)]);
|
||||
LogHelper::log('{user} removed {subject}\'s account', ['subject' => TextHelper::reprUser($name)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
@ -287,13 +281,8 @@ class UserController
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
$this->context->handleExceptions = true;
|
||||
$this->context->transport->user = $user;
|
||||
$this->loadUserView($user);
|
||||
$this->context->transport->tab = 'settings';
|
||||
$this->context->viewName = 'user-view';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->stylesheets []= 'user-view.css';
|
||||
$this->context->subTitle = $name;
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
@ -304,8 +293,9 @@ class UserController
|
||||
$user->enableSafety($safety, in_array($safety, $suppliedSafety));
|
||||
|
||||
$user->enableEndlessScrolling(InputHelper::get('endless-scrolling'));
|
||||
$user->enablePostTagTitles(InputHelper::get('post-tag-titles'));
|
||||
|
||||
R::store($user);
|
||||
Model_User::save($user);
|
||||
if ($user->id == $this->context->user->id)
|
||||
$this->context->user = $user;
|
||||
AuthController::doReLog();
|
||||
@ -326,13 +316,8 @@ class UserController
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
$this->context->handleExceptions = true;
|
||||
$this->context->transport->user = $user;
|
||||
$this->loadUserView($user);
|
||||
$this->context->transport->tab = 'edit';
|
||||
$this->context->viewName = 'user-view';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->stylesheets []= 'user-view.css';
|
||||
$this->context->subTitle = $name;
|
||||
|
||||
$this->context->suppliedCurrentPassword = $suppliedCurrentPassword = InputHelper::get('current-password');
|
||||
$this->context->suppliedName = $suppliedName = InputHelper::get('name');
|
||||
@ -353,7 +338,7 @@ class UserController
|
||||
$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)]);
|
||||
LogHelper::log('{user} renamed {old} to {new}', ['old' => TextHelper::reprUser($oldName), 'new' => TextHelper::reprUser($suppliedName)]);
|
||||
}
|
||||
|
||||
if ($suppliedPassword1 != '')
|
||||
@ -363,7 +348,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)]);
|
||||
LogHelper::log('{user} changed {subject}\'s password', ['subject' => TextHelper::reprUser($user)]);
|
||||
}
|
||||
|
||||
if ($suppliedEmail != '' and $suppliedEmail != $user->email_confirmed)
|
||||
@ -375,13 +360,13 @@ class UserController
|
||||
$user->email_unconfirmed = $suppliedEmail;
|
||||
if (!empty($user->email_unconfirmed))
|
||||
$confirmMail = true;
|
||||
LogHelper::logEvent('user-edit', '{user} changed e-mail to {mail}', ['mail' => $suppliedEmail]);
|
||||
LogHelper::log('{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]);
|
||||
LogHelper::log('{user} changed {subject}\'s e-mail to {mail}', ['subject' => TextHelper::reprUser($user), 'mail' => $suppliedEmail]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,7 +375,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)]);
|
||||
LogHelper::log('{user} changed {subject}\'s access rank to {rank}', ['subject' => TextHelper::reprUser($user), 'rank' => AccessRank::toString($suppliedAccessRank)]);
|
||||
}
|
||||
|
||||
if ($this->context->user->id == $user->id)
|
||||
@ -399,7 +384,7 @@ class UserController
|
||||
if ($suppliedPasswordHash != $currentPasswordHash)
|
||||
throw new SimpleException('Must supply valid current password');
|
||||
}
|
||||
R::store($user);
|
||||
Model_User::save($user);
|
||||
|
||||
if ($confirmMail)
|
||||
self::sendEmailChangeConfirmation($user);
|
||||
@ -437,14 +422,13 @@ class UserController
|
||||
$page = 1;
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->stylesheets []= 'user-view.css';
|
||||
$this->loadUserView($user);
|
||||
$this->context->stylesheets []= 'post-list.css';
|
||||
$this->context->stylesheets []= 'post-small.css';
|
||||
$this->context->stylesheets []= 'paginator.css';
|
||||
$this->context->scripts []= 'post-list.js';
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
$this->context->subTitle = $name;
|
||||
|
||||
$query = '';
|
||||
if ($tab == 'uploads')
|
||||
@ -459,11 +443,8 @@ 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->lastSearchQuery = $query;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
$this->context->transport->paginator->pageCount = $pageCount;
|
||||
@ -489,7 +470,7 @@ class UserController
|
||||
|
||||
AuthController::doReLog();
|
||||
if (!$this->context->user->anonymous)
|
||||
R::store($this->context->user);
|
||||
Model_User::save($this->context->user);
|
||||
|
||||
StatusHelper::success();
|
||||
}
|
||||
@ -534,9 +515,8 @@ class UserController
|
||||
throw new SimpleException('E-mail address is required - you will be sent confirmation e-mail.');
|
||||
|
||||
//register the user
|
||||
$dbUser = R::dispense('user');
|
||||
$dbUser = Model_User::create();
|
||||
$dbUser->name = $suppliedName;
|
||||
$dbUser->pass_salt = md5(mt_rand() . uniqid());
|
||||
$dbUser->pass_hash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
|
||||
$dbUser->email_unconfirmed = $suppliedEmail;
|
||||
|
||||
@ -557,7 +537,7 @@ class UserController
|
||||
}
|
||||
|
||||
//save the user to db if everything went okay
|
||||
R::store($dbUser);
|
||||
Model_User::save($dbUser);
|
||||
|
||||
if (!empty($dbUser->email_unconfirmed))
|
||||
self::sendEmailChangeConfirmation($dbUser);
|
||||
@ -572,7 +552,7 @@ class UserController
|
||||
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)]);
|
||||
LogHelper::log('{subject} just signed up', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
StatusHelper::success($message);
|
||||
|
||||
if (!$this->config->registration->needEmailForRegistering and !$this->config->registration->staffActivation)
|
||||
@ -600,9 +580,9 @@ class UserController
|
||||
$dbUser->email_unconfirmed = null;
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
R::store($dbUser);
|
||||
Model_User::save($dbUser);
|
||||
|
||||
LogHelper::logEvent('user-activation', '{subject} just activated account', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
LogHelper::log('{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.';
|
||||
@ -637,9 +617,9 @@ class UserController
|
||||
$dbUser->pass_hash = Model_User::hashPassword($randomPassword, $dbUser->pass_salt);
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
R::store($dbUser);
|
||||
Model_User::save($dbUser);
|
||||
|
||||
LogHelper::logEvent('user-pass-reset', '{subject} just reset password', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
LogHelper::log('{subject} just reset password', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
$message = 'Password reset successful. Your new password is **' . $randomPassword . '**.';
|
||||
StatusHelper::success($message);
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
<?php
|
||||
class CustomMarkdown extends \Michelf\Markdown
|
||||
{
|
||||
public function __construct()
|
||||
protected $simple = false;
|
||||
|
||||
public function __construct($simple = false)
|
||||
{
|
||||
$this->simple = $simple;
|
||||
$this->no_markup = true;
|
||||
$this->span_gamut += ['doSpoilers' => 71];
|
||||
$this->block_gamut += ['doSpoilers' => 71];
|
||||
$this->span_gamut += ['doSearchPermalinks' => 72];
|
||||
$this->span_gamut += ['doStrike' => 6];
|
||||
$this->span_gamut += ['doUsers' => 7];
|
||||
$this->span_gamut += ['doPosts' => 8];
|
||||
$this->span_gamut += ['doTags' => 9];
|
||||
@ -22,10 +27,39 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function formParagraphs($text)
|
||||
{
|
||||
if ($this->simple)
|
||||
{
|
||||
$text = preg_replace('/\A\n+|\n+\z/', '', $text);
|
||||
$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ($grafs as $key => $value)
|
||||
{
|
||||
if (!preg_match('/^B\x1A[0-9]+B$/', $value))
|
||||
{
|
||||
$value = $this->runSpanGamut($value);
|
||||
$grafs[$key] = $this->unhash($value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$grafs[$key] = $this->html_hashes[$value];
|
||||
}
|
||||
}
|
||||
return implode("\n\n", $grafs);
|
||||
}
|
||||
return parent::formParagraphs($text);
|
||||
}
|
||||
|
||||
public static function simpleTransform($text)
|
||||
{
|
||||
$parser = new self(true);
|
||||
return $parser->transform($text);
|
||||
}
|
||||
|
||||
protected function doAutoLinks2($text)
|
||||
{
|
||||
$text = preg_replace_callback('{(?<!<)((https?|ftp):[^\'"><\s]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
$text = preg_replace_callback('{(?<!\w)(www\.[^\'"><\s]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
$text = preg_replace_callback('{(?<!<)((https?|ftp):[^\'"><\s(){}]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
$text = preg_replace_callback('{(?<![^\s\(\)\[\]])(www\.[^\'"><\s(){}]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
@ -42,37 +76,57 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
|
||||
protected function doHardBreaks($text)
|
||||
{
|
||||
return preg_replace_callback('/\n/', [&$this, '_doHardBreaks_callback'], $text);
|
||||
return preg_replace_callback('/\n(?=[\[\]\(\)\w])/', [&$this, '_doHardBreaks_callback'], $text);
|
||||
}
|
||||
|
||||
protected function doStrike($text)
|
||||
{
|
||||
return preg_replace_callback('{(~~|---)([^~]+)\1}', function($x)
|
||||
{
|
||||
return $this->hashPart('<del>' . $x[2] . '</del>');
|
||||
}, $text);
|
||||
}
|
||||
|
||||
protected function doSpoilers($text)
|
||||
{
|
||||
if (is_array($text))
|
||||
$text = $this->hashPart('<span class="spoiler">') . $text[1] . $this->hashPart('</span>');
|
||||
$text = $this->hashBlock('<span class="spoiler">') . $this->runSpanGamut($text[1]) . $this->hashBlock('</span>');
|
||||
return preg_replace_callback('{\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\])|(?R))+)\[\/spoiler\]}is', [__CLASS__, 'doSpoilers'], $text);
|
||||
}
|
||||
|
||||
protected function doPosts($text)
|
||||
{
|
||||
return preg_replace_callback('/(?:(?<!\w))@(\d+)/', function($x)
|
||||
$link = \Chibi\UrlHelper::route('post', 'view', ['id' => '_post_']);
|
||||
return preg_replace_callback('/(?:(?<![^\s\(\)\[\]]))@(\d+)/', function($x) use ($link)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('post', 'view', ['id' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
return $this->hashPart('<a href="' . str_replace('_post_', $x[1], $link) . '">' . $x[0] . '</a>');
|
||||
}, $text);
|
||||
}
|
||||
|
||||
protected function doTags($text)
|
||||
{
|
||||
return preg_replace_callback('/(?:(?<!\w))#([a-zA-Z0-9_-]+)/', function($x)
|
||||
$link = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']);
|
||||
return preg_replace_callback('/(?:(?<![^\s\(\)\[\]]))#([a-zA-Z0-9_-]+)/', function($x) use ($link)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('post', 'list', ['query' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
return $this->hashPart('<a href="' . str_replace('_query_', $x[1], $link) . '">' . $x[0] . '</a>');
|
||||
}, $text);
|
||||
}
|
||||
|
||||
protected function doUsers($text)
|
||||
{
|
||||
return preg_replace_callback('/(?:(?<!\w))\+([a-zA-Z0-9_-]+)/', function($x)
|
||||
$link = \Chibi\UrlHelper::route('user', 'view', ['name' => '_name_']);
|
||||
return preg_replace_callback('/(?:(?<![^\s\(\)\[\]]))\+([a-zA-Z0-9_-]+)/', function($x) use ($link)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('user', 'view', ['name' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
return $this->hashPart('<a href="' . str_replace('_name_', $x[1], $link) . '">' . $x[0] . '</a>');
|
||||
}, $text);
|
||||
}
|
||||
|
||||
protected function doSearchPermalinks($text)
|
||||
{
|
||||
$link = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']);
|
||||
return preg_replace_callback('{\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]}is', function($x) use ($link)
|
||||
{
|
||||
return $this->hashPart('<a href="' . str_replace('_query_', $x[1], $link) . '">' . $x[1] . '</a>');
|
||||
}, $text);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,15 @@
|
||||
<?php
|
||||
class LogHelper
|
||||
{
|
||||
static $path;
|
||||
static $context;
|
||||
static $config;
|
||||
static $autoFlush;
|
||||
static $content;
|
||||
static $buffer;
|
||||
|
||||
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 = '';
|
||||
self::$buffer = [];
|
||||
}
|
||||
|
||||
public static function bufferChanges()
|
||||
@ -29,40 +24,75 @@ class LogHelper
|
||||
throw new SimpleException('Cannot write to log files');
|
||||
if (flock($fh, LOCK_EX))
|
||||
{
|
||||
fwrite($fh, self::$content);
|
||||
foreach (self::$buffer as $logEvent)
|
||||
fwrite($fh, $logEvent->getFullText() . PHP_EOL);
|
||||
fflush($fh);
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
}
|
||||
self::$content = '';
|
||||
self::$buffer = [];
|
||||
self::$autoFlush = true;
|
||||
}
|
||||
|
||||
public static function getLogPath()
|
||||
{
|
||||
return self::$path;
|
||||
return TextHelper::absolutePath(
|
||||
\Chibi\Registry::getConfig()->main->logsPath . DS . date('Y-m') . '.log');
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
self::$buffer []= new LogEvent($text, $tokens);
|
||||
if (self::$autoFlush)
|
||||
self::flush();
|
||||
}
|
||||
|
||||
public static function logEvent($event, $text, array $tokens = [])
|
||||
//methods for manipulating buffered logs
|
||||
public static function getBuffer()
|
||||
{
|
||||
return self::log(sprintf('[%s] %s', $event, $text), $tokens);
|
||||
return self::$buffer;
|
||||
}
|
||||
|
||||
public static function setBuffer(array $buffer)
|
||||
{
|
||||
self::$buffer = $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
class LogEvent
|
||||
{
|
||||
public $timestamp;
|
||||
public $text;
|
||||
public $ip;
|
||||
public $tokens;
|
||||
|
||||
public function __construct($text, array $tokens = [])
|
||||
{
|
||||
$this->timestamp = time();
|
||||
$this->text = $text;
|
||||
$this->ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
|
||||
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$tokens['anon'] = Model_User::getAnonymousName();
|
||||
if ($context->loggedIn and isset($context->user))
|
||||
$tokens['user'] = TextHelper::reprUser($context->user->name);
|
||||
else
|
||||
$tokens['user'] = $tokens['anon'];
|
||||
$this->tokens = $tokens;
|
||||
}
|
||||
|
||||
public function getText()
|
||||
{
|
||||
return TextHelper::replaceTokens($this->text, $this->tokens);
|
||||
}
|
||||
|
||||
public function getFullText()
|
||||
{
|
||||
$date = date('Y-m-d H:i:s', $this->timestamp);
|
||||
$ip = $this->ip;
|
||||
$text = $this->getText();
|
||||
$line = sprintf('[%s] %s: %s', $date, $ip, $text);
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,38 +146,34 @@ class TextHelper
|
||||
public static function jsonEncode($obj, $illegalKeysRegex = '')
|
||||
{
|
||||
if (is_array($obj))
|
||||
$set = function($key, $val) use ($obj) { $obj[$key] = $val; };
|
||||
else
|
||||
$set = function($key, $val) use ($obj) { $obj->$key = $val; };
|
||||
|
||||
foreach ($obj as $key => $val)
|
||||
{
|
||||
foreach ($obj as $key => $val)
|
||||
if ($val instanceof RedBean_OODBBean)
|
||||
{
|
||||
if ($val instanceof RedBean_OODBBean)
|
||||
{
|
||||
$obj[$key] = R::exportAll($val);
|
||||
}
|
||||
$set($key, R::exportAll($val));
|
||||
}
|
||||
}
|
||||
elseif (is_object($obj))
|
||||
{
|
||||
foreach ($obj as $key => $val)
|
||||
elseif ($val instanceof Exception)
|
||||
{
|
||||
if ($val instanceof RedBean_OODBBean)
|
||||
{
|
||||
$obj->$key = R::exportAll($val);
|
||||
}
|
||||
$set($key, ['message' => $val->getMessage(), 'trace' => $val->getTraceAsString()]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($illegalKeysRegex))
|
||||
self::removeUnsafeKeys($obj, $illegalKeysRegex);
|
||||
|
||||
return json_encode($obj);
|
||||
return json_encode($obj, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
public static function parseMarkdown($text, $inline = false)
|
||||
public static function parseMarkdown($text, $simple = false)
|
||||
{
|
||||
$output = CustomMarkdown::defaultTransform($text);
|
||||
if ($inline)
|
||||
$output = preg_replace('{</?p>}', '', $output);
|
||||
return $output;
|
||||
if ($simple)
|
||||
return CustomMarkdown::simpleTransform($text);
|
||||
else
|
||||
return CustomMarkdown::defaultTransform($text);
|
||||
}
|
||||
|
||||
public static function reprPost($post)
|
||||
@ -228,4 +224,13 @@ class TextHelper
|
||||
$path = rtrim($path, DS);
|
||||
return $path;
|
||||
}
|
||||
|
||||
public static function absolutePath($path)
|
||||
{
|
||||
if ($path{0} != DS)
|
||||
$path = \Chibi\Registry::getContext()->rootDir . DS . $path;
|
||||
|
||||
$path = self::cleanPath($path);
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
@ -50,4 +50,34 @@ abstract class AbstractModel extends RedBean_SimpleModel
|
||||
$dbQuery->from($table);
|
||||
return intval($dbQuery->get('row')['count']);
|
||||
}
|
||||
|
||||
public static function create()
|
||||
{
|
||||
return R::dispense(static::getTableName());
|
||||
}
|
||||
|
||||
public static function remove($entity)
|
||||
{
|
||||
R::trash($entity);
|
||||
}
|
||||
|
||||
public static function save($entity)
|
||||
{
|
||||
R::store($entity);
|
||||
}
|
||||
|
||||
/* FUSE methods for RedBeanPHP, preventing some aliasing errors */
|
||||
public function open()
|
||||
{
|
||||
$this->preload();
|
||||
}
|
||||
|
||||
public function after_update()
|
||||
{
|
||||
$this->preload();
|
||||
}
|
||||
|
||||
public function preload()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,13 @@ class Model_Comment extends AbstractModel
|
||||
return 'Model_Comment_QueryBuilder';
|
||||
}
|
||||
|
||||
public function preload()
|
||||
{
|
||||
R::preload($this->bean, ['commenter' => 'user', 'post', 'post.uploader' => 'user']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$comment = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
||||
@ -23,6 +30,8 @@ class Model_Comment extends AbstractModel
|
||||
return $comment;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateText($text)
|
||||
{
|
||||
$text = trim($text);
|
||||
|
@ -1,6 +1,30 @@
|
||||
<?php
|
||||
class Model_Post extends AbstractModel
|
||||
{
|
||||
protected static $config;
|
||||
|
||||
public static function initModel()
|
||||
{
|
||||
self::$config = \Chibi\Registry::getConfig();
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'post';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Post_QueryBuilder';
|
||||
}
|
||||
|
||||
public function preload()
|
||||
{
|
||||
R::preload($this->bean, ['uploader' => 'user','favoritee' => 'user']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function locate($key, $disallowNumeric = false, $throw = true)
|
||||
{
|
||||
if (is_numeric($key) and !$disallowNumeric)
|
||||
@ -26,6 +50,42 @@ class Model_Post extends AbstractModel
|
||||
return $post;
|
||||
}
|
||||
|
||||
public static function create()
|
||||
{
|
||||
$post = R::dispense(self::getTableName());
|
||||
$post->hidden = false;
|
||||
$post->upload_date = time();
|
||||
do
|
||||
{
|
||||
$post->name = md5(mt_rand() . uniqid());
|
||||
}
|
||||
while (file_exists(self::getFullPath($post->name)));
|
||||
return $post;
|
||||
}
|
||||
|
||||
public static function remove($post)
|
||||
{
|
||||
//remove stuff from auxiliary tables
|
||||
R::trashAll(R::find('postscore', 'post_id = ?', [$post->id]));
|
||||
R::trashAll(R::find('crossref', 'post_id = ? OR post2_id = ?', [$post->id, $post->id]));
|
||||
foreach ($post->ownComment as $comment)
|
||||
{
|
||||
$comment->post = null;
|
||||
R::store($comment);
|
||||
}
|
||||
$post->ownFavoritee = [];
|
||||
$post->sharedTag = [];
|
||||
R::store($post);
|
||||
R::trash($post);
|
||||
}
|
||||
|
||||
public static function save($post)
|
||||
{
|
||||
R::store($post);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateSafety($safety)
|
||||
{
|
||||
$safety = intval($safety);
|
||||
@ -47,13 +107,339 @@ class Model_Post extends AbstractModel
|
||||
return $source;
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
private static function validateThumbSize($width, $height)
|
||||
{
|
||||
return 'post';
|
||||
$width = $width === null ? self::$config->browsing->thumbWidth : $width;
|
||||
$height = $height === null ? self::$config->browsing->thumbHeight : $height;
|
||||
$width = min(1000, max(1, $width));
|
||||
$height = min(1000, max(1, $height));
|
||||
return [$width, $height];
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
public static function getAllPostCount()
|
||||
{
|
||||
return 'Model_Post_QueryBuilder';
|
||||
return R::$f
|
||||
->begin()
|
||||
->select('count(1)')
|
||||
->as('count')
|
||||
->from(self::getTableName())
|
||||
->get('row')['count'];
|
||||
}
|
||||
|
||||
private static function getThumbPathTokenized($text, $name, $width = null, $height = null)
|
||||
{
|
||||
list ($width, $height) = self::validateThumbSize($width, $height);
|
||||
|
||||
return TextHelper::absolutePath(TextHelper::replaceTokens($text, [
|
||||
'fullpath' => self::$config->main->thumbsPath . DS . $name,
|
||||
'width' => $width,
|
||||
'height' => $height]));
|
||||
}
|
||||
|
||||
public static function getThumbCustomPath($name, $width = null, $height = null)
|
||||
{
|
||||
return self::getThumbPathTokenized('{fullpath}.custom', $name, $width, $height);
|
||||
}
|
||||
|
||||
public static function getThumbDefaultPath($name, $width = null, $height = null)
|
||||
{
|
||||
return self::getThumbPathTokenized('{fullpath}-{width}x{height}.default', $name, $width, $height);
|
||||
}
|
||||
|
||||
public static function getFullPath($name)
|
||||
{
|
||||
return TextHelper::absolutePath(self::$config->main->filesPath . DS . $name);
|
||||
}
|
||||
|
||||
public function isTaggedWith($tagName)
|
||||
{
|
||||
$tagName = trim(strtolower($tagName));
|
||||
foreach ($this->sharedTag as $tag)
|
||||
if (trim(strtolower($tag->name)) == $tagName)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasCustomThumb($width = null, $height = null)
|
||||
{
|
||||
$thumbPath = self::getThumbCustomPath($this->name, $width, $height);
|
||||
return file_exists($thumbPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setHidden($hidden)
|
||||
{
|
||||
$this->hidden = boolval($hidden);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setSafety($safety)
|
||||
{
|
||||
$this->safety = self::validateSafety($safety);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setSource($source)
|
||||
{
|
||||
$this->source = self::validateSource($source);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setTagsFromText($tagsText)
|
||||
{
|
||||
$tagNames = Model_Tag::validateTags($tagsText);
|
||||
$dbTags = Model_Tag::insertOrUpdate($tagNames);
|
||||
|
||||
$this->sharedTag = $dbTags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setRelationsFromText($relationsText)
|
||||
{
|
||||
$relatedIds = array_filter(preg_split('/\D/', $relationsText));
|
||||
|
||||
$relatedPosts = [];
|
||||
foreach ($relatedIds as $relatedId)
|
||||
{
|
||||
if ($relatedId == $this->id)
|
||||
continue;
|
||||
|
||||
if (count($relatedPosts) > self::$config->browsing->maxRelatedPosts)
|
||||
throw new SimpleException('Too many related posts (maximum: ' . self::$config->browsing->maxRelatedPosts . ')');
|
||||
|
||||
$relatedPosts []= self::locate($relatedId);
|
||||
}
|
||||
|
||||
$this->bean->via('crossref')->sharedPost = $relatedPosts;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setCustomThumbnailFromPath($srcPath)
|
||||
{
|
||||
$mimeType = mime_content_type($srcPath);
|
||||
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
|
||||
throw new SimpleException('Invalid thumbnail type "' . $mimeType . '"');
|
||||
|
||||
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
|
||||
if ($imageWidth != self::$config->browsing->thumbWidth)
|
||||
throw new SimpleException('Invalid thumbnail width (should be ' . self::$config->browsing->thumbWidth . ')');
|
||||
if ($imageWidth != self::$config->browsing->thumbHeight)
|
||||
throw new SimpleException('Invalid thumbnail width (should be ' . self::$config->browsing->thumbHeight . ')');
|
||||
|
||||
$dstPath = self::getThumbCustomPath($this->name);
|
||||
|
||||
if (is_uploaded_file($srcPath))
|
||||
move_uploaded_file($srcPath, $dstPath);
|
||||
else
|
||||
rename($srcPath, $dstPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setContentFromPath($srcPath)
|
||||
{
|
||||
$this->file_size = filesize($srcPath);
|
||||
$this->file_hash = md5_file($srcPath);
|
||||
|
||||
if ($this->file_size == 0)
|
||||
throw new SimpleException('Specified file is empty');
|
||||
|
||||
$this->mime_type = mime_content_type($srcPath);
|
||||
switch ($this->mime_type)
|
||||
{
|
||||
case 'image/gif':
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
|
||||
$this->type = PostType::Image;
|
||||
$this->image_width = $imageWidth;
|
||||
$this->image_height = $imageHeight;
|
||||
break;
|
||||
case 'application/x-shockwave-flash':
|
||||
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
|
||||
$this->type = PostType::Flash;
|
||||
$this->image_width = $imageWidth;
|
||||
$this->image_height = $imageHeight;
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Invalid file type "' . $this->mime_type . '"');
|
||||
}
|
||||
|
||||
$this->orig_name = basename($srcPath);
|
||||
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$this->file_hash]);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||
|
||||
$dstPath = $this->getFullPath($this->name);
|
||||
|
||||
if (is_uploaded_file($srcPath))
|
||||
move_uploaded_file($srcPath, $dstPath);
|
||||
else
|
||||
rename($srcPath, $dstPath);
|
||||
|
||||
$thumbPath = self::getThumbDefaultPath($this->name);
|
||||
if (file_exists($thumbPath))
|
||||
unlink($thumbPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setContentFromUrl($srcUrl)
|
||||
{
|
||||
$this->orig_name = $srcUrl;
|
||||
|
||||
if (!preg_match('/^https?:\/\//', $srcUrl))
|
||||
throw new SimpleException('Invalid URL "' . $srcUrl . '"');
|
||||
|
||||
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $srcUrl, $matches))
|
||||
{
|
||||
$origName = $matches[1];
|
||||
$this->orig_name = $origName;
|
||||
$this->type = PostType::Youtube;
|
||||
$this->mime_type = null;
|
||||
$this->file_size = null;
|
||||
$this->file_hash = null;
|
||||
$this->image_width = null;
|
||||
$this->image_height = null;
|
||||
|
||||
$thumbPath = self::getThumbDefaultPath($this->name);
|
||||
if (file_exists($thumbPath))
|
||||
unlink($thumbPath);
|
||||
|
||||
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||
return;
|
||||
}
|
||||
|
||||
$srcPath = tempnam(sys_get_temp_dir(), 'upload') . '.dat';
|
||||
|
||||
//warning: low level sh*t ahead
|
||||
//download the URL $srcUrl into $srcPath
|
||||
$maxBytes = TextHelper::stripBytesUnits(ini_get('upload_max_filesize'));
|
||||
set_time_limit(0);
|
||||
$urlFP = fopen($srcUrl, 'rb');
|
||||
if (!$urlFP)
|
||||
throw new SimpleException('Cannot open URL for reading');
|
||||
$srcFP = fopen($srcPath, 'w+b');
|
||||
if (!$srcFP)
|
||||
{
|
||||
fclose($urlFP);
|
||||
throw new SimpleException('Cannot open file for writing');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (!feof($urlFP))
|
||||
{
|
||||
$buffer = fread($urlFP, 4 * 1024);
|
||||
if (fwrite($srcFP, $buffer) === false)
|
||||
throw new SimpleException('Cannot write into file');
|
||||
fflush($srcFP);
|
||||
if (ftell($srcFP) > $maxBytes)
|
||||
throw new SimpleException('File is too big (maximum allowed size: ' . TextHelper::useBytesUnits($maxBytes) . ')');
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
fclose($urlFP);
|
||||
fclose($srcFP);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->setContentFromPath($srcPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (file_exists($srcPath))
|
||||
unlink($srcPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function makeThumb($width = null, $height = null)
|
||||
{
|
||||
list ($width, $height) = self::validateThumbSize($width, $height);
|
||||
$dstPath = self::getThumbDefaultPath($this->name, $width, $height);
|
||||
$srcPath = self::getFullPath($this->name);
|
||||
|
||||
if ($this->type == PostType::Youtube)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
|
||||
$contents = file_get_contents('http://img.youtube.com/vi/' . $this->orig_name . '/mqdefault.jpg');
|
||||
file_put_contents($tmpPath, $contents);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefromjpeg($tmpPath);
|
||||
}
|
||||
else switch ($this->mime_type)
|
||||
{
|
||||
case 'image/jpeg':
|
||||
$srcImage = imagecreatefromjpeg($srcPath);
|
||||
break;
|
||||
case 'image/png':
|
||||
$srcImage = imagecreatefrompng($srcPath);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$srcImage = imagecreatefromgif($srcPath);
|
||||
break;
|
||||
case 'application/x-shockwave-flash':
|
||||
$srcImage = null;
|
||||
exec('which dump-gnash', $tmp, $exitCode);
|
||||
if ($exitCode == 0)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
||||
exec('dump-gnash --screenshot last --screenshot-file ' . $tmpPath . ' -1 -r1 --max-advances 15 ' . $srcPath);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefrompng($tmpPath);
|
||||
}
|
||||
if (!$srcImage)
|
||||
{
|
||||
exec('which swfrender', $tmp, $exitCode);
|
||||
if ($exitCode == 0)
|
||||
{
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
||||
exec('swfrender ' . $srcPath . ' -o ' . $tmpPath);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefrompng($tmpPath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($tmpPath))
|
||||
unlink($tmpPath);
|
||||
|
||||
if (!isset($srcImage))
|
||||
return false;
|
||||
|
||||
switch (self::$config->browsing->thumbStyle)
|
||||
{
|
||||
case 'outside':
|
||||
$dstImage = ThumbnailHelper::cropOutside($srcImage, $width, $height);
|
||||
break;
|
||||
case 'inside':
|
||||
$dstImage = ThumbnailHelper::cropInside($srcImage, $width, $height);
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown thumbnail crop style');
|
||||
}
|
||||
|
||||
imagejpeg($dstImage, $dstPath);
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($dstImage);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Model_Post::initModel();
|
||||
|
@ -1,31 +1,11 @@
|
||||
<?php
|
||||
class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
protected static function attachTableCount($dbQuery, $tableName, $shortName)
|
||||
{
|
||||
$dbQuery
|
||||
->addSql(', ')
|
||||
->open()
|
||||
->select('COUNT(1)')
|
||||
->from($tableName)
|
||||
->where($tableName . '.post_id = post.id')
|
||||
->close()
|
||||
->as($shortName . '_count');
|
||||
}
|
||||
private static $enableTokenLimit = true;
|
||||
|
||||
protected static function attachCommentCount($dbQuery)
|
||||
public static function enableTokenLimit($enable)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'comment', 'comment');
|
||||
}
|
||||
|
||||
protected static function attachFavCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'favoritee', 'fav');
|
||||
}
|
||||
|
||||
protected static function attachTagCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'post_tag', 'tag');
|
||||
self::$enableTokenLimit = $enable;
|
||||
}
|
||||
|
||||
protected static function filterUserSafety($dbQuery)
|
||||
@ -72,7 +52,7 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenId($dbQuery, $val)
|
||||
protected static function filterTokenId($context, $dbQuery, $val)
|
||||
{
|
||||
$ids = preg_split('/[;,]/', $val);
|
||||
$ids = array_map('intval', $ids);
|
||||
@ -81,59 +61,96 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
$dbQuery->put($id);
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMin($dbQuery, $val)
|
||||
protected static function filterTokenIdMin($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMax($dbQuery, $val)
|
||||
protected static function filterTokenIdMax($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMin($dbQuery, $val)
|
||||
protected static function filterTokenScoreMin($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMax($dbQuery, $val)
|
||||
protected static function filterTokenScoreMax($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMin($dbQuery, $val)
|
||||
protected static function filterTokenTagMin($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMax($dbQuery, $val)
|
||||
protected static function filterTokenTagMax($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMin($dbQuery, $val)
|
||||
protected static function filterTokenFavMin($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMax($dbQuery, $val)
|
||||
protected static function filterTokenFavMax($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMin($dbQuery, $val)
|
||||
protected static function filterTokenCommentMin($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMax($dbQuery, $val)
|
||||
protected static function filterTokenCommentMax($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenType($dbQuery, $val)
|
||||
protected static function filterTokenSpecial($context, $dbQuery, $val)
|
||||
{
|
||||
switch ($val)
|
||||
$context = \Chibi\Registry::getContext();
|
||||
|
||||
switch (strtolower($val))
|
||||
{
|
||||
case 'liked':
|
||||
case 'likes':
|
||||
$dbQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('postscore')
|
||||
->where('post_id = post.id')
|
||||
->and('score > 0')
|
||||
->and('user_id = ?')->put($context->user->id)
|
||||
->close();
|
||||
break;
|
||||
|
||||
case 'disliked':
|
||||
case 'dislikes':
|
||||
$dbQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('postscore')
|
||||
->where('post_id = post.id')
|
||||
->and('score < 0')
|
||||
->and('user_id = ?')->put($context->user->id)
|
||||
->close();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new SimpleException('Unknown special "' . $val . '"');
|
||||
}
|
||||
}
|
||||
|
||||
protected static function filterTokenType($context, $dbQuery, $val)
|
||||
{
|
||||
switch (strtolower($val))
|
||||
{
|
||||
case 'swf':
|
||||
$type = PostType::Flash;
|
||||
@ -166,7 +183,7 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
return [$timeMin, $timeMax];
|
||||
}
|
||||
|
||||
protected static function filterTokenDate($dbQuery, $val)
|
||||
protected static function filterTokenDate($context, $dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery
|
||||
@ -175,19 +192,19 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMin($dbQuery, $val)
|
||||
protected static function filterTokenDateMin($context, $dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date >= ?')->put($timeMin);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMax($dbQuery, $val)
|
||||
protected static function filterTokenDateMax($context, $dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date <= ?')->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenFav($dbQuery, $val)
|
||||
protected static function filterTokenFav($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->exists()
|
||||
@ -197,54 +214,101 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
->innerJoin('user')
|
||||
->on('favoritee.user_id = user.id')
|
||||
->where('post_id = post.id')
|
||||
->and('user.name = ?')->put($val)
|
||||
->and('LOWER(user.name) = LOWER(?)')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenFavs($dbQuery, $val)
|
||||
protected static function filterTokenFavs($context, $dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
return self::filterTokenFav($context, $dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenFavitee($dbQuery, $val)
|
||||
protected static function filterTokenComment($context, $dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
$dbQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('comment')
|
||||
->innerJoin('user')
|
||||
->on('commenter_id = user.id')
|
||||
->where('post_id = post.id')
|
||||
->and('LOWER(user.name) = LOWER(?)')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenFaviter($dbQuery, $val)
|
||||
protected static function filterTokenCommenter($context, $dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
return self::filterTokenComment($context, $dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenSubmit($dbQuery, $val)
|
||||
protected static function filterTokenSubmit($context, $dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->addSql('uploader_id = ')
|
||||
->open()
|
||||
->select('user.id')
|
||||
->from('user')
|
||||
->where('name = ?')->put($val)
|
||||
->where('LOWER(name) = LOWER(?)')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenUploader($dbQuery, $val)
|
||||
protected static function filterTokenUploader($context, $dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
return self::filterTokenSubmit($context, $dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUpload($dbQuery, $val)
|
||||
protected static function filterTokenUpload($context, $dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
return self::filterTokenSubmit($context, $dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUploaded($dbQuery, $val)
|
||||
protected static function filterTokenUploaded($context, $dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
return self::filterTokenSubmit($context, $dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenPrev($context, $dbQuery, $val)
|
||||
{
|
||||
self::__filterTokenPrevNext($context, $dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenNext($context, $dbQuery, $val)
|
||||
{
|
||||
$context->orderDir *= -1;
|
||||
self::__filterTokenPrevNext($context, $dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function __filterTokenPrevNext($context, $dbQuery, $val)
|
||||
{
|
||||
$op1 = $context->orderDir == 1 ? '<' : '>';
|
||||
$op2 = $context->orderDir != 1 ? '<' : '>';
|
||||
$dbQuery
|
||||
->open()
|
||||
->open()
|
||||
->addSql($context->orderColumn . ' ' . $op1 . ' ')
|
||||
->open()
|
||||
->select($context->orderColumn)
|
||||
->from('post p2')
|
||||
->where('p2.id = ?')->put(intval($val))
|
||||
->close()
|
||||
->and('id != ?')->put($val)
|
||||
->close()
|
||||
->or()
|
||||
->open()
|
||||
->addSql($context->orderColumn . ' = ')
|
||||
->open()
|
||||
->select($context->orderColumn)
|
||||
->from('post p2')
|
||||
->where('p2.id = ?')->put(intval($val))
|
||||
->close()
|
||||
->and('id ' . $op1 . ' ?')->put(intval($val))
|
||||
->close()
|
||||
->close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected static function order($dbQuery, $val)
|
||||
protected static function parseOrderToken($context, $val)
|
||||
{
|
||||
$randomReset = true;
|
||||
|
||||
@ -268,10 +332,10 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
switch ($val)
|
||||
{
|
||||
case 'id':
|
||||
$orderColumn = 'post.id';
|
||||
$orderColumn = 'id';
|
||||
break;
|
||||
case 'date':
|
||||
$orderColumn = 'post.upload_date';
|
||||
$orderColumn = 'upload_date';
|
||||
break;
|
||||
case 'comment':
|
||||
case 'comments':
|
||||
@ -284,7 +348,6 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
$orderColumn = 'fav_count';
|
||||
break;
|
||||
case 'score':
|
||||
$orderDir *= -1;
|
||||
$orderColumn = 'score';
|
||||
break;
|
||||
case 'tag':
|
||||
@ -311,36 +374,16 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
if ($randomReset and isset($_SESSION['browsing-seed']))
|
||||
unset($_SESSION['browsing-seed']);
|
||||
|
||||
$dbQuery->orderBy($orderColumn);
|
||||
if ($orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
$context->orderColumn = $orderColumn;
|
||||
$context->orderDir = $orderDir;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function build($dbQuery, $query)
|
||||
protected static function iterateTokens($tokens, $callback)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$unparsedTokens = [];
|
||||
|
||||
self::attachCommentCount($dbQuery);
|
||||
self::attachFavCount($dbQuery);
|
||||
self::attachTagCount($dbQuery);
|
||||
|
||||
$dbQuery->from('post');
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserSafety($dbQuery);
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserHidden($dbQuery);
|
||||
|
||||
/* query tokens */
|
||||
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
|
||||
if (count($tokens) > $config->browsing->maxSearchTokens)
|
||||
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
||||
|
||||
$orderToken = 'id';
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if ($token{0} == '-')
|
||||
@ -356,39 +399,96 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
$pos = strpos($token, ':');
|
||||
if ($pos === false)
|
||||
{
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::filterTag($dbQuery, $token);
|
||||
continue;
|
||||
$key = null;
|
||||
$val = $token;
|
||||
}
|
||||
|
||||
$key = substr($token, 0, $pos);
|
||||
$val = substr($token, $pos + 1);
|
||||
|
||||
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
||||
if (method_exists(__CLASS__, $methodName))
|
||||
{
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::$methodName($dbQuery, $val);
|
||||
}
|
||||
|
||||
elseif ($key == 'order')
|
||||
{
|
||||
if ($neg)
|
||||
$orderToken = $val;
|
||||
else
|
||||
$orderToken = '-' . $val;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw new SimpleException('Unknown key "' . $key . '"');
|
||||
$key = strtolower(substr($token, 0, $pos));
|
||||
$val = substr($token, $pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
self::order($dbQuery, $orderToken);
|
||||
$parsed = $callback($neg, $key, $val);
|
||||
|
||||
if (!$parsed)
|
||||
$unparsedTokens []= $token;
|
||||
}
|
||||
return $unparsedTokens;
|
||||
}
|
||||
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
$dbQuery->from('post');
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserSafety($dbQuery);
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserHidden($dbQuery);
|
||||
|
||||
/* query tokens */
|
||||
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
|
||||
if (self::$enableTokenLimit and count($tokens) > $config->browsing->maxSearchTokens)
|
||||
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
||||
|
||||
$context = new StdClass;
|
||||
$context->orderColumn = 'id';
|
||||
$context->orderDir = 1;
|
||||
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($context, $dbQuery, &$orderToken)
|
||||
{
|
||||
if ($key != 'order')
|
||||
return false;
|
||||
|
||||
if ($neg)
|
||||
$orderToken = '-' . $val;
|
||||
else
|
||||
$orderToken = $val;
|
||||
self::parseOrderToken($context, $orderToken);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($context, $dbQuery)
|
||||
{
|
||||
if ($key !== null)
|
||||
return false;
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::filterTag($dbQuery, $val);
|
||||
return true;
|
||||
});
|
||||
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($context, $dbQuery)
|
||||
{
|
||||
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
||||
if (!method_exists(__CLASS__, $methodName))
|
||||
return false;
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::$methodName($context, $dbQuery, $val);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!empty($tokens))
|
||||
throw new SimpleException('Unknown search token "' . array_shift($tokens) . '"');
|
||||
|
||||
$dbQuery->orderBy($context->orderColumn);
|
||||
if ($context->orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
|
||||
$dbQuery->addSql(', id ');
|
||||
if ($context->orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,9 @@
|
||||
class Model_Property extends RedBean_SimpleModel
|
||||
{
|
||||
const FeaturedPostId = 0;
|
||||
const FeaturedPostUserId = 1;
|
||||
const FeaturedPostUserName = 1;
|
||||
const FeaturedPostDate = 2;
|
||||
const DbVersion = 'db-version';
|
||||
|
||||
static $allProperties = null;
|
||||
|
||||
|
@ -1,6 +1,18 @@
|
||||
<?php
|
||||
class Model_Tag extends AbstractModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'tag';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Tag_Querybuilder';
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$tag = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$key]);
|
||||
@ -13,6 +25,8 @@ class Model_Tag extends AbstractModel
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function removeUnused()
|
||||
{
|
||||
$dbQuery = R::$f
|
||||
@ -68,14 +82,6 @@ class Model_Tag extends AbstractModel
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public function getPostCount()
|
||||
{
|
||||
if ($this->bean->getMeta('post_count'))
|
||||
return $this->bean->getMeta('post_count');
|
||||
return $this->bean->countShared('post');
|
||||
}
|
||||
|
||||
|
||||
public static function validateTags($tags)
|
||||
{
|
||||
$tags = trim($tags);
|
||||
@ -92,13 +98,10 @@ class Model_Tag extends AbstractModel
|
||||
return $tags;
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
public function getPostCount()
|
||||
{
|
||||
return 'tag';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Tag_Querybuilder';
|
||||
if ($this->bean->getMeta('post_count'))
|
||||
return $this->bean->getMeta('post_count');
|
||||
return $this->bean->countShared('post');
|
||||
}
|
||||
}
|
||||
|
@ -17,21 +17,71 @@ class model_Tag_QueryBuilder implements AbstractQueryBuilder
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
|
||||
$orderToken = null;
|
||||
|
||||
if ($query !== null)
|
||||
{
|
||||
$limitQuery = true;
|
||||
if (strlen($query) >= 3)
|
||||
$query = '%' . $query;
|
||||
$query .= '%';
|
||||
$dbQuery
|
||||
->and('LOWER(tag.name)')
|
||||
->like('LOWER(?)')
|
||||
->put($query);
|
||||
$tokens = preg_split('/\s+/', $query);
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if (strpos($token, ':') !== false)
|
||||
{
|
||||
list ($key, $value) = explode(':', $token);
|
||||
|
||||
if ($key == 'order')
|
||||
$orderToken = $value;
|
||||
else
|
||||
throw new SimpleException('Unknown key: ' . $key);
|
||||
}
|
||||
else
|
||||
{
|
||||
$limitQuery = true;
|
||||
if (strlen($token) >= 3)
|
||||
$token = '%' . $token;
|
||||
$token .= '%';
|
||||
$dbQuery
|
||||
->and('LOWER(tag.name)')
|
||||
->like('LOWER(?)')
|
||||
->put($token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dbQuery->groupBy('tag.id');
|
||||
if ($orderToken)
|
||||
self::order($dbQuery,$orderToken);
|
||||
|
||||
|
||||
if ($limitQuery)
|
||||
$dbQuery->limit(15);
|
||||
}
|
||||
|
||||
private static function order($dbQuery, $value)
|
||||
{
|
||||
if (strpos($value, ',') !== false)
|
||||
{
|
||||
list ($orderColumn, $orderDir) = explode(',', $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$orderColumn = $value;
|
||||
$orderDir = 'asc';
|
||||
}
|
||||
|
||||
switch ($orderColumn)
|
||||
{
|
||||
case 'popularity':
|
||||
$dbQuery->orderBy('post_count');
|
||||
break;
|
||||
|
||||
case 'alpha':
|
||||
$dbQuery->orderBy('name');
|
||||
break;
|
||||
}
|
||||
|
||||
if ($orderDir == 'asc')
|
||||
$dbQuery->asc();
|
||||
else
|
||||
$dbQuery->desc();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,27 @@
|
||||
<?php
|
||||
class Model_User extends AbstractModel
|
||||
{
|
||||
const SETTING_SAFETY = 1;
|
||||
const SETTING_ENDLESS_SCROLLING = 2;
|
||||
const SETTING_POST_TAG_TITLES = 3;
|
||||
|
||||
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_User_QueryBuilder';
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$user = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
||||
$user = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$key]);
|
||||
if ($user)
|
||||
return $user;
|
||||
|
||||
@ -17,87 +35,49 @@ class Model_User extends AbstractModel
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAvatarUrl($size = 32)
|
||||
public static function create()
|
||||
{
|
||||
$subject = !empty($this->email_confirmed)
|
||||
? $this->email_confirmed
|
||||
: $this->pass_salt . $this->name;
|
||||
$hash = md5(strtolower(trim($subject)));
|
||||
$url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
|
||||
return $url;
|
||||
$user = R::dispense(self::getTableName());
|
||||
$user->pass_salt = md5(mt_rand() . uniqid());
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function getSetting($key)
|
||||
public static function remove($user)
|
||||
{
|
||||
$settings = json_decode($this->settings, true);
|
||||
return isset($settings[$key])
|
||||
? $settings[$key]
|
||||
: null;
|
||||
}
|
||||
|
||||
public function setSetting($key, $value)
|
||||
{
|
||||
$settings = json_decode($this->settings, true);
|
||||
$settings[$key] = $value;
|
||||
$settings = json_encode($settings);
|
||||
if (strlen($settings) > 200)
|
||||
throw new SimpleException('Too much data');
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
|
||||
const SETTING_SAFETY = 1;
|
||||
const SETTING_ENDLESS_SCROLLING = 2;
|
||||
|
||||
public function hasEnabledSafety($safety)
|
||||
{
|
||||
$all = $this->getSetting(self::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
return $safety == PostSafety::Safe;
|
||||
return $all & PostSafety::toFlag($safety);
|
||||
}
|
||||
|
||||
public function enableSafety($safety, $enabled)
|
||||
{
|
||||
$all = $this->getSetting(self::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
$all = PostSafety::toFlag(PostSafety::Safe);
|
||||
|
||||
$new = $all;
|
||||
if (!$enabled)
|
||||
//remove stuff from auxiliary tables
|
||||
R::trashAll(R::find('postscore', 'user_id = ?', [$user->id]));
|
||||
foreach ($user->alias('commenter')->ownComment as $comment)
|
||||
{
|
||||
$new &= ~PostSafety::toFlag($safety);
|
||||
if (!$new)
|
||||
$new = PostSafety::toFlag(PostSafety::Safe);
|
||||
$comment->commenter = null;
|
||||
R::store($comment);
|
||||
}
|
||||
else
|
||||
foreach ($user->alias('uploader')->ownPost as $post)
|
||||
{
|
||||
$new |= PostSafety::toFlag($safety);
|
||||
$post->uploader = null;
|
||||
R::store($post);
|
||||
}
|
||||
|
||||
$this->setSetting(self::SETTING_SAFETY, $new);
|
||||
$user->ownFavoritee = [];
|
||||
R::store($user);
|
||||
R::trash($user);
|
||||
}
|
||||
|
||||
public function hasEnabledEndlessScrolling()
|
||||
public static function save($user)
|
||||
{
|
||||
$ret = $this->getSetting(self::SETTING_ENDLESS_SCROLLING);
|
||||
if ($ret === null)
|
||||
$ret = \Chibi\Registry::getConfig()->browsing->endlessScrollingDefault;
|
||||
return $ret;
|
||||
R::store($user);
|
||||
}
|
||||
|
||||
public function enableEndlessScrolling($enabled)
|
||||
|
||||
|
||||
public static function getAnonymousName()
|
||||
{
|
||||
$this->setSetting(self::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0);
|
||||
return '[Anonymous user]';
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateUserName($userName)
|
||||
{
|
||||
$userName = trim($userName);
|
||||
|
||||
$dbUser = R::findOne(self::getTableName(), 'name = ?', [$userName]);
|
||||
$dbUser = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$userName]);
|
||||
if ($dbUser !== null)
|
||||
{
|
||||
if (!$dbUser->email_confirmed and \Chibi\Registry::getConfig()->registration->needEmailForRegistering)
|
||||
@ -168,13 +148,139 @@ class Model_User extends AbstractModel
|
||||
return sha1($salt1 . $salt2 . $pass);
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
|
||||
|
||||
public function getAvatarUrl($size = 32)
|
||||
{
|
||||
return 'user';
|
||||
$subject = !empty($this->email_confirmed)
|
||||
? $this->email_confirmed
|
||||
: $this->pass_salt . $this->name;
|
||||
$hash = md5(strtolower(trim($subject)));
|
||||
$url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
public function getSetting($key)
|
||||
{
|
||||
return 'Model_User_QueryBuilder';
|
||||
$settings = json_decode($this->settings, true);
|
||||
return isset($settings[$key])
|
||||
? $settings[$key]
|
||||
: null;
|
||||
}
|
||||
|
||||
public function setSetting($key, $value)
|
||||
{
|
||||
$settings = json_decode($this->settings, true);
|
||||
$settings[$key] = $value;
|
||||
$settings = json_encode($settings);
|
||||
if (strlen($settings) > 200)
|
||||
throw new SimpleException('Too much data');
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function hasEnabledSafety($safety)
|
||||
{
|
||||
$all = $this->getSetting(self::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
return $safety == PostSafety::Safe;
|
||||
return $all & PostSafety::toFlag($safety);
|
||||
}
|
||||
|
||||
public function enableSafety($safety, $enabled)
|
||||
{
|
||||
$all = $this->getSetting(self::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
$all = PostSafety::toFlag(PostSafety::Safe);
|
||||
|
||||
$new = $all;
|
||||
if (!$enabled)
|
||||
{
|
||||
$new &= ~PostSafety::toFlag($safety);
|
||||
if (!$new)
|
||||
$new = PostSafety::toFlag(PostSafety::Safe);
|
||||
}
|
||||
else
|
||||
{
|
||||
$new |= PostSafety::toFlag($safety);
|
||||
}
|
||||
|
||||
$this->setSetting(self::SETTING_SAFETY, $new);
|
||||
}
|
||||
|
||||
public function hasEnabledPostTagTitles()
|
||||
{
|
||||
$ret = $this->getSetting(self::SETTING_POST_TAG_TITLES);
|
||||
if ($ret === null)
|
||||
$ret = \Chibi\Registry::getConfig()->browsing->showPostTagTitlesDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enablePostTagTitles($enabled)
|
||||
{
|
||||
$this->setSetting(self::SETTING_POST_TAG_TITLES, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
public function hasEnabledEndlessScrolling()
|
||||
{
|
||||
$ret = $this->getSetting(self::SETTING_ENDLESS_SCROLLING);
|
||||
if ($ret === null)
|
||||
$ret = \Chibi\Registry::getConfig()->browsing->endlessScrollingDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enableEndlessScrolling($enabled)
|
||||
{
|
||||
$this->setSetting(self::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
public function hasFavorited($post)
|
||||
{
|
||||
foreach ($this->bean->ownFavoritee as $fav)
|
||||
if ($fav->post->id == $post->id)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getScore($post)
|
||||
{
|
||||
$s = R::findOne('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->id]);
|
||||
if ($s)
|
||||
return intval($s->score);
|
||||
return null;
|
||||
}
|
||||
|
||||
public function addToFavorites($post)
|
||||
{
|
||||
R::preload($this->bean, ['favoritee' => 'post']);
|
||||
foreach ($this->bean->ownFavoritee as $fav)
|
||||
if ($fav->post_id == $post->id)
|
||||
throw new SimpleException('Already in favorites');
|
||||
|
||||
$this->bean->link('favoritee')->post = $post;
|
||||
}
|
||||
|
||||
public function remFromFavorites($post)
|
||||
{
|
||||
$finalKey = null;
|
||||
foreach ($this->bean->ownFavoritee as $key => $fav)
|
||||
if ($fav->post_id == $post->id)
|
||||
$finalKey = $key;
|
||||
|
||||
if ($finalKey === null)
|
||||
throw new SimpleException('Not in favorites');
|
||||
|
||||
unset($this->bean->ownFavoritee[$finalKey]);
|
||||
}
|
||||
|
||||
public function score($post, $score)
|
||||
{
|
||||
R::trashAll(R::find('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->id]));
|
||||
$score = intval($score);
|
||||
if ($score != 0)
|
||||
{
|
||||
$p = $this->bean->link('postscore');
|
||||
$p->post = $post;
|
||||
$p->score = $score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ class Privilege extends Enum
|
||||
const EditPostThumb = 8;
|
||||
const EditPostSource = 26;
|
||||
const EditPostRelations = 30;
|
||||
const EditPostFile = 36;
|
||||
const HidePost = 9;
|
||||
const DeletePost = 10;
|
||||
const FeaturePost = 25;
|
||||
|
3
src/Upgrades/Upgrade7.sql
Normal file
3
src/Upgrades/Upgrade7.sql
Normal file
@ -0,0 +1,3 @@
|
||||
CREATE UNIQUE INDEX idx_uq_postscore_post_id_user_id ON postscore(post_id, user_id);
|
||||
CREATE UNIQUE INDEX idx_uq_crossref_post_id_post2_id ON crossref(post_id, post2_id);
|
||||
|
37
src/Upgrades/Upgrade8.sql
Normal file
37
src/Upgrades/Upgrade8.sql
Normal file
@ -0,0 +1,37 @@
|
||||
ALTER TABLE post ADD COLUMN tag_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE post ADD COLUMN fav_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE post ADD COLUMN comment_count INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE POST SET tag_count = (SELECT COUNT(*) FROM post_tag WHERE post_id = post.id);
|
||||
UPDATE post SET fav_count = (SELECT COUNT(*) FROM favoritee WHERE post_id = post.id);
|
||||
UPDATE post SET comment_count = (SELECT COUNT(*) FROM comment WHERE post_id = post.id);
|
||||
|
||||
CREATE TRIGGER post_tag_insert AFTER INSERT ON post_tag FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET tag_count = tag_count + 1 WHERE post.id = new.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER post_tag_delete BEFORE DELETE ON post_tag FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET tag_count = tag_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER favoritee_insert AFTER INSERT ON favoritee FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET fav_count = fav_count + 1 WHERE post.id = new.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER favoritee_delete BEFORE DELETE ON favoritee FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET fav_count = fav_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER comment_insert AFTER INSERT ON comment FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET comment_count = comment_count + 1 WHERE post.id = new.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER comment_delete BEFORE DELETE ON comment FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET comment_count = comment_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
@ -2,10 +2,10 @@
|
||||
<div class="avatar">
|
||||
<?php if ($this->context->comment->commenter): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->comment->commenter->name]) ?>">
|
||||
<img src="<?php echo htmlspecialchars($this->context->comment->commenter->getAvatarUrl(40)) ?>" alt="<?php echo $this->context->comment->commenter->name ?: '[unknown user]' ?>"/>
|
||||
<img src="<?php echo htmlspecialchars($this->context->comment->commenter->getAvatarUrl(40)) ?>" alt="<?php echo $this->context->comment->commenter->name ?>"/>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[unknown user]">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="<?php echo Model_User::getAnonymousName() ?>">
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<?php echo $this->context->comment->commenter->name ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
[unknown user]
|
||||
<?php echo Model_User::getAnonymousName() ?>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
|
@ -1,69 +1,26 @@
|
||||
<h1>Browsing</h1>
|
||||
<?php
|
||||
$tabs = $this->config->help->subTitles;
|
||||
$firstTab = !empty($tabs) ? array_keys($tabs)[0] : null;
|
||||
?>
|
||||
|
||||
<p>Clicking the <a href="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">Browse</a> button at the top will take you to the list of recent posts. Use the search box in the top right corner to find posts you want to see.</p>
|
||||
<?php if (count($tabs) > 1): ?>
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<?php foreach ($tabs as $tab => $text): ?>
|
||||
<?php if ($tab == $this->context->tab): ?>
|
||||
<li class="selected <?php echo $tab ?>">
|
||||
<?php else: ?>
|
||||
<li class="<?php echo $tab ?>">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('index', 'help', $tab == $firstTab ? [] : ['tab' => $tab]) ?>">
|
||||
<?php echo $text ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<p>If you’re not a registered user, you will only see public (Safe) posts. Logging in to your account will enable you to filter content by its rating: Safe, Sketchy, and NSFW.</p>
|
||||
|
||||
<p>You can use your keyboard to navigate around the site. There are a few shortcuts:</p>
|
||||
|
||||
<ul>
|
||||
<li>focus search field: <code>[Q]</code></li>
|
||||
<li>scroll up/down: <code>[W]</code><span class="comma">, </span><code>[S]</code></li>
|
||||
<li>go to newer/older post or page: <code>[A]</code><span class="comma">, </span><code>[D]</code></li>
|
||||
<li>edit post: <code>[E]</code></li>
|
||||
<li>focus first post in post list: <code>[P]</code></li>
|
||||
</ul>
|
||||
|
||||
<h1>Search syntax</h1>
|
||||
|
||||
<ul>
|
||||
<li>contatining tag "Haruhi": <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'Haruhi']) ?>"><code>Haruhi</code></a></li>
|
||||
<li><strong>not</strong> contatining tag "Kyon": <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => '-Kyon']) ?>"><code>-Kyon</code></a></li>
|
||||
<li>uploaded by David: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'submit:David']) ?>"><code>submit:David</code></a> (note no spaces)</li>
|
||||
<li>favorited by David: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'fav:David']) ?>"><code>fav:David</code></a></li>
|
||||
<li>favorited by at least four users: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'favmin:4']) ?>"><code>favmin:4</code></a></li>
|
||||
<li>commented by at least three users: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'commentmin:3']) ?>"><code>commentmin:3</code></a></li>
|
||||
<li>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>
|
||||
<li>up to the specified date: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'datemax:2004-07']) ?>"><code>datemax:2004-07</code></a></li>
|
||||
<li>having specific ID: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'id:1,2,3,8']) ?>"><code>id:1,2,3,8</code></a></li>
|
||||
<li>having ID no less than specified value: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'idmin:28']) ?>"><code>idmin:28</code></a></li>
|
||||
<li>by content type: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:img']) ?>"><code>type:img</code></a><span class="comma">, </span><a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:swf']) ?>"><code>type:swf</code></a><span class="comma">, </span><a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:yt'] )?>"><code>type:yt</code></a> (images, flash files and Youtube videos, respectively)</li>
|
||||
</ul>
|
||||
|
||||
<p>You can combine tags and negate any of them for interesting results. <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'sea -favmin:8 type:swf submit:Pirate']) ?>"><code>sea -favmin:8 type:swf submit:Pirate</code></a> will show you <strong>flash files</strong> tagged as <strong>sea</strong>, that were <strong>liked by seven people</strong> at most, uploaded by user <strong>Pirate</strong>.</p>
|
||||
|
||||
<p>All of the above can be sorted using additional sorting tags:</p>
|
||||
|
||||
<ul>
|
||||
<li>as random as it can get: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'order:random']) ?>"><code>order:random</code></a></li>
|
||||
<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>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>
|
||||
|
||||
<h1>Registration</h1>
|
||||
|
||||
<p>The e-mail you enter during account creation is only used to retrieve your <a href="http://gravatar.com">Gravatar</a> and activate your account. Only you can see it (well, except the database staff… we won’t spam your mailbox anyway).</p>
|
||||
|
||||
<p>Oh, and you can delete your account at any time. Posts you uploaded will stay, unless some angry admin removes them.</p>
|
||||
|
||||
<h1>Comments</h1>
|
||||
|
||||
<p>Registered users can post comments. Comments support <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a>, extended by some handy tags:</p>
|
||||
|
||||
<ul>
|
||||
<li>permalink to post number 426: <a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => 426]) ?>"><code>@426</code></a></li>
|
||||
<li>link to tag "Dragon_Ball": <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'Dragon_Ball']) ?>"><code>#Dragon_Ball</code></a></li>
|
||||
<li>mark text as spoiler and hide it: <code class="spoiler">[spoiler]There is no spoon.[/spoiler]</code></li>
|
||||
</ul>
|
||||
|
||||
<h1>Uploads</h1>
|
||||
|
||||
<p>After registering and activating your account, you gain the power to upload files to the service, for everyone else to see. Owners of the site are not responsible for content uploaded by users. You are not allowed to post any form of <a href="http://www.urbandictionary.com/define.php?term=cp">cp</a>. If you possess it, we ask you to leave immediately and never come back.</p>
|
||||
<?php echo TextHelper::parseMarkdown(file_get_contents($this->context->path)) ?>
|
||||
|
@ -7,13 +7,13 @@
|
||||
|
||||
<?php if (!empty($this->context->featuredPost)): ?>
|
||||
<div class="body">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->featuredPost->id]) ?>">
|
||||
<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>
|
||||
<?php $this->context->imageLink = \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->featuredPost->id]) ?>
|
||||
<?php $this->context->transport->post = $this->context->featuredPost ?>
|
||||
<?php echo $this->renderFile('post-file-render') ?>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span class="left">
|
||||
<div class="left">
|
||||
Tags:
|
||||
<ul class="tags">
|
||||
<?php foreach ($this->context->featuredPost->sharedTag as $tag): ?>
|
||||
@ -24,9 +24,9 @@
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="right">
|
||||
<div 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>, 
|
||||
@ -39,7 +39,7 @@
|
||||
<?php else: ?>
|
||||
<?php printf('%d days ago', $x) ?>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
@ -2,18 +2,25 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<?php if (isset($this->context->subTitle)): ?>
|
||||
<title><?php printf('%s – %s', $this->context->title, $this->context->subTitle) ?></title>
|
||||
<?php else: ?>
|
||||
<title><?php echo $this->context->title ?></title>
|
||||
<?php endif ?>
|
||||
<?php
|
||||
$title = isset($this->context->subTitle)
|
||||
? sprintf('%s – %s', $this->context->title, $this->context->subTitle)
|
||||
: $this->context->title
|
||||
?>
|
||||
<title><?php echo $title ?></title>
|
||||
<?php foreach (array_unique($this->context->stylesheets) as $name): ?>
|
||||
<link rel="stylesheet" type="text/css" href="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/css/' . $name) ?>"/>
|
||||
<?php endforeach ?>
|
||||
<?php foreach (array_unique($this->context->scripts) as $name): ?>
|
||||
<script type="text/javascript" src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/js/' . $name) ?>"></script>
|
||||
<?php endforeach ?>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"/>
|
||||
|
||||
<meta property="og:title" content="<?php echo $title ?>"/>
|
||||
<meta property="og:url" content="<?php echo \Chibi\UrlHelper::currentUrl() ?>"/>
|
||||
<?php if (!empty($this->context->pageThumb)): ?>
|
||||
<meta property="og:image" content="<?php echo $this->context->pageThumb ?>"/>
|
||||
<?php endif ?>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -50,7 +57,8 @@
|
||||
$nav []= ['Log out', \Chibi\UrlHelper::route('auth', 'logout')];
|
||||
}
|
||||
|
||||
$nav []= ['Help', \Chibi\UrlHelper::route('index', 'help')];
|
||||
if (!empty($this->config->help->title))
|
||||
$nav []= [$this->config->help->title, \Chibi\UrlHelper::route('index', 'help')];
|
||||
|
||||
foreach ($nav as $navItem)
|
||||
{
|
||||
@ -79,9 +87,15 @@
|
||||
|
||||
<li class="search">
|
||||
<form name="search" action="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>" method="get">
|
||||
<input type="search" name="query" placeholder="Search…" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/>
|
||||
<input class="autocomplete" type="search" name="query" placeholder="Search…" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<?php if (isset($this->context->transport->lastSearchQuery)): ?>
|
||||
<script type="text/javascript">
|
||||
var lastSearchQuery = <?php echo json_encode($this->context->transport->lastSearchQuery) ?>;
|
||||
</script>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
@ -103,9 +117,16 @@
|
||||
<?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>
|
||||
<span><a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Logs</a></span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function()
|
||||
{
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,60 +1,77 @@
|
||||
<?php
|
||||
$pagesVisible = [];
|
||||
$pagesVisible []= 1;
|
||||
$pagesVisible []= $this->context->transport->paginator->pageCount;
|
||||
$delta = 3;
|
||||
$pagesVisible = array_merge($pagesVisible, range($this->context->transport->paginator->page - $delta, $this->context->transport->paginator->page + $delta));
|
||||
$pagesVisible = array_filter($pagesVisible, function($x) { return $x >= 1 and $x <= $this->context->transport->paginator->pageCount; });
|
||||
$pagesVisible = array_unique($pagesVisible);
|
||||
sort($pagesVisible, SORT_NUMERIC);
|
||||
$page = $this->context->transport->paginator->page;
|
||||
$pageCount = $this->context->transport->paginator->pageCount;
|
||||
|
||||
if (!function_exists('pageUrl'))
|
||||
$delta = 3;
|
||||
$pagesVisible = [1, $pageCount];
|
||||
$pagesVisible = array_merge($pagesVisible, range($page - $delta, $page + $delta));
|
||||
$pagesVisible = array_filter($pagesVisible, function($x) use ($pageCount) { return $x >= 1 and $x <= $pageCount; });
|
||||
$pagesVisible = array_unique($pagesVisible);
|
||||
sort($pagesVisible, SORT_NUMERIC);
|
||||
|
||||
$finalPages = [$pagesVisible[0]];
|
||||
for ($i = 1; $i < count($pagesVisible); $i ++)
|
||||
{
|
||||
$prevPage = $pagesVisible[$i - 1];
|
||||
$subPage = $pagesVisible[$i];
|
||||
if ($subPage - $prevPage == 2)
|
||||
$finalPages []= $subPage - 1;
|
||||
elseif ($subPage - $prevPage > 2)
|
||||
$finalPages []= null;
|
||||
$finalPages []= $subPage;
|
||||
}
|
||||
$pagesVisible = $finalPages;
|
||||
|
||||
if (!function_exists('pageUrl'))
|
||||
{
|
||||
function pageUrl($page)
|
||||
{
|
||||
function pageUrl($page)
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$controller = $context->route->simpleControllerName;
|
||||
$action = $context->route->simpleActionName;
|
||||
$page = max(1, $page);
|
||||
$page = min($context->transport->paginator->pageCount, $page);
|
||||
$params = $context->route->arguments;
|
||||
$params['page'] = $page;
|
||||
return \Chibi\UrlHelper::route($controller, $action, $params);
|
||||
}
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$controller = $context->route->simpleControllerName;
|
||||
$action = $context->route->simpleActionName;
|
||||
$page = max(1, min($context->transport->paginator->pageCount, $page));
|
||||
$params = $context->route->arguments;
|
||||
$params['page'] = $page;
|
||||
return \Chibi\UrlHelper::route($controller, $action, $params);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (!empty($pagesVisible)): ?>
|
||||
<nav class="paginator-wrapper">
|
||||
<ul class="paginator">
|
||||
<?php if ($this->context->transport->paginator->page > 1): ?>
|
||||
<?php if ($page > 1): ?>
|
||||
<li class="prev">
|
||||
<?php else: ?>
|
||||
<li class="prev disabled">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo pageUrl($this->context->transport->paginator->page - 1) ?>">
|
||||
<a href="<?php echo pageUrl($page - 1) ?>">
|
||||
«
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<?php foreach ($pagesVisible as $page): ?>
|
||||
<?php if ($page == $this->context->transport->paginator->page): ?>
|
||||
<li class="active">
|
||||
<?php foreach ($pagesVisible as $subPage): ?>
|
||||
<?php if ($subPage === null): ?>
|
||||
<li>…</li>
|
||||
<?php else: ?>
|
||||
<li>
|
||||
<?php if ($subPage == $page): ?>
|
||||
<li class="active">
|
||||
<?php else: ?>
|
||||
<li>
|
||||
<?php endif ?>
|
||||
<a href="<?php echo pageUrl($subPage) ?>">
|
||||
<?php echo $subPage ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<a href="<?php echo pageUrl($page) ?>">
|
||||
<?php echo $page ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php if ($this->context->transport->paginator->page < $this->context->transport->paginator->pageCount): ?>
|
||||
<?php if ($page < $pageCount): ?>
|
||||
<li class="next">
|
||||
<?php else: ?>
|
||||
<li class="next disabled">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo pageUrl($this->context->transport->paginator->page + 1) ?>">
|
||||
<a href="<?php echo pageUrl($page + 1) ?>">
|
||||
»
|
||||
</a>
|
||||
</li>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<label class="left" for="tags">Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags…" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->sharedTag)) ?>"/></div>
|
||||
</div>
|
||||
<input type="hidden" name="tags-token" id="tags-token" value="<?php echo $this->context->transport->tagsToken ?>"/>
|
||||
<input type="hidden" name="edit-token" id="edit-token" value="<?php echo $this->context->transport->editToken ?>"/>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
@ -34,10 +34,27 @@
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="url">
|
||||
<label class="left" for="url">File:</label>
|
||||
<div class="input-wrapper"><input type="text" name="url" id="url" placeholder="Some url…"/></div>
|
||||
</div>
|
||||
|
||||
<div class="file">
|
||||
<label class="left" for="file"></label>
|
||||
<div class="input-wrapper"><input type="file" name="file" id="file"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="thumb">Thumb:</label>
|
||||
<div class="input-wrapper"><input type="file" name="thumb" id="thumb"/></div>
|
||||
<div class="input-wrapper">
|
||||
<input type="file" name="thumb" id="thumb"/>
|
||||
<?php if ($this->context->transport->post->hasCustomThumb()): ?>
|
||||
<small>(Currently using custom thumb)</small>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
|
23
src/Views/post-file-render.phtml
Normal file
23
src/Views/post-file-render.phtml
Normal file
@ -0,0 +1,23 @@
|
||||
<?php $post = $this->context->transport->post ?>
|
||||
|
||||
<?php if ($post->type == PostType::Image): ?>
|
||||
|
||||
<?php if (!empty($this->context->imageLink)): ?>
|
||||
<a href="<?php echo $this->context->imageLink ?>">
|
||||
<?php endif ?>
|
||||
|
||||
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $post->name]) ?>" alt="<?php echo $post->name ?>"/>
|
||||
|
||||
<?php if (!empty($this->context->imageLink)): ?>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
|
||||
<?php elseif ($post->type == PostType::Flash): ?>
|
||||
|
||||
<iframe width="<?php echo $post->image_width ?>" height="<?php echo $post->image_height ?>" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $post->name]) ?>"> </iframe>
|
||||
|
||||
<?php elseif ($post->type == PostType::Youtube): ?>
|
||||
|
||||
<iframe width="800" height="600" src="//www.youtube.com/embed/<?php echo $post->orig_name ?>" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
<?php endif ?>
|
@ -1,18 +1,24 @@
|
||||
<?php $classNames = ['post', 'post-type-' . TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post->type))] ?>
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag'): ?>
|
||||
<?php $masstag = (isset($this->context->source) and $this->context->source == 'mass-tag' and !empty($this->context->additionalInfo)) ?>
|
||||
<?php if ($masstag): ?>
|
||||
<?php $classNames []= 'taggable' ?>
|
||||
<?php if (in_array($this->context->additionalInfo, array_map(function($x) { return $x->name; }, $this->context->post->sharedTag))): ?>
|
||||
<?php if ($this->context->post->isTaggedWith($this->context->additionalInfo)): ?>
|
||||
<?php $classNames []= 'tagged' ?>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="<?php echo implode(' ', $classNames) ?>">
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag'): ?>
|
||||
<a class="toggle-tag" href="<?php echo \Chibi\UrlHelper::route('post', 'toggle-tag', ['id' => $this->context->post->id, 'tag' => $this->context->additionalInfo]) ?>" data-text-tagged="Tagged" data-text-untagged="Untagged">
|
||||
<?php if ($masstag): ?>
|
||||
<a class="toggle-tag" href="<?php echo \Chibi\UrlHelper::route('post', 'toggle-tag', ['id' => $this->context->post->id, 'tag' => $this->context->additionalInfo, 'enable' => '_enable_']) ?>" data-text-tagged="Tagged" data-text-untagged="Untagged">
|
||||
<?php echo in_array('tagged', $classNames) ? 'Tagged' : 'Untagged' ?>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
<a class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
|
||||
<?php if ($this->context->user->hasEnabledPostTagTitles()): ?>
|
||||
<a title="<?php echo join(', ', array_map(['TextHelper', 'reprTag'], $this->context->post->sharedTag)) ?>" class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
<?php else: ?>
|
||||
<a class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
<?php endif ?>
|
||||
<img class="thumb" src="<?php echo \Chibi\UrlHelper::route('post', 'thumb', ['name' => $this->context->post->name]) ?>" alt="@<?php echo $this->context->post->id ?>"/>
|
||||
<?php
|
||||
$x =
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div id="sidebar">
|
||||
<nav>
|
||||
<nav id="around">
|
||||
<div class="left">
|
||||
<?php if ($this->context->transport->nextPostId): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->transport->nextPostId]) ?>">
|
||||
@ -23,6 +23,13 @@
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<?php if (!empty($this->context->transport->lastSearchQuery)): ?>
|
||||
<div class="text">
|
||||
Current search:<br/>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $this->context->transport->lastSearchQuery]) ?>"><?php echo $this->context->transport->lastSearchQuery ?></a>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</nav>
|
||||
|
||||
<div class="unit tags">
|
||||
@ -56,9 +63,9 @@
|
||||
</a>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="value" title="[unknown user]">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[unknown user]"/>
|
||||
[unknown user]
|
||||
<span class="value" title="<?php echo Model_User::getAnonymousName() ?>">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="<?php echo Model_User::getAnonymousName() ?>"/>
|
||||
<?php echo Model_User::getAnonymousName() ?>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
@ -77,12 +84,12 @@
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ScorePost)): ?>
|
||||
[
|
||||
<?php $scoreLink = \Chibi\UrlHelper::route('post', 'score', ['id' => $this->context->transport->post->id, 'score' => '{score}']) ?>
|
||||
<?php $scoreLink = function($score) { return \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]) ?>">
|
||||
<a class="simple-action selected" href="<?php echo $scoreLink(0) ?>">
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo TextHelper::replaceTokens($scoreLink, ['score' => 1]) ?>">
|
||||
<a class="simple-action" href="<?php echo $scoreLink(1) ?>">
|
||||
<?php endif ?>
|
||||
vote up
|
||||
</a>
|
||||
@ -90,9 +97,9 @@
|
||||
,
|
||||
|
||||
<?php if ($this->context->score === -1): ?>
|
||||
<a class="simple-action selected" href="<?php echo TextHelper::replaceTokens($scoreLink, ['score' => 0]) ?>">
|
||||
<a class="simple-action selected" href="<?php echo $scoreLink(0) ?>">
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo TextHelper::replaceTokens($scoreLink, ['score' => -1]) ?>">
|
||||
<a class="simple-action" href="<?php echo $scoreLink(-1) ?>">
|
||||
<?php endif ?>
|
||||
down
|
||||
</a>]
|
||||
@ -146,11 +153,8 @@
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<div class="unit favorites">
|
||||
<?php if (count($this->context->transport->post->ownFavoritee) == 0): ?>
|
||||
<h1>favorites</h1>
|
||||
<p>None yet.</p>
|
||||
<?php else: ?>
|
||||
<?php if (count($this->context->transport->post->ownFavoritee) > 0): ?>
|
||||
<div class="unit favorites">
|
||||
<h1>favorites (<?php echo count($this->context->transport->post->ownFavoritee) ?>)</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->via('favoritee')->sharedUser as $user): ?>
|
||||
@ -161,8 +165,8 @@
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (count($this->context->transport->post->via('crossref')->sharedPost)): ?>
|
||||
<div class="relations unit">
|
||||
@ -279,34 +283,23 @@
|
||||
|
||||
<div id="inner-content">
|
||||
<div class="post-wrapper post-type-<?php echo strtolower(PostType::toString($this->context->transport->post->type)) ?>">
|
||||
<?php switch ($this->context->transport->post->type):
|
||||
case PostType::Image: ?>
|
||||
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" alt="<?php echo $this->context->transport->post->name ?>"/>
|
||||
<?php break ?>
|
||||
<?php case PostType::Flash: ?>
|
||||
<embed width="<?php echo $this->context->transport->post->image_width ?>" height="<?php echo $this->context->transport->post->image_height ?>" type="application/x-shockwave-flash" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>"/>
|
||||
<?php break ?>
|
||||
<?php case PostType::Youtube: ?>
|
||||
<iframe width="800" height="600" src="//www.youtube.com/embed/<?php echo $this->context->transport->post->orig_name ?>" frameborder="0" allowfullscreen></iframe>
|
||||
<?php break ?>
|
||||
<?php endswitch ?>
|
||||
<?php echo $this->renderFile('post-file-render') ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canEditAnything): ?>
|
||||
<?php $this->renderFile('post-edit') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="comments unit">
|
||||
<?php if (empty($this->context->transport->post->ownComment)): ?>
|
||||
<h1>comments</h1>
|
||||
None yet.
|
||||
<?php else: ?>
|
||||
<h1>comments (<?php echo $this->context->transport->post->countOwn('comment') ?>)</h1>
|
||||
<div class="comments">
|
||||
<?php foreach ($this->context->transport->post->ownComment as $comment): ?>
|
||||
<?php $this->context->comment = $comment ?>
|
||||
<?php echo $this->renderFile('comment-small') ?>
|
||||
<?php endforeach ?>
|
||||
<div class="comments-wrapper">
|
||||
<?php if (!empty($this->context->transport->post->ownComment)): ?>
|
||||
<div class="comments unit">
|
||||
<h1>comments (<?php echo $this->context->transport->post->countOwn('comment') ?>)</h1>
|
||||
<div class="comments">
|
||||
<?php foreach ($this->context->transport->post->ownComment as $comment): ?>
|
||||
<?php $this->context->comment = $comment ?>
|
||||
<?php echo $this->renderFile('comment-small') ?>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
@ -1,17 +1,48 @@
|
||||
<?php $max = max([0]+array_map(function($x) { return $x['post_count']; }, $this->context->transport->tags)); ?>
|
||||
<?php $add = 0.25 ?>
|
||||
<?php $mul = 0.75 / max(1, log(max(1, $max))) ?>
|
||||
<?php $url = \Chibi\UrlHelper::route('post', 'list', ['query' => '{query}']) ?>
|
||||
<div class="tags">
|
||||
<nav class="sort-styles">
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->tags as $tag): ?>
|
||||
<?php $name = $tag['name'] ?>
|
||||
<?php $count = $tag['post_count'] ?>
|
||||
<li class="tag" title="<?php echo $name ?> (<?php echo $count ?>)">
|
||||
<a href="<?php echo TextHelper::replaceTokens($url, ['query' => $name]) ?>" style="opacity: <?php printf('%.02f', $add + $mul * log($count)) ?>">
|
||||
<?php echo $name . ' (' . $count . ')' ?>
|
||||
</a>
|
||||
<?php
|
||||
$sortStyles =
|
||||
[
|
||||
'order:alpha,asc' => 'Sort A→Z',
|
||||
'order:alpha,desc' => 'Sort Z→A',
|
||||
'order:popularity,desc' => 'Often used first',
|
||||
'order:popularity,asc' => 'Rarely used first',
|
||||
];
|
||||
|
||||
if ($this->config->registration->staffActivation)
|
||||
$sortStyles['pending'] = 'Pending staff review';
|
||||
?>
|
||||
|
||||
<?php foreach ($sortStyles as $key => $text): ?>
|
||||
<?php if ($this->context->filter == $key): ?>
|
||||
<li class="active">
|
||||
<?php else: ?>
|
||||
<li>
|
||||
<?php endif ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('tag', 'list', ['filter' => $key]) ?>"><?php echo $text ?></a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<?php if (empty($this->context->transport->tags)): ?>
|
||||
<p class="alert alert-warning">No tags to show.</p>
|
||||
<?php else: ?>
|
||||
<?php $max = max([0]+array_map(function($x) { return $x['post_count']; }, $this->context->transport->tags)); ?>
|
||||
<?php $add = 0. ?>
|
||||
<?php $mul = 10. / max(1, log(max(1, $max))) ?>
|
||||
<?php $url = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']) ?>
|
||||
<div class="tags">
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->tags as $tag): ?>
|
||||
<?php $name = $tag['name'] ?>
|
||||
<?php $count = $tag['post_count'] ?>
|
||||
<li class="tag" title="<?php echo $name ?> (<?php echo $count ?>)">
|
||||
<a href="<?php echo str_replace('_query_', $name, $url) ?>" class="frequency<?php printf('%1.0f', $add + $mul * log($count)) ?>">
|
||||
<?php echo $name . ' (' . $count . ')' ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
@ -3,12 +3,12 @@
|
||||
<h1>mass tag</h1>
|
||||
<div>
|
||||
<label class="left" for="mass-tag-query">Search query:</label>
|
||||
<div class="input-wrapper"><input type="text" name="query" id="mass-tag-query" value="<?php echo isset($this->context->massTagQuery) ? $this->context->massTagQuery : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
<div class="input-wrapper"><input class="autocomplete" type="text" type="text" name="query" id="mass-tag-query" value="<?php echo isset($this->context->massTagQuery) ? $this->context->massTagQuery : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="mass-tag-tag">Tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tag" id="mass-tag-tag" value="<?php echo isset($this->context->massTagTag) ? $this->context->massTagTag : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
<div class="input-wrapper"><input class="autocomplete" type="text" type="text" name="tag" id="mass-tag-tag" value="<?php echo isset($this->context->massTagTag) ? $this->context->massTagTag : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
@ -3,12 +3,12 @@
|
||||
<h1>merge tags</h1>
|
||||
<div>
|
||||
<label class="left" for="merge-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="merge-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
<div class="input-wrapper"><input class="autocomplete" type="text" type="text" name="source-tag" id="merge-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="merge-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="target-tag" id="merge-target-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
<div class="input-wrapper"><input class="autocomplete" type="text" name="target-tag" id="merge-target-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h1>rename tags</h1>
|
||||
<div>
|
||||
<label class="left" for="rename-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="rename-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
<div class="input-wrapper"><input class="autocomplete" type="text" type="text" name="source-tag" id="rename-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'settings', ['name' => $this->context->transport->user->name]) ?>" method="post" class="settings aligned">
|
||||
<div class="safety">
|
||||
<label class="left" for="name">Safety:</label>
|
||||
<label class="left">Safety:</label>
|
||||
<div class="input-wrapper">
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety))): ?>
|
||||
@ -12,10 +12,20 @@
|
||||
</div>
|
||||
|
||||
<div class="endless-scrolling">
|
||||
<label class="left" for="name">Endless scrolling:</label>
|
||||
<label class="left" for="endless-scrolling">Endless scrolling:</label>
|
||||
<div class="input-wrapper">
|
||||
<label>
|
||||
<input type="checkbox" name="endless-scrolling" <?php if ($this->context->transport->user->hasEnabledEndlessScrolling()) echo ' checked="checked"' ?>/>
|
||||
<input type="checkbox" id="endless-scrolling" name="endless-scrolling" <?php if ($this->context->transport->user->hasEnabledEndlessScrolling()) echo ' checked="checked"' ?>/>
|
||||
Enabled
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-tag-titles">
|
||||
<label class="left" for="post-tag-titles">Tags in thumbs:</label>
|
||||
<div class="input-wrapper">
|
||||
<label>
|
||||
<input type="checkbox" id="post-tag-titles" name="post-tag-titles" <?php if ($this->context->transport->user->hasEnabledPostTagTitles()) echo ' checked="checked"' ?>/>
|
||||
Enabled
|
||||
</label>
|
||||
</div>
|
||||
|
35
src/core.php
35
src/core.php
@ -1,32 +1,47 @@
|
||||
<?php
|
||||
define('SZURU_VERSION', '0.4.0');
|
||||
define('SZURU_VERSION', '0.5.0');
|
||||
define('SZURU_LINK', 'http://github.com/rr-/szurubooru');
|
||||
|
||||
//basic settings and preparation
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
$startTime = microtime(true);
|
||||
$rootDir = __DIR__ . DS . '..' . DS;
|
||||
|
||||
$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');
|
||||
|
||||
//extension sanity checks
|
||||
$requiredExtensions = ['pdo', 'pdo_sqlite', 'gd', 'openssl', 'fileinfo'];
|
||||
foreach ($requiredExtensions as $ext)
|
||||
if (!extension_loaded($ext))
|
||||
die('PHP extension "' . $ext . '" must be enabled to continue.' . PHP_EOL);
|
||||
|
||||
//basic include calls, autoloader init
|
||||
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';
|
||||
|
||||
\Chibi\AutoLoader::init(__DIR__);
|
||||
|
||||
//load config manually
|
||||
$configPaths =
|
||||
[
|
||||
$rootDir . DS . 'data' . DS . 'config.ini',
|
||||
$rootDir . DS . 'data' . DS . 'local.ini',
|
||||
];
|
||||
$config = new \Chibi\Config();
|
||||
foreach ($configPaths as $path)
|
||||
if (file_exists($path))
|
||||
$config->loadIni($path);
|
||||
\Chibi\Registry::setConfig($config);
|
||||
|
||||
//prepare context
|
||||
\Chibi\Facade::init();
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$context->startTime = $startTime;
|
||||
$context->rootDir = $rootDir;
|
||||
|
||||
R::setup('sqlite:' . $config->main->dbPath);
|
||||
//load database
|
||||
R::setup('sqlite:' . TextHelper::absolutePath($config->main->dbPath));
|
||||
R::freeze(true);
|
||||
R::dependencies(['tag' => ['post'], 'favoritee' => ['post', 'user'], 'comment' => ['post', 'user']]);
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
require_once 'src/core.php';
|
||||
$config = configFactory();
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
$dbVersion = Model_Property::get('db-version');
|
||||
$dbVersion = Model_Property::get(Model_Property::DbVersion);
|
||||
printf('DB version = %d' . PHP_EOL, $dbVersion);
|
||||
|
||||
$upgrades = glob('src/Upgrades/*.sql');
|
||||
$upgradesPath = TextHelper::absolutePath(\Chibi\Registry::getContext()->rootDir . DS . 'src' . DS . 'Upgrades');
|
||||
$upgrades = glob($upgradesPath . DS . '*.sql');
|
||||
natcasesort($upgrades);
|
||||
|
||||
foreach ($upgrades as $upgradePath)
|
||||
@ -28,5 +29,5 @@ foreach ($upgrades as $upgradePath)
|
||||
}
|
||||
}
|
||||
|
||||
Model_Property::set('db-version', $upgradeVersion);
|
||||
Model_Property::set(Model_Property::DbVersion, $upgradeVersion);
|
||||
}
|
||||
|
Reference in New Issue
Block a user