Compare commits
136 Commits
Author | SHA1 | Date | |
---|---|---|---|
aff68e88cf | |||
bf0e40683c | |||
17bd7a7572 | |||
a5d0a3f9ef | |||
5eb5e18b77 | |||
19a8b90ca2 | |||
e7ec8ea49f | |||
0286e11c30 | |||
7605177a6b | |||
52ceb8d962 | |||
cc30829c63 | |||
9ab961985d | |||
fdee23af99 | |||
3c41940142 | |||
da63c0fd19 | |||
4fd25b10c6 | |||
210342a5bf | |||
69a993c5af | |||
7b473ba06f | |||
4166200dbc | |||
4e64431a96 | |||
6582b395d2 | |||
04e9bad79e | |||
45e9d32f58 | |||
bb01ae7fca | |||
039d56c260 | |||
76a60ed5d7 | |||
fb02feeed3 | |||
9ec269330c | |||
8cd457848c | |||
70a4b46cf1 | |||
202c820a9a | |||
5e30253789 | |||
6fadc612fd | |||
7faf46beb9 | |||
7b014f036b | |||
51dbc65754 | |||
b8fedc1297 | |||
bb0e844e4e | |||
09b5a38c95 | |||
b093a090eb | |||
e1c8139373 | |||
101864459d | |||
f7a0b7b440 | |||
b3f15dc049 | |||
be919603e3 | |||
ac506e8c95 | |||
8d5b82287a | |||
f32c045349 | |||
579df65c21 | |||
c4faa3bf85 | |||
c3b2c68add | |||
fe99f97287 | |||
bd05123cfc | |||
9110a27167 | |||
fd99821bd7 | |||
ad8f2a8038 | |||
86c811b0e7 | |||
ea4c7fac6e | |||
1714e9e665 | |||
157572d9ca | |||
19eea1e5b6 | |||
b7084d61ae | |||
36caef3831 | |||
e0c4c28e70 | |||
96d994eeea | |||
bc43883339 | |||
cf1b5837a7 | |||
f119ab724a | |||
3130a66ad3 | |||
e2e9d9bf13 | |||
9e6716021a | |||
49b91b7f55 | |||
2aaafcd0de | |||
24f5024db3 | |||
e346a8e57c | |||
558f8f42c8 | |||
2f8d43cb4b | |||
c4d5263422 | |||
3f3024d6ac | |||
b55a8f1dce | |||
f726690ea3 | |||
0d360d525e | |||
bddf04ea78 | |||
d92d49d60d | |||
35146e9587 | |||
cf749aa5fd | |||
0712f15ee4 | |||
db180376d4 | |||
0eb1ef4fff | |||
5c76a41ae7 | |||
0ea25dad24 | |||
c648cd848d | |||
c662d52d62 | |||
e733da58d2 | |||
febf22a667 | |||
7d6bab9590 | |||
2279e5605b | |||
4ecb3f3b81 | |||
89826a0be9 | |||
d3eaf27bdc | |||
47759adb66 | |||
b5070e06fe | |||
e1acb8bd99 | |||
872780397d | |||
d135f84bf2 | |||
31f07672c4 | |||
328d3f833b | |||
87eaa9ba9e | |||
18097b6192 | |||
739e5d3b5d | |||
7cc2a98992 | |||
7f9aaad324 | |||
319a9852fc | |||
d45ab47d3b | |||
eaa8c4897d | |||
823888b0c1 | |||
90a75e4d30 | |||
ce302c438d | |||
70f931b921 | |||
7743753641 | |||
e910d2f517 | |||
83355f3789 | |||
9f5bdc3da0 | |||
0f72ef3963 | |||
6b55706fb4 | |||
ff3e4bc287 | |||
f2947a2550 | |||
aab67f4b6c | |||
aed60da6f9 | |||
58a6345ae8 | |||
bc24b7d2cf | |||
3052a6f032 | |||
4bfa2a019a | |||
688385d553 | |||
a3be044ced |
45
config.ini
@ -1,5 +1,4 @@
|
||||
[chibi]
|
||||
userCodeDir=./src/
|
||||
prettyPrint=1
|
||||
|
||||
[main]
|
||||
@ -8,17 +7,22 @@ filesPath=./files/
|
||||
thumbsPath=./thumbs/
|
||||
mediaPath=./public_html/media/
|
||||
title=szurubooru
|
||||
salt = "1A2/$_4xVa"
|
||||
logsPath=./logs/
|
||||
|
||||
[misc]
|
||||
featuredPostMaxDays=7
|
||||
debugQueries=0
|
||||
|
||||
[browsing]
|
||||
usersPerPage=8
|
||||
postsPerPage=20
|
||||
thumbWidth=140
|
||||
thumbHeight=140
|
||||
thumbWidth=150
|
||||
thumbHeight=150
|
||||
thumbStyle=outside
|
||||
endlessScrolling=1
|
||||
endlessScrollingDefault=1
|
||||
maxSearchTokens=4
|
||||
maxRelatedPosts=50
|
||||
|
||||
[comments]
|
||||
minLength = 5
|
||||
@ -32,21 +36,30 @@ passRegex = "/^.+$/"
|
||||
userNameMinLength = 3
|
||||
userNameMaxLength = 20
|
||||
userNameRegex = "/^[\w_-]+$/ui"
|
||||
salt = "1A2/$_4xVa"
|
||||
|
||||
needEmailForRegistering = 1
|
||||
needEmailForCommenting = 0
|
||||
needEmailForUploading = 1
|
||||
confirmationEmailEnabled = 1
|
||||
confirmationEmailSenderName = "{host} registration engine"
|
||||
confirmationEmailSenderName = "{host} mailing system"
|
||||
confirmationEmailSenderEmail = "noreply@{host}"
|
||||
confirmationEmailSubject = "{host} activation"
|
||||
confirmationEmailSubject = "{host} - account activation"
|
||||
confirmationEmailBody = "Hello,
|
||||
|
||||
You received this e-mail because someone registered a user with this address at {host}. If it's you, visit {link} to finish registration process, otherwise you may ignore and delete this e-mail.
|
||||
You received this e-mail because someone registered a user with this e-mail address at {host}. If it's you, visit {link} to finish registration process, otherwise you may ignore and delete this e-mail.
|
||||
|
||||
Kind regards,
|
||||
{host} registration engine"
|
||||
{host} mailing system"
|
||||
|
||||
passwordResetEmailSenderName = "{host} mailing system"
|
||||
passwordResetEmailSenderEmail = "noreply@{host}"
|
||||
passwordResetEmailSubject = "{host} - password reset"
|
||||
passwordResetEmailBody = "Hello,
|
||||
|
||||
You received this e-mail because someone requested a password reset for user with this e-mail address at {host}. If it's you, visit {link} to finish password reset process, otherwise you may ignore and delete this e-mail.
|
||||
|
||||
Kind regards,
|
||||
{host} mailing system"
|
||||
|
||||
[privileges]
|
||||
uploadPost=registered
|
||||
@ -65,26 +78,34 @@ editPostSafety.all=moderator
|
||||
editPostTags=registered
|
||||
editPostThumb=moderator
|
||||
editPostSource=moderator
|
||||
editPostRelations.own=registered
|
||||
editPostRelations.all=moderator
|
||||
hidePost.own=moderator
|
||||
hidePost.all=moderator
|
||||
deletePost.own=moderator
|
||||
deletePost.all=moderator
|
||||
featurePost=moderator
|
||||
scorePost=registered
|
||||
flagPost=registered
|
||||
|
||||
listUsers=registered
|
||||
viewUser=registered
|
||||
viewUserEmail=admin
|
||||
viewUserEmail.all=admin
|
||||
viewUserEmail.own=registered
|
||||
changeUserPassword.own=registered
|
||||
changeUserPassword.all=admin
|
||||
changeUserEmail.own=registered
|
||||
changeUserEmail.all=admin
|
||||
changeUserAccessRank=admin
|
||||
changeUserName=moderator
|
||||
changeUserSettings.all=nobody
|
||||
changeUserSettings.own=registered
|
||||
acceptUserRegistration=moderator
|
||||
banUser.own=nobody
|
||||
banUser.all=admin
|
||||
deleteUser.own=registered
|
||||
deleteUser.all=nobody
|
||||
flagUser=registered
|
||||
|
||||
listComments=anonymous
|
||||
addComment=registered
|
||||
@ -94,3 +115,7 @@ deleteComment.all=moderator
|
||||
listTags=anonymous
|
||||
mergeTags=moderator
|
||||
renameTags=moderator
|
||||
massTag=moderator
|
||||
|
||||
listLogs=moderator
|
||||
viewLog=moderator
|
||||
|
5
init.php
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once 'src/core.php';
|
||||
$config = configFactory();
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$fontsPath = $config->main->mediaPath . DS . 'fonts' . DS;
|
||||
$libPath = $config->main->mediaPath . DS . 'lib' . DS;
|
||||
|
||||
@ -49,6 +49,9 @@ foreach ($lines as $line)
|
||||
download('http://raw.github.com/aehlke/tag-it/master/css/jquery.tagit.css', $libPath . 'tagit' . DS . 'jquery.tagit.css');
|
||||
download('http://raw.github.com/aehlke/tag-it/master/js/tag-it.min.js', $libPath . 'tagit' . DS . 'jquery.tagit.js');
|
||||
|
||||
//Mousetrap
|
||||
download('http://raw.github.com/ccampbell/mousetrap/master/mousetrap.min.js', $libPath . 'mousetrap' . DS . 'mousetrap.min.js');
|
||||
|
||||
//fonts
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans.ttf', $fontsPath . 'DroidSans.ttf');
|
||||
download('http://googlefontdirectory.googlecode.com/hg/apache/droidsans/DroidSans-Bold.ttf', $fontsPath . 'DroidSans-Bold.ttf');
|
||||
|
2
logs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
chdir('..');
|
||||
require_once 'src/core.php';
|
||||
require_once 'src/Bootstrap.php';
|
||||
|
||||
$query = $_SERVER['REQUEST_URI'];
|
||||
\Chibi\Facade::run($query, configFactory(), new Bootstrap());
|
||||
\Chibi\Facade::run($query, new Bootstrap());
|
||||
|
@ -11,3 +11,20 @@ form.auth p {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
form.auth .help {
|
||||
opacity: .5;
|
||||
margin-top: 1em;
|
||||
font-size: small;
|
||||
}
|
||||
form.auth .help p {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
form.auth .help label+div {
|
||||
float: left;
|
||||
}
|
||||
form.auth .help ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -98,8 +98,8 @@ body {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
width: 25px;
|
||||
line-height: 38px;
|
||||
margin-right: -1px;
|
||||
line-height: 28px;
|
||||
margin: 5px -1px 5px 0;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
#top-nav li.safety a:after {
|
||||
@ -108,6 +108,7 @@ body {
|
||||
#top-nav li.safety span {
|
||||
display: none;
|
||||
}
|
||||
#top-nav li.safety a:focus,
|
||||
#top-nav li.safety a:hover { opacity: .7; }
|
||||
#top-nav li.safety a.inactive { opacity: 1; }
|
||||
#top-nav li.safety .safety-safe .enabled { background: #cfe6c2; background: linear-gradient(to bottom, #CFE6C2 0%, #80C670 100%); }
|
||||
@ -265,7 +266,11 @@ form.aligned input[type=file] {
|
||||
}
|
||||
form.aligned input[type=radio],
|
||||
form.aligned input[type=checkbox] {
|
||||
vertical-align: text-top;
|
||||
width: auto;
|
||||
max-width: auto;
|
||||
margin: 0 10px 0 0;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
@ -276,8 +281,11 @@ form.aligned input[type=checkbox] {
|
||||
.input-wrapper input,
|
||||
.input-wrapper textarea,
|
||||
.input-wrapper select {
|
||||
width: 80%;
|
||||
max-width: 80%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
label {
|
||||
@ -368,3 +376,7 @@ pre.debug {
|
||||
.spoiler:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
9
public_html/media/css/index-help.css
Normal file
@ -0,0 +1,9 @@
|
||||
code {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
span.comma {
|
||||
margin-left: -0.5em;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 2em;
|
||||
}
|
@ -1,70 +1,67 @@
|
||||
#sidebar {
|
||||
min-width: 100px;
|
||||
padding: 5em 0;
|
||||
width: 25%;
|
||||
margin-right: 5%;
|
||||
#welcome {
|
||||
text-align: center;
|
||||
}
|
||||
#sidebar p {
|
||||
#welcome p {
|
||||
font-size: small;
|
||||
margin-top: 0;
|
||||
}
|
||||
#sidebar p span:not(:last-child):after {
|
||||
#welcome p span:not(:last-child):after {
|
||||
content: '\022C5';
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
#sidebar h1 {
|
||||
#content h1 {
|
||||
font-size: 26pt;
|
||||
}
|
||||
#sidebar input {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
border: 2px solid #ccc;
|
||||
padding: 5px;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#inner-content {
|
||||
float: right;
|
||||
#content {
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
min-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#inner-content .header .tags:before {
|
||||
margin: 0 0.5em;
|
||||
content: '\2013';
|
||||
.small-screen #content {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 500px;
|
||||
}
|
||||
#inner-content .header ul {
|
||||
|
||||
#content .body {
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAJElEQVQImWNgYGBgePfu3X8YZoABFA6SIqwS+HXgtANZF7IEAJnGPJE70lLLAAAAAElFTkSuQmCC');
|
||||
margin-top: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
#content .body img {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
#content .body a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#content .footer {
|
||||
font-size: small;
|
||||
color: dimgray;
|
||||
margin: 0.5em 0 3em 0;
|
||||
}
|
||||
#content .footer .left {
|
||||
float: left;
|
||||
}
|
||||
#content .footer .right {
|
||||
float: right;
|
||||
}
|
||||
#content .footer ul {
|
||||
list-style-type: none;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#inner-content .header li {
|
||||
#content .footer li {
|
||||
display: inline;
|
||||
}
|
||||
#inner-content .header li:not(:last-child) a:after {
|
||||
#content .footer li:not(:last-child) a:after {
|
||||
content: ', ';
|
||||
}
|
||||
|
||||
#inner-content .body {
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAJElEQVQImWNgYGBgePfu3X8YZoABFA6SIqwS+HXgtANZF7IEAJnGPJE70lLLAAAAAElFTkSuQmCC');
|
||||
margin: 1em 0;
|
||||
text-align: center;
|
||||
}
|
||||
#inner-content .body img {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
#inner-content .body a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#inner-content .header .favs-comments {
|
||||
margin-left: 0.5em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#inner-content .footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
13
public_html/media/css/logs.css
Normal file
@ -0,0 +1,13 @@
|
||||
#content input {
|
||||
margin: 0 1em;
|
||||
height: 25px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
pre strong {
|
||||
background: #fee;
|
||||
}
|
@ -31,3 +31,9 @@
|
||||
.paginator li.disabled a {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.paginator li a:focus,
|
||||
.paginator li a:hover {
|
||||
border: 1px solid firebrick;
|
||||
background: pink;
|
||||
}
|
||||
|
@ -1,4 +1,31 @@
|
||||
.post {
|
||||
margin: 0.5em;
|
||||
float: left;
|
||||
}
|
||||
.posts-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
.posts {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
.small-screen .form-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
form.aligned {
|
||||
margin: 0 auto;
|
||||
width: 24em;
|
||||
text-align: left;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
}
|
||||
form h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li.mass-tag {
|
||||
float: right;
|
||||
}
|
||||
|
@ -1,62 +1,127 @@
|
||||
.post {
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0.25em 0.25em #eee;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.post-type-flash {
|
||||
border-color: #dd5;
|
||||
box-shadow: 0.25em 0.25em #eeb, 0.1em 0.1em 0.5em 0.1em rgba(238,238,187,0.5);
|
||||
.post .link {
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0.25em 0.25em #eee;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.post:focus,
|
||||
.post:hover {
|
||||
.post-type-youtube:after,
|
||||
.post-type-flash:after {
|
||||
position: absolute;
|
||||
right: 1px; /* border */
|
||||
top: 1px; /* border */
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
content: ' ';
|
||||
pointer-events: none;
|
||||
}
|
||||
.post-type-flash {
|
||||
border-color: red;
|
||||
}
|
||||
.post-type-youtube {
|
||||
border-color: red;
|
||||
}
|
||||
.post-type-flash:after {
|
||||
background: url('../img/thumb-overlay-swf.png');
|
||||
}
|
||||
.post-type-youtube:after {
|
||||
background: url('../img/thumb-overlay-yt.png');
|
||||
}
|
||||
|
||||
|
||||
.post .toggle-tag {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
background: whitesmoke;
|
||||
opacity: .5;
|
||||
border: 1px solid black;
|
||||
margin: 60px 25px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.post .toggle-tag:focus,
|
||||
.post .toggle-tag:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.post.taggable.tagged .toggle-tag {
|
||||
background-color: #0f0;
|
||||
color: black;
|
||||
}
|
||||
.post.taggable:not(.tagged) .toggle-tag {
|
||||
background-color: #f00;
|
||||
color: white;
|
||||
}
|
||||
.post .link {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
.post .link:focus,
|
||||
.post .link:hover {
|
||||
border: 1px solid firebrick;
|
||||
box-shadow: 0.25em 0.25em pink;
|
||||
}
|
||||
.post:focus img.thumb,
|
||||
.post:hover img.thumb {
|
||||
.post .link:focus img.thumb,
|
||||
.post .link:hover img.thumb {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.post a {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.post img.thumb {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
display: block;
|
||||
display: inline-block;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.post .info-bar {
|
||||
display: none;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
border-top: 1px solid firebrick;
|
||||
background: rgba(255, 128, 128, 0.75);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-indeX: 3;
|
||||
left: 1px; /* border */
|
||||
right: 1px; /* border */
|
||||
bottom: 1px; /* border */
|
||||
text-align: center;
|
||||
}
|
||||
.post:hover .info-bar {
|
||||
.post .link:focus .info-bar,
|
||||
.post .link:hover .info-bar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post .icon-score {
|
||||
background-position: -85px -1px;
|
||||
}
|
||||
.post .icon-comments {
|
||||
margin-left: 3px;
|
||||
background-position: -64px -1px;
|
||||
}
|
||||
.post .icon-favs {
|
||||
background-position: -43px -1px;
|
||||
}
|
||||
.post [class^='icon-'] {
|
||||
.post .link [class^='icon-'] {
|
||||
opacity: .75;
|
||||
background-color: transparent;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.post span {
|
||||
.post .link span {
|
||||
vertical-align: top;
|
||||
font-size: small;
|
||||
line-height: 20px;
|
||||
margin-right: 0.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
.post .link span.inactive {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#sidebar {
|
||||
width: 200px;
|
||||
width: 224px;
|
||||
line-height: 1.33em;
|
||||
font-size: 90%;
|
||||
}
|
||||
@ -10,17 +10,22 @@ embed {
|
||||
}
|
||||
|
||||
.post-type-image img {
|
||||
background: url('../img/bk-image.png') lemonchiffon;
|
||||
/*background: url('../img/bk-image.png') lemonchiffon;*/
|
||||
}
|
||||
.post-type-flash embed {
|
||||
background: url('../img/bk-swf.png') lemonchiffon;
|
||||
}
|
||||
|
||||
#sidebar .relations ul,
|
||||
#sidebar .tags ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#sidebar .tags li {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#sidebar .tags li .count {
|
||||
padding-left: 0.5em;
|
||||
color: silver;
|
||||
@ -42,6 +47,29 @@ embed {
|
||||
background-color: silver;
|
||||
}
|
||||
|
||||
#sidebar .uploader img {
|
||||
vertical-align: middle;
|
||||
margin: 0 0.5em 0 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=16');
|
||||
}
|
||||
|
||||
#sidebar .safety-safe {
|
||||
color: #43aa43;
|
||||
}
|
||||
#sidebar .safety-sketchy {
|
||||
color: #d4a627;
|
||||
}
|
||||
#sidebar .safety-unsafe {
|
||||
color: #df4b0d;
|
||||
}
|
||||
|
||||
#sidebar .score .selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
i.icon-prev {
|
||||
background-position: -12px -1px;
|
||||
}
|
||||
|
30
public_html/media/css/tabs.css
Normal file
@ -0,0 +1,30 @@
|
||||
.tabs ul {
|
||||
list-style-type: none;
|
||||
margin: 0 0 1em 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.tabs li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tabs li a {
|
||||
display: inline-block;
|
||||
padding: 0.5em 1em;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.tabs li a {
|
||||
border: 1px solid white;
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: silver;
|
||||
}
|
||||
.tabs li.selected a {
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: 1px solid white;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.tabs li a:focus {
|
||||
color: firebrick;
|
||||
}
|
@ -6,15 +6,22 @@
|
||||
-moz-column-width: 14em;
|
||||
-webkit-column-width: 14em;
|
||||
}
|
||||
.tags li a:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.tags li {
|
||||
margin: 0.2em 0.5em;
|
||||
text-align: top;
|
||||
width: 14em;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
width: 50%;
|
||||
max-width: 24em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
@ -23,14 +30,11 @@
|
||||
}
|
||||
form.aligned {
|
||||
text-align: left;
|
||||
margin: 2em auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
}
|
||||
form.aligned input {
|
||||
width: 24em;
|
||||
}
|
||||
form h1 {
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
@ -8,6 +8,13 @@
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tab {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.tab.url {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-handler-wrapper {
|
||||
display: table;
|
||||
width: 100%;
|
||||
@ -17,7 +24,7 @@
|
||||
font-size: 150%;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
height: 300px;
|
||||
height: 8em;
|
||||
display: table-cell;
|
||||
border: 3px dashed #ddd;
|
||||
}
|
||||
@ -26,28 +33,34 @@
|
||||
border-color: firebrick;
|
||||
}
|
||||
|
||||
#url-handler textarea {
|
||||
width: 100%;
|
||||
height: 10em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.post .thumbnail {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
background-image: url('../img/thumb-upload.png');
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
line-height: 150px;
|
||||
background-image: url('../img/thumb.jpg');
|
||||
background-size: 150px 150px;
|
||||
border: 1px solid black;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.post .alert,
|
||||
#upload-step2,
|
||||
#upload-no-posts,
|
||||
#post-template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post {
|
||||
margin-bottom: 4em;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.post .ops {
|
||||
@ -101,7 +114,8 @@
|
||||
}
|
||||
.post label.left {
|
||||
display: inline-block;
|
||||
width: 4em;
|
||||
width: 60px;
|
||||
padding-right: 10px;
|
||||
float: left;
|
||||
}
|
||||
.post .safety label:not(.left) {
|
||||
@ -112,18 +126,19 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 50%;
|
||||
white-space: pre;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 33px;
|
||||
}
|
||||
|
||||
.safety-sfw {
|
||||
color: #63ca63;
|
||||
.safety-safe {
|
||||
color: #43aa43;
|
||||
}
|
||||
.safety-sketchy {
|
||||
color: #f4c657;
|
||||
color: #d4a627;
|
||||
}
|
||||
.safety-nsfw {
|
||||
.safety-unsafe {
|
||||
color: #df4b0d;
|
||||
}
|
||||
|
||||
@ -134,11 +149,8 @@ ul.tagit {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.submit-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
#theSubmit {
|
||||
margin: 0 auto;
|
||||
#the-submit {
|
||||
margin: 0 0 0 205px;
|
||||
}
|
||||
|
||||
.post .form-wrapper {
|
||||
|
@ -3,33 +3,6 @@
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.tabs ul {
|
||||
list-style-type: none;
|
||||
margin: 0 0 1em 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.tabs li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tabs li a {
|
||||
display: inline-block;
|
||||
padding: 0.5em 1em;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.tabs li a {
|
||||
border: 1px solid white;
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: silver;
|
||||
}
|
||||
.tabs li.selected a {
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: 1px solid white;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
@ -40,14 +13,20 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form.settings label.left,
|
||||
form.delete label.left,
|
||||
form.edit label.left {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
form.settings .alert,
|
||||
form.delete .alert,
|
||||
form.edit .alert {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
form.settings input,
|
||||
form.delete input,
|
||||
form.edit select,
|
||||
form.edit input {
|
||||
width: 16em;
|
||||
|
Before Width: | Height: | Size: 612 B After Width: | Height: | Size: 766 B |
BIN
public_html/media/img/thumb-overlay-swf.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
public_html/media/img/thumb-overlay-yt.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 688 B |
BIN
public_html/media/img/thumb.jpg
Normal file
After Width: | Height: | Size: 933 B |
@ -1,4 +1,6 @@
|
||||
$.fn.hasAttr = function(name) {
|
||||
//core functionalities, prototypes
|
||||
$.fn.hasAttr = function(name)
|
||||
{
|
||||
return this.attr(name) !== undefined;
|
||||
};
|
||||
|
||||
@ -19,6 +21,9 @@ if ($.when.all === undefined)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//safety trigger
|
||||
$(function()
|
||||
{
|
||||
$('.safety a').click(function(e)
|
||||
@ -34,16 +39,20 @@ $(function()
|
||||
$.get(url, function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
window.location.reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
}
|
||||
alert(data['message']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
//basic event listeners
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', function()
|
||||
{
|
||||
function confirmEvent(e)
|
||||
{
|
||||
if (!confirm($(this).attr('data-confirm-text')))
|
||||
@ -69,18 +78,20 @@ $(function()
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url, function(data)
|
||||
$.get(url, {submit: 1}, function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
if (aDom.hasAttr('data-redirect-url'))
|
||||
window.location.href = aDom.attr('data-redirect-url');
|
||||
else if (aDom.data('callback'))
|
||||
aDom.data('callback')();
|
||||
else
|
||||
window.location.reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
alert(data['message']);
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
@ -101,13 +112,14 @@ $(function()
|
||||
form.append(input);
|
||||
});
|
||||
});
|
||||
|
||||
$(window).resize();
|
||||
});
|
||||
});
|
||||
|
||||
$(window).resize(function()
|
||||
|
||||
|
||||
//modify DOM on small viewports
|
||||
function processSidebar()
|
||||
{
|
||||
//modify DOM on small viewports
|
||||
$('#inner-content .unit').addClass('bottom-unit');
|
||||
if ($('body').width() < 600)
|
||||
{
|
||||
@ -118,7 +130,121 @@ $(window).resize(function()
|
||||
else
|
||||
{
|
||||
$('body').removeClass('small-screen');
|
||||
$('#inner-content').insertAfter($('#sidebar'));
|
||||
$('#sidebar').insertBefore($('#inner-content'));
|
||||
$('#sidebar .unit').removeClass('bottom-unit').addClass('left-unit');
|
||||
}
|
||||
$('body').data('last-width', $('body').width());
|
||||
}
|
||||
$(function()
|
||||
{
|
||||
$(window).resize(function()
|
||||
{
|
||||
if ($('body').width() == $('body').data('last-width'))
|
||||
return;
|
||||
});
|
||||
$('body').bind('dom-update', processSidebar);
|
||||
});
|
||||
|
||||
|
||||
|
||||
//autocomplete
|
||||
function split(val)
|
||||
{
|
||||
return val.split(/\s+/);
|
||||
}
|
||||
|
||||
function extractLast(term)
|
||||
{
|
||||
return split(term).pop();
|
||||
}
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('[data-autocomplete-url]').each(function()
|
||||
{
|
||||
var searchInput = $(this);
|
||||
searchInput
|
||||
// don't navigate away from the field on tab when selecting an item
|
||||
.bind("keydown", function(event)
|
||||
{
|
||||
if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active)
|
||||
{
|
||||
event.preventDefault();
|
||||
}
|
||||
}).autocomplete({
|
||||
minLength: 1,
|
||||
source: function(request, response)
|
||||
{
|
||||
var term = extractLast(request.term);
|
||||
if (term != '')
|
||||
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term}, function(data)
|
||||
{
|
||||
response($.map(data.tags, function(tag) { return { label: tag.name, value: tag.name }; }));
|
||||
});
|
||||
},
|
||||
focus: function()
|
||||
{
|
||||
// prevent value inserted on focus
|
||||
return false;
|
||||
},
|
||||
select: function(event, ui)
|
||||
{
|
||||
var terms = split(this.value);
|
||||
terms.pop();
|
||||
terms.push(ui.item.value);
|
||||
terms.push('');
|
||||
this.value = terms.join(' ');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getTagItOptions()
|
||||
{
|
||||
return {
|
||||
caseSensitive: false,
|
||||
autocomplete:
|
||||
{
|
||||
source:
|
||||
function(request, response)
|
||||
{
|
||||
var term = request.term.toLowerCase();
|
||||
var tags = $.map(this.options.availableTags, function(a)
|
||||
{
|
||||
return a.name;
|
||||
});
|
||||
var results = $.grep(tags, function(a)
|
||||
{
|
||||
if (term.length < 3)
|
||||
return a.toLowerCase().indexOf(term) == 0;
|
||||
else
|
||||
return a.toLowerCase().indexOf(term) != -1;
|
||||
});
|
||||
results = results.slice(0, 15);
|
||||
if (!this.options.allowDuplicates)
|
||||
results = this._subtractArray(results, this.assignedTags());
|
||||
response(results);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
//hotkeys
|
||||
$(function()
|
||||
{
|
||||
Mousetrap.bind('q', function() { $('#top-nav input').focus(); return false; }, 'keyup');
|
||||
Mousetrap.bind('w', function() { $('body,html').animate({scrollTop: '-=150px'}, 200); });
|
||||
Mousetrap.bind('s', function() { $('body,html').animate({scrollTop: '+=150px'}, 200); });
|
||||
Mousetrap.bind('a', function() { var url = $('.paginator:visible .prev:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('d', function() { var url = $('.paginator:visible .next:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
||||
Mousetrap.bind('p', function() { $('.post a').eq(0).focus(); return false; }, 'keyup');
|
||||
});
|
||||
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
|
4
public_html/media/js/logs.js
Normal file
@ -0,0 +1,4 @@
|
||||
$(function()
|
||||
{
|
||||
$('#content form input').eq(0).focus().select();
|
||||
});
|
38
public_html/media/js/mass-tag.js
Normal file
@ -0,0 +1,38 @@
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', function()
|
||||
{
|
||||
$('.post a.toggle-tag').click(function(e)
|
||||
{
|
||||
if(e.isPropagationStopped())
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
return;
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var url = $(this).attr('href') + '?json';
|
||||
$.get(url, {submit: 1}, function(data)
|
||||
{
|
||||
if (data['success'])
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
aDom.parents('.post').toggleClass('tagged');
|
||||
aDom.text(aDom.parents('.post').hasClass('tagged')
|
||||
? aDom.attr('data-text-tagged')
|
||||
: aDom.attr('data-text-untagged'));
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['message']);
|
||||
aDom.removeClass('inactive');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('body').trigger('dom-update');
|
||||
});
|
@ -18,6 +18,7 @@ function scrolled()
|
||||
var nextPage = dom.find('.paginator .next:not(.disabled) a').attr('href');
|
||||
$(document).data('page-next', nextPage);
|
||||
$('.paginator-content').append($(response).find('.paginator-content').children().css({opacity: 0}).animate({opacity: 1}, 'slow'));
|
||||
$('body').trigger('dom-update');
|
||||
scrolled();
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
$(function()
|
||||
function onDomUpdate()
|
||||
{
|
||||
$('li.edit a').click(function(e)
|
||||
{
|
||||
@ -12,22 +12,48 @@ $(function()
|
||||
var tags = [];
|
||||
$.getJSON('/tags?json', function(data)
|
||||
{
|
||||
aDom.removeClass('inactive');
|
||||
var formDom = $('form.edit-post');
|
||||
tags = data['tags'];
|
||||
|
||||
var tagItOptions =
|
||||
if (!$(formDom).is(':visible'))
|
||||
{
|
||||
caseSensitive: true,
|
||||
availableTags: tags,
|
||||
placeholderText: $('.tags input').attr('placeholder')
|
||||
};
|
||||
var tagItOptions = getTagItOptions();
|
||||
tagItOptions.availableTags = tags;
|
||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
||||
$('.tags input').tagit(tagItOptions);
|
||||
|
||||
e.preventDefault();
|
||||
var formDom = $('form.edit-post');
|
||||
formDom.show().css('height', formDom.height()).hide().slideDown();
|
||||
}
|
||||
|
||||
formDom.find('input[type=text]:visible:eq(0)').focus();
|
||||
$('html, body').animate({ scrollTop: $(formDom).offset().top + 'px' }, 'fast');
|
||||
});
|
||||
});
|
||||
|
||||
$('.comments.unit a.simple-action').data('callback', function()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('.comments.unit').replaceWith($(data).find('.comments.unit'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
});
|
||||
|
||||
$('#sidebar a.simple-action').data('callback', function()
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('#sidebar').replaceWith($(data).find('#sidebar'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('body').bind('dom-update', onDomUpdate);
|
||||
onDomUpdate();
|
||||
|
||||
$('form.edit-post').submit(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
@ -57,7 +83,7 @@ $(function()
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
alert(data['message']);
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
@ -102,17 +128,22 @@ $(function()
|
||||
if (preview)
|
||||
{
|
||||
formDom.find('.preview').html(data['textPreview']).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$.get(window.location.href, function(data)
|
||||
{
|
||||
$('.comments.unit').replaceWith($(data).find('.comments.unit'));
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
formDom.find('textarea').val('');
|
||||
}
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
alert(data['errorMessage']);
|
||||
alert(data['message']);
|
||||
formDom.find(':input').attr('readonly', false);
|
||||
formDom.removeClass('inactive');
|
||||
}
|
||||
@ -121,4 +152,8 @@ $(function()
|
||||
|
||||
$.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('e', function() { $('li.edit a').trigger('click'); return false; }, 'keyup');
|
||||
});
|
||||
|
@ -1,35 +1,36 @@
|
||||
$(function()
|
||||
{
|
||||
$('.tabs nav a').click(function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var className = $(this).parents('li').attr('class').replace('selected', '').replace(/^\s+|\s+$/, '');
|
||||
$('.tabs nav li').removeClass('selected');
|
||||
$(this).parents('li').addClass('selected');
|
||||
$('.tab').hide();
|
||||
$('.tab.' + className).show();
|
||||
});
|
||||
|
||||
var tags = [];
|
||||
$.getJSON('/tags?json', function(data)
|
||||
{
|
||||
tags = data['tags'];
|
||||
});
|
||||
|
||||
var handler = $('#file-handler');
|
||||
handler.on('dragenter', function(e)
|
||||
$('#file-handler').on('dragenter', function(e)
|
||||
{
|
||||
$(this).addClass('active');
|
||||
});
|
||||
|
||||
handler.on('dragleave', function(e)
|
||||
}).on('dragleave', function(e)
|
||||
{
|
||||
$(this).removeClass('active');
|
||||
});
|
||||
|
||||
handler.on('dragover', function(e)
|
||||
}).on('dragover', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
handler.on('drop', function(e)
|
||||
}).on('drop', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
handleFiles(e.originalEvent.dataTransfer.files);
|
||||
$(this).trigger('dragleave');
|
||||
});
|
||||
|
||||
handler.on('click', function(e)
|
||||
}).on('click', function(e)
|
||||
{
|
||||
$(':file').show().focus().trigger('click').hide();
|
||||
});
|
||||
@ -39,6 +40,24 @@ $(function()
|
||||
handleFiles(this.files);
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('#url-handler-wrapper button').click(function(e)
|
||||
{
|
||||
var urls = [];
|
||||
$.each($('#url-handler-wrapper textarea').val().split(/\s+/), function(i, url)
|
||||
{
|
||||
url = url.replace(/^\s+|\s+$/, '');
|
||||
if (url == '')
|
||||
return;
|
||||
urls.push(url);
|
||||
});
|
||||
$('#url-handler-wrapper textarea').val('');
|
||||
handleURLs(urls);
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('.post .move-down-trigger, .post .move-up-trigger').on('click', function()
|
||||
{
|
||||
var dir = $(this).hasClass('move-down-trigger') ? 'd' : 'u';
|
||||
@ -53,15 +72,12 @@ $(function()
|
||||
$(this).parents('.post').slideUp(function()
|
||||
{
|
||||
$(this).remove();
|
||||
handleInputs([]);
|
||||
});
|
||||
if ($('#upload-step2 .post').length == 1)
|
||||
{
|
||||
$('#upload-step2').slideUp();
|
||||
$('#upload-no-posts').slideDown();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
function sendNextPost()
|
||||
{
|
||||
var posts = $('#upload-step2 .post');
|
||||
@ -73,15 +89,12 @@ $(function()
|
||||
|
||||
var postDom = posts.first();
|
||||
var url = postDom.find('form').attr('action') + '?json';
|
||||
var file = postDom.data('file');
|
||||
var tags = postDom.find('[name=tags]').val();
|
||||
var safety = postDom.find('[name=safety]:checked').val();
|
||||
var source = postDom.find('[name=source]').val();
|
||||
var fd = new FormData();
|
||||
fd.append('file', file);
|
||||
fd.append('tags', tags);
|
||||
fd.append('safety', safety);
|
||||
fd.append('source', source);
|
||||
console.log(postDom.find('form').get(0));
|
||||
var fd = new FormData(postDom.find('form').get(0));
|
||||
|
||||
fd.append('file', postDom.data('file'));
|
||||
fd.append('url', postDom.data('url'));
|
||||
|
||||
|
||||
var ajaxData =
|
||||
{
|
||||
@ -103,7 +116,7 @@ $(function()
|
||||
}
|
||||
else
|
||||
{
|
||||
postDom.find('.alert').html(data['errorHtml']).slideDown();
|
||||
postDom.find('.alert').html(data['messageHtml']).slideDown();
|
||||
enableUpload();
|
||||
}
|
||||
}
|
||||
@ -145,51 +158,79 @@ $(function()
|
||||
|
||||
function handleFiles(files)
|
||||
{
|
||||
$('#upload-step1').fadeOut(function()
|
||||
handleInputs(files, function(postDom, file)
|
||||
{
|
||||
for (var i = 0; i < files.length; i ++)
|
||||
{
|
||||
var file = files[i];
|
||||
var postDom = $('#post-template').clone(true);
|
||||
postDom.find('form').submit(false);
|
||||
postDom.removeAttr('id');
|
||||
postDom.data('file', file);
|
||||
$('.file-name strong', postDom).text(file.name);
|
||||
$('.posts').append(postDom);
|
||||
|
||||
postDom.show();
|
||||
var tagItOptions =
|
||||
if (file.type.match('image.*'))
|
||||
{
|
||||
caseSensitive: true,
|
||||
availableTags: tags,
|
||||
placeholderText: $('.tags input').attr('placeholder')
|
||||
};
|
||||
$('.tags input', postDom).tagit(tagItOptions);
|
||||
|
||||
if (!file.type.match('image.*'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var img = postDom.find('img')
|
||||
var reader = new FileReader();
|
||||
reader.onload = (function(theFile, img)
|
||||
{
|
||||
return function(e)
|
||||
{
|
||||
/*img.css('max-width', img.css('width'));
|
||||
img.css('max-height', img.css('height'));
|
||||
img.css('width', 'auto');
|
||||
img.css('height', 'auto');*/
|
||||
img.css('background-image', 'none');
|
||||
img.attr('src', e.target.result);
|
||||
};
|
||||
})(file, img);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
$('#upload-step2').fadeIn(function()
|
||||
{
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleURLs(urls)
|
||||
{
|
||||
handleInputs(urls, function(postDom, url)
|
||||
{
|
||||
postDom.data('url', url);
|
||||
postDom.find('[name=source]').val(url);
|
||||
if (matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/))
|
||||
{
|
||||
postDom.find('.file-name strong').text(url);
|
||||
$.getJSON('http://gdata.youtube.com/feeds/api/videos/' + matches[1] + '?v=2&alt=jsonc', function(data)
|
||||
{
|
||||
postDom.find('.file-name strong')
|
||||
.text(data.data.title);
|
||||
postDom.find('img')
|
||||
.css('background-image', 'none')
|
||||
.attr('src', data.data.thumbnail.hqDefault);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
postDom.find('.file-name strong')
|
||||
.text(url);
|
||||
postDom.find('img')
|
||||
.css('background-image', 'none')
|
||||
.attr('src', url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleInputs(inputs, callback)
|
||||
{
|
||||
for (var i = 0; i < inputs.length; i ++)
|
||||
{
|
||||
var input = inputs[i];
|
||||
var postDom = $('#post-template').clone(true);
|
||||
postDom.find('form').submit(false);
|
||||
postDom.removeAttr('id');
|
||||
|
||||
$('.posts').append(postDom);
|
||||
|
||||
postDom.show();
|
||||
var tagItOptions = getTagItOptions();
|
||||
tagItOptions.availableTags = tags;
|
||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
||||
$('.tags input', postDom).tagit(tagItOptions);
|
||||
|
||||
callback(postDom, input);
|
||||
}
|
||||
if ($('.posts .post').length == 0)
|
||||
$('#upload-step2').fadeOut();
|
||||
else
|
||||
$('#upload-step2').fadeIn();
|
||||
}
|
||||
});
|
||||
|
27
scripts/find-posts.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
|
||||
function usage()
|
||||
{
|
||||
echo 'Usage: ' . basename(__FILE__);
|
||||
echo ' QUERY' . PHP_EOL;
|
||||
return true;
|
||||
}
|
||||
|
||||
array_shift($argv);
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
|
||||
$filesPath = rtrim(\Chibi\Registry::getConfig()->main->filesPath, DS);
|
||||
$query = array_shift($argv);
|
||||
$posts = Model_Post::getEntities($query, null, null);
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
echo implode("\t",
|
||||
[
|
||||
$post->id,
|
||||
$post->name,
|
||||
$filesPath . DS . $post->name,
|
||||
$post->mimeType,
|
||||
]). PHP_EOL;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
function usage()
|
||||
{
|
||||
@ -30,23 +31,23 @@ switch ($action)
|
||||
mkdir($dir, 0755, true);
|
||||
if (!is_dir($dir))
|
||||
die($dir . ' is not a dir' . PHP_EOL);
|
||||
$func = function($name) use ($dir)
|
||||
$func = function($name) use ($dir, $config)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
static $filesPath = null;
|
||||
if ($filesPath == null)
|
||||
$filesPath = configFactory()->main->filesPath;
|
||||
$filesPath = $config->main->filesPath;
|
||||
rename($filesPath . DS . $name, $dir . DS . $name);
|
||||
};
|
||||
break;
|
||||
|
||||
case '-purge':
|
||||
$func = function($name) use ($dir)
|
||||
$func = function($name) use ($dir, $config)
|
||||
{
|
||||
echo $name . PHP_EOL;
|
||||
static $filesPath = null;
|
||||
if ($filesPath == null)
|
||||
$filesPath = configFactory()->main->filesPath;
|
||||
$filesPath = $config->main->filesPath;
|
||||
unlink($filesPath . DS . $name);
|
||||
};
|
||||
break;
|
||||
@ -62,7 +63,6 @@ foreach (R::findAll('post') as $post)
|
||||
}
|
||||
$names = array_flip($names);
|
||||
|
||||
$config = configFactory();
|
||||
$filesPath = $config->main->filesPath;
|
||||
foreach (glob($filesPath . DS . '*') as $name)
|
||||
{
|
||||
|
47
scripts/process-old-users.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/core.php';
|
||||
|
||||
function usage()
|
||||
{
|
||||
echo 'Usage: ' . basename(__FILE__);
|
||||
echo ' -print|-purge';
|
||||
return true;
|
||||
}
|
||||
|
||||
array_shift($argv);
|
||||
if (empty($argv))
|
||||
usage() and die;
|
||||
|
||||
function printUser($user)
|
||||
{
|
||||
echo 'ID: ' . $user->id . PHP_EOL;
|
||||
echo 'Name: ' . $user->name . PHP_EOL;
|
||||
echo 'E-mail: ' . $user->email_unconfirmed . PHP_EOL;
|
||||
echo 'Date joined: ' . date('Y-m-d H:i:s', $user->join_date) . PHP_EOL;
|
||||
echo PHP_EOL;
|
||||
}
|
||||
|
||||
$action = array_shift($argv);
|
||||
switch ($action)
|
||||
{
|
||||
case '-print':
|
||||
$func = 'printUser';
|
||||
break;
|
||||
|
||||
case '-purge':
|
||||
$func = function($user)
|
||||
{
|
||||
printUser($user);
|
||||
R::trash($user);
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
die('Unknown action' . PHP_EOL);
|
||||
}
|
||||
|
||||
$rows = R::find('user', 'email_confirmed IS NULL AND DATETIME(join_date) < DATETIME("now", "-21 days")');
|
||||
foreach ($rows as $user)
|
||||
{
|
||||
$func($user);
|
||||
}
|
14
scripts/remove-letterbox.sh
Normal file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
process () {
|
||||
x="$1";
|
||||
echo "$x";
|
||||
convert "$x" -fuzz 5% -trim +repage tmp && mv tmp "$x"
|
||||
}
|
||||
|
||||
while read x; do
|
||||
process "$x";
|
||||
done
|
||||
for x in $@; do
|
||||
process "$x";
|
||||
done
|
@ -1,26 +1,6 @@
|
||||
<?php
|
||||
class Bootstrap
|
||||
{
|
||||
public function attachUser()
|
||||
{
|
||||
$this->context->loggedIn = false;
|
||||
if (isset($_SESSION['user-id']))
|
||||
{
|
||||
$this->context->user = R::findOne('user', 'id = ?', [$_SESSION['user-id']]);
|
||||
if (!empty($this->context->user))
|
||||
{
|
||||
$this->context->loggedIn = true;
|
||||
}
|
||||
}
|
||||
if (empty($this->context->user))
|
||||
{
|
||||
$dummy = R::dispense('user');
|
||||
$dummy->name = 'Anonymous';
|
||||
$dummy->access_rank = AccessRank::Anonymous;
|
||||
$this->context->user = $dummy;
|
||||
}
|
||||
}
|
||||
|
||||
public function workWrapper($workCallback)
|
||||
{
|
||||
$this->config->chibi->baseUrl = 'http://' . rtrim($_SERVER['HTTP_HOST'], '/') . '/';
|
||||
@ -37,16 +17,18 @@ class Bootstrap
|
||||
[
|
||||
'../lib/jquery/jquery.min.js',
|
||||
'../lib/jquery-ui/jquery-ui.min.js',
|
||||
'../lib/mousetrap/mousetrap.min.js',
|
||||
'core.js',
|
||||
];
|
||||
|
||||
$this->context->layoutName = isset($_GET['json'])
|
||||
$this->context->json = isset($_GET['json']);
|
||||
$this->context->layoutName = $this->context->json
|
||||
? 'layout-json'
|
||||
: 'layout-normal';
|
||||
$this->context->transport = new StdClass;
|
||||
$this->context->transport->success = null;
|
||||
StatusHelper::init();
|
||||
|
||||
$this->attachUser();
|
||||
AuthController::doLogIn();
|
||||
|
||||
if (empty($this->context->route))
|
||||
{
|
||||
@ -59,24 +41,28 @@ class Bootstrap
|
||||
{
|
||||
$workCallback();
|
||||
}
|
||||
catch (\Chibi\MissingViewFileException $e)
|
||||
{
|
||||
$this->context->json = true;
|
||||
$this->context->layoutName = 'layout-json';
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
catch (SimpleException $e)
|
||||
{
|
||||
$this->context->transport->errorMessage = rtrim($e->getMessage(), '.') . '.';
|
||||
$this->context->transport->errorHtml = TextHelper::parseMarkdown($this->context->transport->errorMessage, true);
|
||||
$this->context->transport->exception = $e;
|
||||
$this->context->transport->success = false;
|
||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
||||
if (!$this->context->handleExceptions)
|
||||
$this->context->viewName = 'error-simple';
|
||||
$this->context->viewName = 'message';
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->context->transport->errorMessage = rtrim($e->getMessage(), '.') . '.';
|
||||
$this->context->transport->errorHtml = TextHelper::parseMarkdown($this->context->transport->errorMessage, true);
|
||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
||||
$this->context->transport->exception = $e;
|
||||
$this->context->transport->success = false;
|
||||
$this->context->transport->queries = array_map(function($x) { return preg_replace('/\s+/', ' ', $x); }, queryLogger()->getLogs());
|
||||
$this->context->viewName = 'error-exception';
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
|
||||
AuthController::observeWorkFinish();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,54 @@
|
||||
<?php
|
||||
class AuthController
|
||||
{
|
||||
private static function redirectAfterLog()
|
||||
{
|
||||
if (isset($_SESSION['login-redirect-url']))
|
||||
{
|
||||
\Chibi\UrlHelper::forward($_SESSION['login-redirect-url']);
|
||||
unset($_SESSION['login-redirect-url']);
|
||||
return;
|
||||
}
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
}
|
||||
|
||||
public static function tryLogin($name, $password)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$context = \Chibi\Registry::getContext();
|
||||
|
||||
$dbUser = R::findOne('user', 'name = ?', [$name]);
|
||||
if ($dbUser === null)
|
||||
throw new SimpleException('Invalid username');
|
||||
|
||||
$passwordHash = Model_User::hashPassword($password, $dbUser->pass_salt);
|
||||
if ($passwordHash != $dbUser->pass_hash)
|
||||
throw new SimpleException('Invalid password');
|
||||
|
||||
if (!$dbUser->staff_confirmed and $config->registration->staffActivation)
|
||||
throw new SimpleException('Staff hasn\'t confirmed your registration yet');
|
||||
|
||||
if ($dbUser->banned)
|
||||
throw new SimpleException('You are banned');
|
||||
|
||||
if ($config->registration->needEmailForRegistering)
|
||||
PrivilegesHelper::confirmEmail($dbUser);
|
||||
|
||||
$context->user = $dbUser;
|
||||
self::doReLog();
|
||||
return $dbUser;
|
||||
}
|
||||
|
||||
public static function tryAutoLogin()
|
||||
{
|
||||
if (!isset($_COOKIE['auth']))
|
||||
return;
|
||||
|
||||
$token = TextHelper::decrypt($_COOKIE['auth']);
|
||||
list ($name, $password) = array_map('base64_decode', explode('|', $token));
|
||||
return self::tryLogin($name, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /auth/login
|
||||
*/
|
||||
@ -13,34 +61,23 @@ class AuthController
|
||||
//check if already logged in
|
||||
if ($this->context->loggedIn)
|
||||
{
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
self::redirectAfterLog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedName = InputHelper::get('name');
|
||||
$suppliedPassword = InputHelper::get('password');
|
||||
if ($suppliedName !== null and $suppliedPassword !== null)
|
||||
$dbUser = self::tryLogin($suppliedName, $suppliedPassword);
|
||||
|
||||
if (InputHelper::get('remember'))
|
||||
{
|
||||
$dbUser = R::findOne('user', 'name = ?', [$suppliedName]);
|
||||
if ($dbUser === null)
|
||||
throw new SimpleException('Invalid username');
|
||||
|
||||
$suppliedPasswordHash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
|
||||
if ($suppliedPasswordHash != $dbUser->pass_hash)
|
||||
throw new SimpleException('Invalid password');
|
||||
|
||||
if (!$dbUser->staff_confirmed and $this->config->registration->staffActivation)
|
||||
throw new SimpleException('Staff hasn\'t confirmed your registration yet');
|
||||
|
||||
if ($dbUser->banned)
|
||||
throw new SimpleException('You are banned');
|
||||
|
||||
if ($this->config->registration->needEmailForRegistering)
|
||||
PrivilegesHelper::confirmEmail($dbUser);
|
||||
|
||||
$_SESSION['user-id'] = $dbUser->id;
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
$this->context->transport->success = true;
|
||||
$token = implode('|', [base64_encode($suppliedName), base64_encode($suppliedPassword)]);
|
||||
setcookie('auth', TextHelper::encrypt($token), time() + 365 * 24 * 3600, '/');
|
||||
}
|
||||
StatusHelper::success();
|
||||
self::redirectAfterLog();
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,8 +87,65 @@ class AuthController
|
||||
public function logoutAction()
|
||||
{
|
||||
$this->context->viewName = null;
|
||||
$this->context->viewName = null;
|
||||
unset($_SESSION['user-id']);
|
||||
$this->context->layoutName = null;
|
||||
self::doLogOut();
|
||||
setcookie('auth', false, 0, '/');
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
}
|
||||
|
||||
public static function doLogOut()
|
||||
{
|
||||
unset($_SESSION['user']);
|
||||
}
|
||||
|
||||
public static function doLogIn()
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if (!isset($_SESSION['user']))
|
||||
{
|
||||
if (!empty($context->user) and $context->user->id)
|
||||
{
|
||||
$dbUser = R::findOne('user', 'id = ?', [$context->user->id]);
|
||||
$_SESSION['user'] = serialize($dbUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
$dummy = R::dispense('user');
|
||||
$dummy->name = 'Anonymous';
|
||||
$dummy->access_rank = AccessRank::Anonymous;
|
||||
$dummy->anonymous = true;
|
||||
$_SESSION['user'] = serialize($dummy);
|
||||
}
|
||||
}
|
||||
$context->user = unserialize($_SESSION['user']);
|
||||
$context->loggedIn = $context->user->anonymous ? false : true;
|
||||
if (!$context->loggedIn)
|
||||
{
|
||||
try
|
||||
{
|
||||
self::tryAutoLogin();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function doReLog()
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if ($context->user !== null)
|
||||
$_SESSION['user'] = serialize($context->user);
|
||||
self::doLogIn();
|
||||
}
|
||||
|
||||
public static function observeWorkFinish()
|
||||
{
|
||||
if (strpos(\Chibi\HeadersHelper::get('Content-Type'), 'text/html') === false)
|
||||
return;
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if ($context->route->simpleControllerName == 'auth')
|
||||
return;
|
||||
$_SESSION['login-redirect-url'] = $context->query;
|
||||
}
|
||||
}
|
||||
|
@ -11,35 +11,20 @@ class CommentController
|
||||
$this->context->stylesheets []= 'post-small.css';
|
||||
$this->context->stylesheets []= 'comment-list.css';
|
||||
$this->context->stylesheets []= 'comment-small.css';
|
||||
$this->context->subTitle = 'comments';
|
||||
if ($this->config->browsing->endlessScrolling)
|
||||
$this->context->stylesheets []= 'paginator.css';
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
|
||||
$page = intval($page);
|
||||
$commentsPerPage = intval($this->config->comments->commentsPerPage);
|
||||
$this->context->subTitle = 'comments';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListComments);
|
||||
|
||||
$buildDbQuery = function($dbQuery)
|
||||
{
|
||||
$dbQuery->from('comment');
|
||||
$dbQuery->orderBy('comment_date')->desc();
|
||||
};
|
||||
|
||||
$countDbQuery = R::$f->begin();
|
||||
$countDbQuery->select('COUNT(1)')->as('count');
|
||||
$buildDbQuery($countDbQuery);
|
||||
$commentCount = intval($countDbQuery->get('row')['count']);
|
||||
$commentCount = Model_Comment::getEntityCount(null);
|
||||
$pageCount = ceil($commentCount / $commentsPerPage);
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$comments = Model_Comment::getEntities(null, $commentsPerPage, $page);
|
||||
|
||||
$searchDbQuery = R::$f->begin();
|
||||
$searchDbQuery->select('comment.*');
|
||||
$buildDbQuery($searchDbQuery);
|
||||
$searchDbQuery->limit('?')->put($commentsPerPage);
|
||||
$searchDbQuery->offset('?')->put(($page - 1) * $commentsPerPage);
|
||||
|
||||
$comments = $searchDbQuery->get();
|
||||
$comments = R::convertToBeans('comment', $comments);
|
||||
R::preload($comments, ['commenter' => 'user', 'post', 'post.uploader' => 'user']);
|
||||
$this->context->postGroups = true;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
@ -65,19 +50,24 @@ class CommentController
|
||||
|
||||
$post = Model_Post::locate($postId);
|
||||
|
||||
$text = InputHelper::get('text');
|
||||
if (!empty($text))
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$text = InputHelper::get('text');
|
||||
$text = Model_Comment::validateText($text);
|
||||
|
||||
$comment = R::dispense('comment');
|
||||
$comment->post = $post;
|
||||
if ($this->context->loggedIn)
|
||||
$comment->commenter = $this->context->user;
|
||||
$comment->comment_date = time();
|
||||
$comment->text = $text;
|
||||
if (InputHelper::get('sender') != 'preview')
|
||||
{
|
||||
R::store($comment);
|
||||
LogHelper::logEvent('comment-add', '{user} commented on {post}', ['post' => TextHelper::reprPost($post->id)]);
|
||||
}
|
||||
$this->context->transport->textPreview = $comment->getText();
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +82,8 @@ class CommentController
|
||||
$comment = Model_Comment::locate($id);
|
||||
R::preload($comment, ['commenter' => 'user']);
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($comment->commenter));
|
||||
LogHelper::logEvent('comment-del', '{user} removed comment from {post}', ['post' => TextHelper::reprPost($comment->post)]);
|
||||
R::trash($comment);
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class IndexController
|
||||
$this->context->stylesheets []= 'index-index.css';
|
||||
$this->context->transport->postCount = R::$f->begin()->select('count(1)')->as('count')->from('post')->get('row')['count'];
|
||||
|
||||
$featuredPostRotationTime = $this->config->main->featuredPostMaxDays * 24 * 3600;
|
||||
$featuredPostRotationTime = $this->config->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$featuredPostId = Model_Property::get(Model_Property::FeaturedPostId);
|
||||
$featuredPostUserId = Model_Property::get(Model_Property::FeaturedPostUserId);
|
||||
@ -41,6 +41,7 @@ class IndexController
|
||||
$this->context->featuredPost = $featuredPost;
|
||||
$this->context->featuredPostUser = $featuredPostUser;
|
||||
$this->context->featuredPostDate = $featuredPostDate;
|
||||
$this->context->pageThumb = \Chibi\UrlHelper::route('post', 'thumb', ['name' => $featuredPost->name]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,6 +50,7 @@ class IndexController
|
||||
*/
|
||||
public function helpAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'index-help.css';
|
||||
$this->context->subTitle = 'help';
|
||||
}
|
||||
}
|
||||
|
67
src/Controllers/LogController.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
class LogController
|
||||
{
|
||||
/**
|
||||
* @route /logs
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$this->context->subTitle = 'latest logs';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListLogs);
|
||||
|
||||
$path = $this->context->rootDir . DS . $this->config->main->logsPath;
|
||||
$path = TextHelper::cleanPath($path);
|
||||
|
||||
$logs = [];
|
||||
foreach (glob($path . DS . '*.log') as $log)
|
||||
$logs []= basename($log);
|
||||
|
||||
usort($logs, function($a, $b)
|
||||
{
|
||||
return strnatcasecmp($b, $a); //reverse natcasesort
|
||||
});
|
||||
|
||||
$this->context->transport->logs = $logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /log/{name}
|
||||
* @validate name [0-9a-zA-Z._-]+
|
||||
*/
|
||||
public function viewAction($name)
|
||||
{
|
||||
$this->context->subTitle = 'logs (' . $name . ')';
|
||||
$this->context->stylesheets []= 'logs.css';
|
||||
$this->context->scripts []= 'logs.js';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewLog);
|
||||
|
||||
$name = str_replace(['/', '\\'], '', $name); //paranoia mode
|
||||
$path = $this->context->rootDir . DS . $this->config->main->logsPath . DS . $name;
|
||||
$path = TextHelper::cleanPath($path);
|
||||
if (!file_exists($path))
|
||||
throw new SimpleException('Specified log doesn\'t exist');
|
||||
|
||||
$filter = InputHelper::get('filter');
|
||||
|
||||
$lines = file_get_contents($path);
|
||||
$lines = explode(PHP_EOL, str_replace(["\r", "\n"], PHP_EOL, $lines));
|
||||
$lines = array_reverse($lines);
|
||||
|
||||
if (!empty($filter))
|
||||
$lines = array_filter($lines, function($line) use ($filter) { return stripos($line, $filter) !== false; });
|
||||
|
||||
//stylize important lines
|
||||
foreach ($lines as &$line)
|
||||
if (strpos($line, 'flag') !== false)
|
||||
$line = '**' . $line . '**';
|
||||
unset($line);
|
||||
|
||||
$lines = join(PHP_EOL, $lines);
|
||||
$lines = TextHelper::parseMarkdown($lines);
|
||||
$lines = trim($lines);
|
||||
|
||||
$this->context->transport->filter = $filter;
|
||||
$this->context->transport->name = $name;
|
||||
$this->context->transport->log = $lines;
|
||||
}
|
||||
}
|
@ -7,29 +7,31 @@ class TagController
|
||||
public function listAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListTags);
|
||||
$suppliedFilter = InputHelper::get('filter');
|
||||
|
||||
$dbQuery = R::$f->begin();
|
||||
$dbQuery->select('tag.name, COUNT(1) AS count');
|
||||
$dbQuery->from('tag');
|
||||
$dbQuery->innerJoin('post_tag');
|
||||
$dbQuery->on('tag.id = post_tag.tag_id');
|
||||
$dbQuery->groupBy('tag.id');
|
||||
$dbQuery->orderBy('LOWER(tag.name)')->asc();
|
||||
$rows = $dbQuery->get();
|
||||
|
||||
$tags = [];
|
||||
$tagDistribution = [];
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$tags []= strval($row['name']);
|
||||
$tagDistribution[$row['name']] = intval($row['count']);
|
||||
}
|
||||
|
||||
$tags = Model_Tag::getEntitiesRows($suppliedFilter, null, null);
|
||||
$this->context->transport->tags = $tags;
|
||||
$this->context->transport->tagDistribution = $tagDistribution;
|
||||
|
||||
if ($this->context->json)
|
||||
{
|
||||
$this->context->transport->tags = array_values(array_map(function($tag) {
|
||||
return ['name' => $tag['name'], 'count' => $tag['post_count']];
|
||||
}, $this->context->transport->tags));
|
||||
usort($this->context->transport->tags, function($a, $b) {
|
||||
return $a['count'] > $b['count'] ? -1 : 1;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
uasort($this->context->transport->tags, function($a, $b) {
|
||||
return strnatcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,9 +39,24 @@ class TagController
|
||||
*/
|
||||
public function mergeAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
$sourceTag = Model_Tag::locate(InputHelper::get('source-tag'));
|
||||
$targetTag = Model_Tag::locate(InputHelper::get('target-tag'));
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedSourceTag = InputHelper::get('source-tag');
|
||||
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
|
||||
$suppliedTargetTag = InputHelper::get('target-tag');
|
||||
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
|
||||
$targetTag = Model_Tag::locate($suppliedTargetTag);
|
||||
|
||||
if ($sourceTag->id == $targetTag->id)
|
||||
throw new SimpleException('Source and target tag are the same');
|
||||
|
||||
R::preload($sourceTag, 'post');
|
||||
|
||||
@ -52,8 +69,11 @@ class TagController
|
||||
R::store($post);
|
||||
}
|
||||
R::trash($sourceTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
$this->view->context->success = true;
|
||||
LogHelper::logEvent('tag-merge', '{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,19 +81,52 @@ class TagController
|
||||
*/
|
||||
public function renameAction()
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedSourceTag = InputHelper::get('source-tag');
|
||||
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
|
||||
|
||||
$suppliedTargetTag = InputHelper::get('target-tag');
|
||||
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
|
||||
$targetTag = Model_Tag::locate($suppliedTargetTag, false);
|
||||
if ($targetTag)
|
||||
throw new SimpleException('Target tag already exists');
|
||||
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
$sourceTag->name = $suppliedTargetTag;
|
||||
R::store($sourceTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
$this->context->transport->success = true;
|
||||
LogHelper::logEvent('tag-rename', '{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /mass-tag-redirect
|
||||
*/
|
||||
public function massTagRedirectAction()
|
||||
{
|
||||
$this->context->stylesheets []= 'tag-list.css';
|
||||
$this->context->stylesheets []= 'tabs.css';
|
||||
$this->context->subTitle = 'tags';
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedQuery = InputHelper::get('query');
|
||||
if (!$suppliedQuery)
|
||||
$suppliedQuery = ' ';
|
||||
$suppliedTag = InputHelper::get('tag');
|
||||
$suppliedTag = Model_Tag::validateTag($suppliedTag);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', ['source' => 'mass-tag', 'query' => $suppliedQuery, 'additionalInfo' => $suppliedTag]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,54 @@
|
||||
<?php
|
||||
class UserController
|
||||
{
|
||||
private static function sendEmailConfirmation(&$user)
|
||||
private function loadUserView($user)
|
||||
{
|
||||
$regConfig = \Chibi\Registry::getConfig()->registration;
|
||||
|
||||
if (!$regConfig->confirmationEmailEnabled)
|
||||
{
|
||||
$user->email_confirmed = $user->email_unconfirmed;
|
||||
$user->email_unconfirmed = null;
|
||||
return;
|
||||
$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,
|
||||
$subject,
|
||||
$senderName,
|
||||
$senderEmail,
|
||||
$recipientEmail,
|
||||
$linkActionName)
|
||||
{
|
||||
//prepare unique user token
|
||||
do
|
||||
{
|
||||
$tokenText = md5(mt_rand() . uniqid());
|
||||
}
|
||||
while (R::findOne('usertoken', 'token = ?', [$tokenText]) !== null);
|
||||
$token = R::dispense('usertoken');
|
||||
$token->user = $user;
|
||||
$token->token = $tokenText;
|
||||
$token->used = false;
|
||||
$token->expires = null;
|
||||
R::store($token);
|
||||
|
||||
\Chibi\Registry::getContext()->mailSent = true;
|
||||
$tokens = [];
|
||||
$tokens['host'] = $_SERVER['HTTP_HOST'];
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', 'activation', ['token' => $user->email_token]);
|
||||
$tokens['token'] = $tokenText;
|
||||
if ($linkActionName !== null)
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', $linkActionName, ['token' => $tokenText]);
|
||||
|
||||
$body = wordwrap(TextHelper::replaceTokens($regConfig->confirmationEmailBody, $tokens), 70);
|
||||
$subject = TextHelper::replaceTokens($regConfig->confirmationEmailSubject, $tokens);
|
||||
$senderName = TextHelper::replaceTokens($regConfig->confirmationEmailSenderName, $tokens);
|
||||
$senderEmail = TextHelper::replaceTokens($regConfig->confirmationEmailSenderEmail, $tokens);
|
||||
$recipientEmail = $user->email_unconfirmed;
|
||||
$body = wordwrap(TextHelper::replaceTokens($body, $tokens), 70);
|
||||
$subject = TextHelper::replaceTokens($subject, $tokens);
|
||||
$senderName = TextHelper::replaceTokens($senderName, $tokens);
|
||||
$senderEmail = TextHelper::replaceTokens($senderEmail, $tokens);
|
||||
|
||||
if (empty($recipientEmail))
|
||||
throw new SimpleException('Destination e-mail address was not found');
|
||||
|
||||
$headers = [];
|
||||
$headers []= sprintf('MIME-Version: 1.0');
|
||||
@ -35,8 +62,44 @@ class UserController
|
||||
$headers []= sprintf('Content-Type: text/plain; charset=utf-8', $subject);
|
||||
$headers []= sprintf('X-Mailer: PHP/%s', phpversion());
|
||||
$headers []= sprintf('X-Originating-IP: %s', $_SERVER['SERVER_ADDR']);
|
||||
$subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
||||
mail($recipientEmail, $subject, $body, implode("\r\n", $headers), '-f' . $senderEmail);
|
||||
$encodedSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
||||
mail($recipientEmail, $encodedSubject, $body, implode("\r\n", $headers), '-f' . $senderEmail);
|
||||
|
||||
LogHelper::logEvent('mail', 'Sending e-mail with subject "{subject}" to {mail}', ['subject' => $subject, 'mail' => $recipientEmail]);
|
||||
}
|
||||
|
||||
private static function sendEmailChangeConfirmation($user)
|
||||
{
|
||||
$regConfig = \Chibi\Registry::getConfig()->registration;
|
||||
if (!$regConfig->confirmationEmailEnabled)
|
||||
{
|
||||
$user->email_confirmed = $user->email_unconfirmed;
|
||||
$user->email_unconfirmed = null;
|
||||
return;
|
||||
}
|
||||
|
||||
return self::sendTokenizedEmail(
|
||||
$user,
|
||||
$regConfig->confirmationEmailBody,
|
||||
$regConfig->confirmationEmailSubject,
|
||||
$regConfig->confirmationEmailSenderName,
|
||||
$regConfig->confirmationEmailSenderEmail,
|
||||
$user->email_unconfirmed,
|
||||
'activation');
|
||||
}
|
||||
|
||||
private static function sendPasswordResetConfirmation($user)
|
||||
{
|
||||
$regConfig = \Chibi\Registry::getConfig()->registration;
|
||||
|
||||
return self::sendTokenizedEmail(
|
||||
$user,
|
||||
$regConfig->passwordResetEmailBody,
|
||||
$regConfig->passwordResetEmailSubject,
|
||||
$regConfig->passwordResetEmailSenderName,
|
||||
$regConfig->passwordResetEmailSenderEmail,
|
||||
$user->email_confirmed,
|
||||
'password-reset');
|
||||
}
|
||||
|
||||
|
||||
@ -53,61 +116,24 @@ class UserController
|
||||
{
|
||||
$this->context->stylesheets []= 'user-list.css';
|
||||
$this->context->stylesheets []= 'paginator.css';
|
||||
if ($this->config->browsing->endlessScrolling)
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
|
||||
$page = intval($page);
|
||||
$usersPerPage = intval($this->config->browsing->usersPerPage);
|
||||
$this->context->subTitle = 'users';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
||||
|
||||
if ($sortStyle == '' or $sortStyle == 'alpha')
|
||||
$sortStyle = 'alpha,asc';
|
||||
if ($sortStyle == 'date')
|
||||
$sortStyle = 'date,asc';
|
||||
|
||||
$buildDbQuery = function($dbQuery, $sortStyle)
|
||||
{
|
||||
$dbQuery->from('user');
|
||||
$page = intval($page);
|
||||
$usersPerPage = intval($this->config->browsing->usersPerPage);
|
||||
$this->context->subTitle = 'users';
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
||||
|
||||
switch ($sortStyle)
|
||||
{
|
||||
case 'alpha,asc':
|
||||
$dbQuery->orderBy('name')->asc();
|
||||
break;
|
||||
case 'alpha,desc':
|
||||
$dbQuery->orderBy('name')->desc();
|
||||
break;
|
||||
case 'date,asc':
|
||||
$dbQuery->orderBy('join_date')->asc();
|
||||
break;
|
||||
case 'date,desc':
|
||||
$dbQuery->orderBy('join_date')->desc();
|
||||
break;
|
||||
case 'pending':
|
||||
$dbQuery->where('staff_confirmed IS NULL');
|
||||
$dbQuery->or('staff_confirmed = 0');
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown sort style');
|
||||
}
|
||||
};
|
||||
|
||||
$countDbQuery = R::$f->begin();
|
||||
$countDbQuery->select('COUNT(1)')->as('count');
|
||||
$buildDbQuery($countDbQuery, $sortStyle);
|
||||
$userCount = intval($countDbQuery->get('row')['count']);
|
||||
$userCount = Model_User::getEntityCount($sortStyle);
|
||||
$pageCount = ceil($userCount / $usersPerPage);
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$users = Model_User::getEntities($sortStyle, $usersPerPage, $page);
|
||||
|
||||
$searchDbQuery = R::$f->begin();
|
||||
$searchDbQuery->select('user.*');
|
||||
$buildDbQuery($searchDbQuery, $sortStyle);
|
||||
$searchDbQuery->limit('?')->put($usersPerPage);
|
||||
$searchDbQuery->offset('?')->put(($page - 1) * $usersPerPage);
|
||||
|
||||
$users = $searchDbQuery->get();
|
||||
$users = R::convertToBeans('user', $users);
|
||||
$this->context->sortStyle = $sortStyle;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
@ -120,6 +146,32 @@ class UserController
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /user/{name}/flag
|
||||
* @validate name [^\/]+
|
||||
*/
|
||||
public function flagAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagUser);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$key = TextHelper::reprUser($user);
|
||||
|
||||
$flagged = SessionHelper::get('flagged', []);
|
||||
if (in_array($key, $flagged))
|
||||
throw new SimpleException('You already flagged this user');
|
||||
$flagged []= $key;
|
||||
SessionHelper::set('flagged', $flagged);
|
||||
|
||||
LogHelper::logEvent('user-flag', '{user} flagged {subject} for moderator attention', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /user/{name}/ban
|
||||
* @validate name [^\/]+
|
||||
@ -128,10 +180,18 @@ class UserController
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = true;
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('ban', '{user} banned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{name}/unban
|
||||
@ -141,10 +201,18 @@ class UserController
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = false;
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('unban', '{user} unbanned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /post/{name}/accept-registration
|
||||
@ -154,11 +222,14 @@ class UserController
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::AcceptUserRegistration);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->staff_confirmed = true;
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
LogHelper::logEvent('reg-accept', '{user} confirmed account for {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -172,23 +243,21 @@ 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 []= 'user-view.css';
|
||||
$this->context->subTitle = $name;
|
||||
|
||||
$this->context->suppliedCurrentPassword = $suppliedCurrentPassword = InputHelper::get('current-password');
|
||||
|
||||
if (InputHelper::get('remove'))
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = $user->name;
|
||||
if ($this->context->user->id == $user->id)
|
||||
{
|
||||
$suppliedPasswordHash = Model_User::hashPassword($suppliedCurrentPassword, $user->pass_salt);
|
||||
if ($suppliedPasswordHash != $user->pass_hash)
|
||||
throw new SimpleException('Must supply valid password');
|
||||
}
|
||||
R::trashAll(R::find('postscore', 'user_id = ?', [$user->id]));
|
||||
foreach ($user->alias('commenter')->ownComment as $comment)
|
||||
{
|
||||
$comment->commenter = null;
|
||||
@ -200,10 +269,47 @@ class UserController
|
||||
R::store($post);
|
||||
}
|
||||
$user->ownFavoritee = [];
|
||||
if ($user->id == $this->context->user->id)
|
||||
AuthController::doLogOut();
|
||||
R::store($user);
|
||||
R::trash($user);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
|
||||
$this->context->transport->success = true;
|
||||
LogHelper::logEvent('user-del', '{user} removed account for {subject}', ['subject' => TextHelper::reprUser($name)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /user/{name}/settings
|
||||
* @validate name [^\/]+
|
||||
*/
|
||||
public function settingsAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
$this->loadUserView($user);
|
||||
$this->context->transport->tab = 'settings';
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedSafety = InputHelper::get('safety');
|
||||
if (!is_array($suppliedSafety))
|
||||
$suppliedSafety = [];
|
||||
foreach (PostSafety::getAll() as $safety)
|
||||
$user->enableSafety($safety, in_array($safety, $suppliedSafety));
|
||||
|
||||
$user->enableEndlessScrolling(InputHelper::get('endless-scrolling'));
|
||||
|
||||
R::store($user);
|
||||
if ($user->id == $this->context->user->id)
|
||||
$this->context->user = $user;
|
||||
AuthController::doReLog();
|
||||
StatusHelper::success('Browsing settings updated!');
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,17 +323,11 @@ class UserController
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
$user = Model_User::locate($name);
|
||||
$edited = false;
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
$this->context->handleExceptions = true;
|
||||
$this->context->transport->user = $user;
|
||||
$this->loadUserView($user);
|
||||
$this->context->transport->tab = 'edit';
|
||||
$this->context->viewName = 'user-view';
|
||||
$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');
|
||||
@ -237,12 +337,18 @@ class UserController
|
||||
$this->context->suppliedAccessRank = $suppliedAccessRank = InputHelper::get('access-rank');
|
||||
$currentPasswordHash = $user->pass_hash;
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$confirmMail = false;
|
||||
LogHelper::bufferChanges();
|
||||
|
||||
if ($suppliedName != '' and $suppliedName != $user->name)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$suppliedName = Model_User::validateUserName($suppliedName);
|
||||
$oldName = $user->name;
|
||||
$user->name = $suppliedName;
|
||||
$edited = true;
|
||||
LogHelper::logEvent('user-edit', '{user} renamed {old} to {new}', ['old' => TextHelper::reprUser($oldName), 'new' => TextHelper::reprUser($suppliedName)]);
|
||||
}
|
||||
|
||||
if ($suppliedPassword1 != '')
|
||||
@ -252,7 +358,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);
|
||||
$edited = true;
|
||||
LogHelper::logEvent('user-edit', '{user} changed password for {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
}
|
||||
|
||||
if ($suppliedEmail != '' and $suppliedEmail != $user->email_confirmed)
|
||||
@ -263,13 +369,15 @@ class UserController
|
||||
{
|
||||
$user->email_unconfirmed = $suppliedEmail;
|
||||
if (!empty($user->email_unconfirmed))
|
||||
self::sendEmailConfirmation($user);
|
||||
$confirmMail = true;
|
||||
LogHelper::logEvent('user-edit', '{user} changed e-mail to {mail}', ['mail' => $suppliedEmail]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$user->email_unconfirmed = null;
|
||||
$user->email_confirmed = $suppliedEmail;
|
||||
LogHelper::logEvent('user-edit', '{user} changed e-mail for {subject} to {mail}', ['subject' => TextHelper::reprUser($user), 'mail' => $suppliedEmail]);
|
||||
}
|
||||
$edited = true;
|
||||
}
|
||||
|
||||
if ($suppliedAccessRank != '' and $suppliedAccessRank != $user->access_rank)
|
||||
@ -277,11 +385,9 @@ class UserController
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$suppliedAccessRank = Model_User::validateAccessRank($suppliedAccessRank);
|
||||
$user->access_rank = $suppliedAccessRank;
|
||||
$edited = true;
|
||||
LogHelper::logEvent('user-edit', '{user} changed access rank for {subject} to {rank}', ['subject' => TextHelper::reprUser($user), 'rank' => AccessRank::toString($suppliedAccessRank)]);
|
||||
}
|
||||
|
||||
if ($edited)
|
||||
{
|
||||
if ($this->context->user->id == $user->id)
|
||||
{
|
||||
$suppliedPasswordHash = Model_User::hashPassword($suppliedCurrentPassword, $user->pass_salt);
|
||||
@ -289,9 +395,16 @@ class UserController
|
||||
throw new SimpleException('Must supply valid current password');
|
||||
}
|
||||
R::store($user);
|
||||
$this->context->transport->success = true;
|
||||
}
|
||||
|
||||
if ($confirmMail)
|
||||
self::sendEmailChangeConfirmation($user);
|
||||
|
||||
LogHelper::flush();
|
||||
$message = 'Account settings updated!';
|
||||
if ($confirmMail)
|
||||
$message .= ' You will be sent an e-mail address confirmation message soon.';
|
||||
StatusHelper::success($message);
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
@ -319,76 +432,26 @@ class UserController
|
||||
$page = 1;
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$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';
|
||||
if ($this->config->browsing->endlessScrolling)
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
$this->context->scripts []= 'paginator-endless.js';
|
||||
$this->context->subTitle = $name;
|
||||
|
||||
$buildDbQuery = function($dbQuery, $user, $tab)
|
||||
{
|
||||
$dbQuery->from('post');
|
||||
$query = '';
|
||||
if ($tab == 'uploads')
|
||||
$query = 'submit:' . $user->name;
|
||||
elseif ($tab == 'favs')
|
||||
$query = 'fav:' . $user->name;
|
||||
else
|
||||
throw new SimpleException('Wrong tab');
|
||||
|
||||
|
||||
/* safety */
|
||||
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
|
||||
{
|
||||
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
|
||||
$this->context->user->hasEnabledSafety($safety);
|
||||
});
|
||||
$dbQuery->where('safety IN (' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
|
||||
|
||||
/* hidden */
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->andNot('hidden');
|
||||
|
||||
|
||||
/* tab */
|
||||
switch ($tab)
|
||||
{
|
||||
case 'uploads':
|
||||
$dbQuery
|
||||
->and('uploader_id = ?')
|
||||
->put($user->id);
|
||||
break;
|
||||
case 'favs':
|
||||
$dbQuery
|
||||
->and()
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('favoritee')
|
||||
->where('post_id = post.id')
|
||||
->and('favoritee.user_id = ?')
|
||||
->put($user->id)
|
||||
->close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$countDbQuery = R::$f->begin()->select('COUNT(*)')->as('count');
|
||||
$buildDbQuery($countDbQuery, $user, $tab);
|
||||
$postCount = intval($countDbQuery->get('row')['count']);
|
||||
$postCount = Model_Post::getEntityCount($query);
|
||||
$pageCount = ceil($postCount / $postsPerPage);
|
||||
$page = max(1, min($pageCount, $page));
|
||||
$posts = Model_Post::getEntities($query, $postsPerPage, $page);
|
||||
|
||||
$searchDbQuery = R::$f->begin()->select('*');
|
||||
$buildDbQuery($searchDbQuery, $user, $tab);
|
||||
$searchDbQuery->orderBy('id DESC')
|
||||
->limit('?')
|
||||
->put($postsPerPage)
|
||||
->offset('?')
|
||||
->put(($page - 1) * $postsPerPage);
|
||||
|
||||
$posts = $searchDbQuery->get();
|
||||
$posts = R::convertToBeans('post', $posts);
|
||||
R::preload($posts, ['uploader' => 'user']);
|
||||
$this->context->transport->user = $user;
|
||||
$this->context->transport->tab = $tab;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
@ -405,8 +468,7 @@ class UserController
|
||||
*/
|
||||
public function toggleSafetyAction($safety)
|
||||
{
|
||||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($this->context->user));
|
||||
|
||||
if (!in_array($safety, PostSafety::getAll()))
|
||||
throw new SimpleExcetpion('Invalid safety');
|
||||
@ -414,9 +476,11 @@ class UserController
|
||||
$this->context->user->enableSafety($safety,
|
||||
!$this->context->user->hasEnabledSafety($safety));
|
||||
|
||||
AuthController::doReLog();
|
||||
if (!$this->context->user->anonymous)
|
||||
R::store($this->context->user);
|
||||
|
||||
$this->context->transport->success = true;
|
||||
StatusHelper::success();
|
||||
}
|
||||
|
||||
|
||||
@ -446,7 +510,7 @@ class UserController
|
||||
$this->context->suppliedPassword2 = $suppliedPassword2;
|
||||
$this->context->suppliedEmail = $suppliedEmail;
|
||||
|
||||
if ($suppliedName !== null)
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedName = Model_User::validateUserName($suppliedName);
|
||||
|
||||
@ -465,19 +529,13 @@ class UserController
|
||||
$dbUser->pass_hash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
|
||||
$dbUser->email_unconfirmed = $suppliedEmail;
|
||||
|
||||
//prepare unique registration token
|
||||
do
|
||||
{
|
||||
$emailToken = md5(mt_rand() . uniqid());
|
||||
}
|
||||
while (R::findOne('user', 'email_token = ?', [$emailToken]) !== null);
|
||||
$dbUser->email_token = $emailToken;
|
||||
|
||||
$dbUser->join_date = time();
|
||||
if (R::findOne('user') === null)
|
||||
{
|
||||
//very first user
|
||||
$dbUser->access_rank = AccessRank::Admin;
|
||||
$dbUser->staff_confirmed = true;
|
||||
$dbUser->email_unconfirmed = null;
|
||||
$dbUser->email_confirmed = $suppliedEmail;
|
||||
}
|
||||
else
|
||||
@ -485,18 +543,31 @@ class UserController
|
||||
$dbUser->access_rank = AccessRank::Registered;
|
||||
$dbUser->staff_confirmed = false;
|
||||
$dbUser->staff_confirmed = null;
|
||||
if (!empty($dbUser->email_unconfirmed))
|
||||
self::sendEmailConfirmation($dbUser);
|
||||
}
|
||||
|
||||
//save the user to db if everything went okay
|
||||
R::store($dbUser);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
if (!empty($dbUser->email_unconfirmed))
|
||||
self::sendEmailChangeConfirmation($dbUser);
|
||||
|
||||
$message = 'Congratulations, your account was created.';
|
||||
if (!empty($this->context->mailSent))
|
||||
{
|
||||
$message .= ' Please wait for activation e-mail.';
|
||||
if ($this->config->registration->staffActivation)
|
||||
$message .= ' After this, your registration must be confirmed by staff.';
|
||||
}
|
||||
elseif ($this->config->registration->staffActivation)
|
||||
$message .= ' Your registration must be now confirmed by staff.';
|
||||
|
||||
LogHelper::logEvent('user-reg', '{subject} just signed up', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
StatusHelper::success($message);
|
||||
|
||||
if (!$this->config->registration->needEmailForRegistering and !$this->config->registration->staffActivation)
|
||||
{
|
||||
$_SESSION['user-id'] = $dbUser->id;
|
||||
\Chibi\Registry::getBootstrap()->attachUser();
|
||||
$this->context->user = $dbUser;
|
||||
AuthController::doReLog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -509,26 +580,108 @@ class UserController
|
||||
public function activationAction($token)
|
||||
{
|
||||
$this->context->subTitle = 'account activation';
|
||||
$this->context->viewName = 'message';
|
||||
|
||||
if (empty($token))
|
||||
throw new SimpleException('Invalid activation token');
|
||||
|
||||
$dbUser = R::findOne('user', 'email_token = ?', [$token]);
|
||||
if ($dbUser === null)
|
||||
throw new SimpleException('No user with such activation token');
|
||||
|
||||
if (!$dbUser->email_unconfirmed)
|
||||
throw new SimpleException('This user was already activated');
|
||||
$dbToken = Model_Token::locate($token);
|
||||
|
||||
$dbUser = $dbToken->user;
|
||||
$dbUser->email_confirmed = $dbUser->email_unconfirmed;
|
||||
$dbUser->email_unconfirmed = null;
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
R::store($dbUser);
|
||||
$this->context->transport->success = true;
|
||||
|
||||
LogHelper::logEvent('user-activation', '{subject} just activated account', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
$message = 'Activation completed successfully.';
|
||||
if ($this->config->registration->staffActivation)
|
||||
$message .= ' However, your account still must be confirmed by staff.';
|
||||
StatusHelper::success($message);
|
||||
|
||||
if (!$this->config->registration->staffActivation)
|
||||
{
|
||||
$_SESSION['user-id'] = $dbUser->id;
|
||||
\Chibi\Registry::getBootstrap()->attachUser();
|
||||
$this->context->user = $dbUser;
|
||||
AuthController::doReLog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /password-reset/{token}
|
||||
*/
|
||||
public function passwordResetAction($token)
|
||||
{
|
||||
$this->context->subTitle = 'password reset';
|
||||
$this->context->viewName = 'message';
|
||||
|
||||
$dbToken = Model_Token::locate($token);
|
||||
|
||||
$alphabet = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
|
||||
$randomPassword = join('', array_map(function($x) use ($alphabet)
|
||||
{
|
||||
return $alphabet[$x];
|
||||
}, array_rand($alphabet, 8)));
|
||||
|
||||
$dbUser = $dbToken->user;
|
||||
$dbUser->pass_hash = Model_User::hashPassword($randomPassword, $dbUser->pass_salt);
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
R::store($dbUser);
|
||||
|
||||
LogHelper::logEvent('user-pass-reset', '{subject} just reset password', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
$message = 'Password reset successful. Your new password is **' . $randomPassword . '**.';
|
||||
StatusHelper::success($message);
|
||||
|
||||
$this->context->user = $dbUser;
|
||||
AuthController::doReLog();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @route /password-reset-proxy
|
||||
*/
|
||||
public function passwordResetProxyAction()
|
||||
{
|
||||
$this->context->subTtile = 'password reset';
|
||||
$this->context->viewName = 'user-select';
|
||||
$this->context->stylesheets []= 'auth.css';
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = InputHelper::get('name');
|
||||
$user = Model_User::locate($name);
|
||||
if (empty($user->email_confirmed))
|
||||
throw new SimpleException('This user has no e-mail confirmed; password reset cannot proceed');
|
||||
|
||||
self::sendPasswordResetConfirmation($user);
|
||||
StatusHelper::success('E-mail sent. Follow instructions to reset password.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @route /activation-proxy
|
||||
*/
|
||||
public function activationProxyAction()
|
||||
{
|
||||
$this->context->subTitle = 'account activation';
|
||||
$this->context->viewName = 'user-select';
|
||||
$this->context->stylesheets []= 'auth.css';
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = InputHelper::get('name');
|
||||
$user = Model_User::locate($name);
|
||||
if (empty($user->email_unconfirmed))
|
||||
{
|
||||
if (!empty($user->email_confirmed))
|
||||
throw new SimpleException('E-mail was already confirmed; activation skipped');
|
||||
else
|
||||
throw new SimpleException('This user has no e-mail specified; activation cannot proceed');
|
||||
}
|
||||
self::sendEmailChangeConfirmation($user);
|
||||
StatusHelper::success('Activation e-mail resent.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,28 +5,65 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
{
|
||||
$this->no_markup = true;
|
||||
$this->span_gamut += ['doSpoilers' => 71];
|
||||
$this->span_gamut += ['doStrike' => 6];
|
||||
$this->span_gamut += ['doUsers' => 7];
|
||||
$this->span_gamut += ['doPosts' => 8];
|
||||
$this->span_gamut += ['doTags' => 9];
|
||||
$this->span_gamut += ['doAutoLinks2' => 29];
|
||||
|
||||
//fix italics/bold in the middle of sentence
|
||||
$prop = ['em_relist', 'strong_relist', 'em_strong_relist'];
|
||||
for ($i = 0; $i < 3; $i ++)
|
||||
{
|
||||
$this->{$prop[$i]}[''] = '(?:(?<!\*)' . str_repeat('\*', $i + 1) . '(?!\*)|(?<![a-zA-Z0-9_])' . str_repeat('_', $i + 1) . '(?!_))(?=\S|$)(?![\.,:;]\s)';
|
||||
$this->{$prop[$i]}[str_repeat('*', $i + 1)] = '(?<=\S|^)(?<!\*)' . str_repeat('\*', $i + 1) . '(?!\*)';
|
||||
$this->{$prop[$i]}[str_repeat('_', $i + 1)] = '(?<=\S|^)(?<!_)' . str_repeat('_', $i + 1) . '(?![a-zA-Z0-9_])';
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function doAutoLinks2($text)
|
||||
{
|
||||
$text = preg_replace_callback('{(?<!<)((https?|ftp):[^\'"><\s]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
$text = preg_replace_callback('{(?<!\w)(www\.[^\'"><\s]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function _doAnchors_inline_callback($matches)
|
||||
{
|
||||
if ($matches[3] == '')
|
||||
$url = &$matches[4];
|
||||
else
|
||||
$url = &$matches[3];
|
||||
if (!preg_match('/^((https?|ftp):|)\/\//', $url))
|
||||
$url = 'http://' . $url;
|
||||
return parent::_doAnchors_inline_callback($matches);
|
||||
}
|
||||
|
||||
protected function doHardBreaks($text)
|
||||
{
|
||||
return preg_replace_callback('/\n/', array(&$this, '_doHardBreaks_callback'), $text);
|
||||
return preg_replace_callback('/\n/', [&$this, '_doHardBreaks_callback'], $text);
|
||||
}
|
||||
|
||||
protected function doStrike($text)
|
||||
{
|
||||
return preg_replace_callback('{(~~|---)([^~]+)\1}', function($x)
|
||||
{
|
||||
return $this->hashPart('<del>') . $x[2] . $this->hashPart('</del>');
|
||||
}, $text);
|
||||
}
|
||||
|
||||
protected function doSpoilers($text)
|
||||
{
|
||||
if (is_array($text))
|
||||
{
|
||||
$text = $this->hashPart('<span class="spoiler">') . $text[1] . $this->hashPart('</span>');
|
||||
}
|
||||
return preg_replace_callback('{\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\])|(?R))+)\[\/spoiler\]}is', [__CLASS__, 'doSpoilers'], $text);
|
||||
}
|
||||
|
||||
protected function doPosts($text)
|
||||
{
|
||||
return preg_replace_callback('/@(\d+)/', function($x)
|
||||
return preg_replace_callback('/(?:(?<!\w))@(\d+)/', function($x)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('post', 'view', ['id' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
}, $text);
|
||||
@ -34,9 +71,17 @@ class CustomMarkdown extends \Michelf\Markdown
|
||||
|
||||
protected function doTags($text)
|
||||
{
|
||||
return preg_replace_callback('/#([a-zA-Z0-9_-]+)/', function($x)
|
||||
return preg_replace_callback('/(?:(?<!\w))#([a-zA-Z0-9_-]+)/', function($x)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('post', 'list', ['query' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
}, $text);
|
||||
}
|
||||
|
||||
protected function doUsers($text)
|
||||
{
|
||||
return preg_replace_callback('/(?:(?<!\w))\+([a-zA-Z0-9_-]+)/', function($x)
|
||||
{
|
||||
return $this->hashPart('<a href="' . \Chibi\UrlHelper::route('user', 'view', ['name' => $x[1]]) . '">') . $x[0] . $this->hashPart('</a>');
|
||||
}, $text);
|
||||
}
|
||||
}
|
||||
|
20
src/Helpers/BenchmarkHelper.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
class BenchmarkHelper
|
||||
{
|
||||
protected static $lastTime;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$lastTime = microtime(true);
|
||||
}
|
||||
|
||||
public static function tick()
|
||||
{
|
||||
$t = microtime(true);
|
||||
$lt = self::$lastTime;
|
||||
self::$lastTime = $t;
|
||||
return $t - $lt;
|
||||
}
|
||||
}
|
||||
|
||||
BenchmarkHelper::init();
|
69
src/Helpers/LogHelper.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
class LogHelper
|
||||
{
|
||||
static $path;
|
||||
static $context;
|
||||
static $config;
|
||||
static $autoFlush;
|
||||
static $content;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$config = \Chibi\Registry::getConfig();
|
||||
self::$context = \Chibi\Registry::getContext();
|
||||
self::$path = self::$config->main->logsPath . date('Y-m') . '.log';
|
||||
self::$autoFlush = true;
|
||||
|
||||
self::$content = '';
|
||||
}
|
||||
|
||||
public static function bufferChanges()
|
||||
{
|
||||
self::$autoFlush = false;
|
||||
}
|
||||
|
||||
public static function flush()
|
||||
{
|
||||
$fh = fopen(self::getLogPath(), 'ab');
|
||||
if (!$fh)
|
||||
throw new SimpleException('Cannot write to log files');
|
||||
if (flock($fh, LOCK_EX))
|
||||
{
|
||||
fwrite($fh, self::$content);
|
||||
fflush($fh);
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
}
|
||||
self::$content = '';
|
||||
self::$autoFlush = true;
|
||||
}
|
||||
|
||||
public static function getLogPath()
|
||||
{
|
||||
return self::$path;
|
||||
}
|
||||
|
||||
public static function log($text, array $tokens = [])
|
||||
{
|
||||
if (isset(self::$context->user))
|
||||
$tokens['user'] = TextHelper::reprUser(self::$context->user->name);
|
||||
|
||||
$text = TextHelper::replaceTokens($text, $tokens);
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
|
||||
$line = sprintf('[%s] %s: %s' . PHP_EOL, $timestamp, $ip, $text);
|
||||
|
||||
self::$content .= $line;
|
||||
|
||||
if (self::$autoFlush)
|
||||
self::flush();
|
||||
}
|
||||
|
||||
public static function logEvent($event, $text, array $tokens = [])
|
||||
{
|
||||
return self::log(sprintf('[%s] %s', $event, $text), $tokens);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper::init();
|
@ -22,6 +22,9 @@ class PrivilegesHelper
|
||||
|
||||
public static function confirm($privilege, $subPrivilege = null)
|
||||
{
|
||||
if (php_sapi_name() == 'cli')
|
||||
return true;
|
||||
|
||||
$user = \Chibi\Registry::getContext()->user;
|
||||
$minAccessRank = AccessRank::Admin;
|
||||
|
||||
@ -53,7 +56,7 @@ class PrivilegesHelper
|
||||
public static function getIdentitySubPrivilege($user)
|
||||
{
|
||||
if (!$user)
|
||||
return false;
|
||||
return 'all';
|
||||
$userFromContext = \Chibi\Registry::getContext()->user;
|
||||
return $user->id == $userFromContext->id ? 'own' : 'all';
|
||||
}
|
||||
@ -63,6 +66,19 @@ class PrivilegesHelper
|
||||
if (!$user->email_confirmed)
|
||||
throw new SimpleException('Need e-mail address confirmation to continue');
|
||||
}
|
||||
|
||||
public static function getAllowedSafety()
|
||||
{
|
||||
if (php_sapi_name() == 'cli')
|
||||
return PostSafety::getAll();
|
||||
|
||||
$context = \Chibi\Registry::getContext();
|
||||
return array_filter(PostSafety::getAll(), function($safety) use ($context)
|
||||
{
|
||||
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
|
||||
$context->user->hasEnabledSafety($safety);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PrivilegesHelper::init();
|
||||
|
15
src/Helpers/SessionHelper.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
class SessionHelper
|
||||
{
|
||||
public static function get($key, $default = null)
|
||||
{
|
||||
if (!isset($_SESSION[$key]))
|
||||
return $default;
|
||||
return $_SESSION[$key];
|
||||
}
|
||||
|
||||
public static function set($key, $value)
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
}
|
30
src/Helpers/StatusHelper.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
class StatusHelper
|
||||
{
|
||||
private static function flag($success, $message = null)
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if (!empty($message))
|
||||
{
|
||||
$context->transport->message = $message;
|
||||
$context->transport->messageHtml = TextHelper::parseMarkdown($message, true);
|
||||
}
|
||||
$context->transport->success = $success;
|
||||
}
|
||||
|
||||
public static function init()
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$context->transport->success = null;
|
||||
}
|
||||
|
||||
public static function success($message = null)
|
||||
{
|
||||
self::flag(true, $message);
|
||||
}
|
||||
|
||||
public static function failure($message = null)
|
||||
{
|
||||
self::flag(false, $message);
|
||||
}
|
||||
}
|
@ -48,6 +48,14 @@ class TextHelper
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function humanCaseToKebabCase($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
$string = str_replace(' ', '-', $string);
|
||||
$string = strtolower($string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function resolveConstant($constantName, $className = null)
|
||||
{
|
||||
$constantName = self::kebabCaseToCamelCase($constantName);
|
||||
@ -63,6 +71,18 @@ class TextHelper
|
||||
return constant($constantName);
|
||||
}
|
||||
|
||||
private static function stripUnits($string, $base, $suffixes)
|
||||
{
|
||||
$suffix = substr($string, -1, 1);
|
||||
$index = array_search($suffix, $suffixes);
|
||||
if ($index === false)
|
||||
return $string;
|
||||
$number = intval($string);
|
||||
for ($i = 0; $i < $index; $i ++)
|
||||
$number *= $base;
|
||||
return $number;
|
||||
}
|
||||
|
||||
private static function useUnits($number, $base, $suffixes)
|
||||
{
|
||||
$suffix = array_shift($suffixes);
|
||||
@ -89,6 +109,16 @@ class TextHelper
|
||||
return self::useUnits($number, 1000, ['', 'K', 'M']);
|
||||
}
|
||||
|
||||
public static function stripBytesUnits($string)
|
||||
{
|
||||
return self::stripUnits($string, 1024, ['B', 'K', 'M', 'G']);
|
||||
}
|
||||
|
||||
public static function stripDecimalUnits($string)
|
||||
{
|
||||
return self::stripUnits($string, 1000, ['', 'K', 'M']);
|
||||
}
|
||||
|
||||
public static function removeUnsafeKeys(&$input, $regex)
|
||||
{
|
||||
if (is_array($input))
|
||||
@ -149,4 +179,53 @@ class TextHelper
|
||||
$output = preg_replace('{</?p>}', '', $output);
|
||||
return $output;
|
||||
}
|
||||
|
||||
public static function reprPost($post)
|
||||
{
|
||||
if (!is_object($post))
|
||||
return '@' . $post;
|
||||
return '@' . $post->id;
|
||||
}
|
||||
|
||||
public static function reprUser($user)
|
||||
{
|
||||
if (!is_object($user))
|
||||
return '+' . $user;
|
||||
return '+' . $user->name;
|
||||
}
|
||||
|
||||
public static function reprTag($tag)
|
||||
{
|
||||
if (!is_object($tag))
|
||||
return '#' . $tag;
|
||||
return '#' . $tag->name;
|
||||
}
|
||||
|
||||
public static function encrypt($text)
|
||||
{
|
||||
$salt = \Chibi\Registry::getConfig()->main->salt;
|
||||
$alg = MCRYPT_RIJNDAEL_256;
|
||||
$mode = MCRYPT_MODE_ECB;
|
||||
$iv = mcrypt_create_iv(mcrypt_get_iv_size($alg, $mode), MCRYPT_RAND);
|
||||
return trim(base64_encode(mcrypt_encrypt($alg, $salt, $text, $mode, $iv)));
|
||||
}
|
||||
|
||||
public static function decrypt($text)
|
||||
{
|
||||
$salt = \Chibi\Registry::getConfig()->main->salt;
|
||||
$alg = MCRYPT_RIJNDAEL_256;
|
||||
$mode = MCRYPT_MODE_ECB;
|
||||
$iv = mcrypt_create_iv(mcrypt_get_iv_size($alg, $mode), MCRYPT_RAND);
|
||||
return trim(mcrypt_decrypt($alg, $salt, base64_decode($text), $mode, $iv));
|
||||
}
|
||||
|
||||
public static function cleanPath($path)
|
||||
{
|
||||
$path = str_replace(['/', '\\'], DS, $path);
|
||||
$path = preg_replace('{[^' . DS . ']+' . DS . '\.\.(' . DS . '|$)}', '', $path);
|
||||
$path = preg_replace('{(' . DS . '|^)\.' . DS . '}', '\1', $path);
|
||||
$path = preg_replace('{' . DS . '{2,}}', DS, $path);
|
||||
$path = rtrim($path, DS);
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
53
src/Models/AbstractModel.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
abstract class AbstractModel extends RedBean_SimpleModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
throw new SimpleException('Not implemented.');
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
throw new SimpleException('Not implemented.');
|
||||
}
|
||||
|
||||
public static function getEntitiesRows($query, $perPage = null, $page = 1)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$dbQuery = R::$f->getNew()->begin();
|
||||
$dbQuery->select($table . '.*');
|
||||
$builder = static::getQueryBuilder();
|
||||
if ($builder)
|
||||
$builder::build($dbQuery, $query);
|
||||
else
|
||||
$dbQuery->from($table);
|
||||
if ($perPage !== null)
|
||||
{
|
||||
$dbQuery->limit('?')->put($perPage);
|
||||
$dbQuery->offset('?')->put(($page - 1) * $perPage);
|
||||
}
|
||||
$rows = $dbQuery->get();
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public static function getEntities($query, $perPage = null, $page = 1)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$rows = self::getEntitiesRows($query, $perPage, $page);
|
||||
$entities = R::convertToBeans($table, $rows);
|
||||
return $entities;
|
||||
}
|
||||
|
||||
public static function getEntityCount($query)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$dbQuery = R::$f->getNew()->begin();
|
||||
$dbQuery->select('COUNT(1)')->as('count');
|
||||
$builder = static::getQueryBuilder();
|
||||
if ($builder)
|
||||
$builder::build($dbQuery, $query);
|
||||
else
|
||||
$dbQuery->from($table);
|
||||
return intval($dbQuery->get('row')['count']);
|
||||
}
|
||||
}
|
5
src/Models/AbstractQueryBuilder.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
interface AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query);
|
||||
}
|
@ -1,11 +1,25 @@
|
||||
<?php
|
||||
class Model_Comment extends RedBean_SimpleModel
|
||||
class Model_Comment extends AbstractModel
|
||||
{
|
||||
public static function locate($key)
|
||||
public static function getTableName()
|
||||
{
|
||||
$comment = R::findOne('comment', 'id = ?', [$key]);
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Comment_QueryBuilder';
|
||||
}
|
||||
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$comment = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
||||
if (!$comment)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid comment ID "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
return $comment;
|
||||
}
|
||||
|
||||
|
13
src/Models/Model_Comment_QueryBuilder.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
class Model_Comment_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$dbQuery
|
||||
->from('comment')
|
||||
->where('post_id')
|
||||
->is()->not('NULL')
|
||||
->orderBy('id')
|
||||
->desc();
|
||||
}
|
||||
}
|
@ -1,19 +1,27 @@
|
||||
<?php
|
||||
class Model_Post extends RedBean_SimpleModel
|
||||
class Model_Post extends AbstractModel
|
||||
{
|
||||
public static function locate($key, $disallowNumeric = false)
|
||||
public static function locate($key, $disallowNumeric = false, $throw = true)
|
||||
{
|
||||
if (is_numeric($key) and !$disallowNumeric)
|
||||
{
|
||||
$post = R::findOne('post', 'id = ?', [$key]);
|
||||
$post = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
||||
if (!$post)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid post ID "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$post = R::findOne('post', 'name = ?', [$key]);
|
||||
$post = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
||||
if (!$post)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid post name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $post;
|
||||
}
|
||||
@ -32,10 +40,20 @@ class Model_Post extends RedBean_SimpleModel
|
||||
{
|
||||
$source = trim($source);
|
||||
|
||||
$maxLength = 100;
|
||||
$maxLength = 200;
|
||||
if (strlen($source) > $maxLength)
|
||||
throw new SimpleException('Source must have at most ' . $maxLength . ' characters');
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'post';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Post_QueryBuilder';
|
||||
}
|
||||
}
|
||||
|
394
src/Models/Model_Post_QueryBuilder.php
Normal file
@ -0,0 +1,394 @@
|
||||
<?php
|
||||
class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
protected static function attachTableCount($dbQuery, $tableName, $shortName)
|
||||
{
|
||||
$dbQuery
|
||||
->addSql(', ')
|
||||
->open()
|
||||
->select('COUNT(1)')
|
||||
->from($tableName)
|
||||
->where($tableName . '.post_id = post.id')
|
||||
->close()
|
||||
->as($shortName . '_count');
|
||||
}
|
||||
|
||||
protected static function attachCommentCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'comment', 'comment');
|
||||
}
|
||||
|
||||
protected static function attachFavCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'favoritee', 'fav');
|
||||
}
|
||||
|
||||
protected static function attachTagCount($dbQuery)
|
||||
{
|
||||
self::attachTableCount($dbQuery, 'post_tag', 'tag');
|
||||
}
|
||||
|
||||
protected static function filterUserSafety($dbQuery)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
$dbQuery->addSql('safety')->in('(' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
}
|
||||
|
||||
protected static function filterUserHidden($dbQuery)
|
||||
{
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->not()->addSql('hidden');
|
||||
else
|
||||
$dbQuery->addSql('1');
|
||||
}
|
||||
|
||||
protected static function filterChain($dbQuery)
|
||||
{
|
||||
if (isset($dbQuery->__chained))
|
||||
$dbQuery->and();
|
||||
else
|
||||
$dbQuery->where();
|
||||
$dbQuery->__chained = true;
|
||||
}
|
||||
|
||||
protected static function filterNegate($dbQuery)
|
||||
{
|
||||
$dbQuery->not();
|
||||
}
|
||||
|
||||
protected static function filterTag($dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->innerJoin('tag')
|
||||
->on('post_tag.tag_id = tag.id')
|
||||
->where('post_id = post.id')
|
||||
->and('LOWER(tag.name) = LOWER(?)')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenId($dbQuery, $val)
|
||||
{
|
||||
$ids = preg_split('/[;,]/', $val);
|
||||
$ids = array_map('intval', $ids);
|
||||
$dbQuery->addSql('id')->in('(' . R::genSlots($ids) . ')');
|
||||
foreach ($ids as $id)
|
||||
$dbQuery->put($id);
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMin($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMax($dbQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenType($dbQuery, $val)
|
||||
{
|
||||
switch ($val)
|
||||
{
|
||||
case 'swf':
|
||||
$type = PostType::Flash;
|
||||
break;
|
||||
case 'img':
|
||||
$type = PostType::Image;
|
||||
break;
|
||||
case 'yt':
|
||||
case 'youtube':
|
||||
$type = PostType::Youtube;
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown type "' . $val . '"');
|
||||
}
|
||||
$dbQuery->addSql('type = ?')->put($type);
|
||||
}
|
||||
|
||||
protected static function __filterTokenDateParser($val)
|
||||
{
|
||||
list ($year, $month, $day) = explode('-', $val . '-0-0');
|
||||
$yearMin = $yearMax = intval($year);
|
||||
$monthMin = $monthMax = intval($month);
|
||||
$monthMin = $monthMin ?: 1;
|
||||
$monthMax = $monthMax ?: 12;
|
||||
$dayMin = $dayMax = intval($day);
|
||||
$dayMin = $dayMin ?: 1;
|
||||
$dayMax = $dayMax ?: intval(date('t', mktime(0, 0, 0, $monthMax, 1, $year)));
|
||||
$timeMin = mktime(0, 0, 0, $monthMin, $dayMin, $yearMin);
|
||||
$timeMax = mktime(0, 0, -1, $monthMax, $dayMax+1, $yearMax);
|
||||
return [$timeMin, $timeMax];
|
||||
}
|
||||
|
||||
protected static function filterTokenDate($dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery
|
||||
->addSql('upload_date >= ?')->and('upload_date <= ?')
|
||||
->put($timeMin)
|
||||
->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMin($dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date >= ?')->put($timeMin);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMax($dbQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date <= ?')->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenFav($dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('favoritee')
|
||||
->innerJoin('user')
|
||||
->on('favoritee.user_id = user.id')
|
||||
->where('post_id = post.id')
|
||||
->and('user.name = ?')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenFavs($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenFavitee($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenFaviter($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenSubmit($dbQuery, $val)
|
||||
{
|
||||
$dbQuery
|
||||
->addSql('uploader_id = ')
|
||||
->open()
|
||||
->select('user.id')
|
||||
->from('user')
|
||||
->where('name = ?')->put($val)
|
||||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenUploader($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUpload($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUploaded($dbQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($dbQuery, $val);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected static function order($dbQuery, $val)
|
||||
{
|
||||
$randomReset = true;
|
||||
|
||||
$orderDir = 1;
|
||||
if (substr($val, -4) == 'desc')
|
||||
{
|
||||
$orderDir = 1;
|
||||
$val = rtrim(substr($val, 0, -4), ',');
|
||||
}
|
||||
elseif (substr($val, -3) == 'asc')
|
||||
{
|
||||
$orderDir = -1;
|
||||
$val = rtrim(substr($val, 0, -3), ',');
|
||||
}
|
||||
if ($val{0} == '-')
|
||||
{
|
||||
$orderDir *= -1;
|
||||
$val = substr($val, 1);
|
||||
}
|
||||
|
||||
switch ($val)
|
||||
{
|
||||
case 'id':
|
||||
$orderColumn = 'post.id';
|
||||
break;
|
||||
case 'date':
|
||||
$orderColumn = 'post.upload_date';
|
||||
break;
|
||||
case 'comment':
|
||||
case 'comments':
|
||||
case 'commentcount':
|
||||
$orderColumn = 'comment_count';
|
||||
break;
|
||||
case 'fav':
|
||||
case 'favs':
|
||||
case 'favcount':
|
||||
$orderColumn = 'fav_count';
|
||||
break;
|
||||
case 'score':
|
||||
$orderDir *= -1;
|
||||
$orderColumn = 'score';
|
||||
break;
|
||||
case 'tag':
|
||||
case 'tags':
|
||||
case 'tagcount':
|
||||
$orderColumn = 'tag_count';
|
||||
break;
|
||||
case 'random':
|
||||
//seeding works like this: if you visit anything
|
||||
//that triggers order other than random, the seed
|
||||
//is going to reset. however, it stays the same as
|
||||
//long as you keep visiting pages with order:random
|
||||
//specified.
|
||||
$randomReset = false;
|
||||
if (!isset($_SESSION['browsing-seed']))
|
||||
$_SESSION['browsing-seed'] = mt_rand();
|
||||
$seed = $_SESSION['browsing-seed'];
|
||||
$orderColumn = 'SUBSTR(id * ' . $seed .', LENGTH(id) + 2)';
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown key "' . $val . '"');
|
||||
}
|
||||
|
||||
if ($randomReset and isset($_SESSION['browsing-seed']))
|
||||
unset($_SESSION['browsing-seed']);
|
||||
|
||||
$dbQuery->orderBy($orderColumn);
|
||||
if ($orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
self::attachCommentCount($dbQuery);
|
||||
self::attachFavCount($dbQuery);
|
||||
self::attachTagCount($dbQuery);
|
||||
|
||||
$dbQuery->from('post');
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserSafety($dbQuery);
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserHidden($dbQuery);
|
||||
|
||||
/* query tokens */
|
||||
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
|
||||
if (count($tokens) > $config->browsing->maxSearchTokens)
|
||||
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
||||
|
||||
$orderToken = 'id';
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if ($token{0} == '-')
|
||||
{
|
||||
$token = substr($token, 1);
|
||||
$neg = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$neg = false;
|
||||
}
|
||||
|
||||
$pos = strpos($token, ':');
|
||||
if ($pos === false)
|
||||
{
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::filterTag($dbQuery, $token);
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = substr($token, 0, $pos);
|
||||
$val = substr($token, $pos + 1);
|
||||
|
||||
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
||||
if (method_exists(__CLASS__, $methodName))
|
||||
{
|
||||
self::filterChain($dbQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::$methodName($dbQuery, $val);
|
||||
}
|
||||
|
||||
elseif ($key == 'order')
|
||||
{
|
||||
if ($neg)
|
||||
$orderToken = $val;
|
||||
else
|
||||
$orderToken = '-' . $val;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw new SimpleException('Unknown key "' . $key . '"');
|
||||
}
|
||||
}
|
||||
|
||||
self::order($dbQuery, $orderToken);
|
||||
}
|
||||
}
|
@ -1,12 +1,34 @@
|
||||
<?php
|
||||
class Model_Tag extends RedBean_SimpleModel
|
||||
class Model_Tag extends AbstractModel
|
||||
{
|
||||
public static function locate($key)
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$user = R::findOne('tag', 'name = ?', [$key]);
|
||||
if (!$user)
|
||||
$tag = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$key]);
|
||||
if (!$tag)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid tag name "' . $key . '"');
|
||||
return $user;
|
||||
return null;
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public static function removeUnused()
|
||||
{
|
||||
$dbQuery = R::$f
|
||||
->begin()
|
||||
->select('id, name')
|
||||
->from(self::getTableName())
|
||||
->where()
|
||||
->not()->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.tag_id = tag.id')
|
||||
->close();
|
||||
$rows = $dbQuery->get();
|
||||
$entities = R::convertToBeans(self::getTableName(), $rows);
|
||||
R::trashAll($entities);
|
||||
}
|
||||
|
||||
public static function insertOrUpdate($tags)
|
||||
@ -14,10 +36,10 @@ class Model_Tag extends RedBean_SimpleModel
|
||||
$dbTags = [];
|
||||
foreach ($tags as $tag)
|
||||
{
|
||||
$dbTag = R::findOne('tag', 'name = ?', [$tag]);
|
||||
$dbTag = self::locate($tag, false);
|
||||
if (!$dbTag)
|
||||
{
|
||||
$dbTag = R::dispense('tag');
|
||||
$dbTag = R::dispense(self::getTableName());
|
||||
$dbTag->name = $tag;
|
||||
R::store($dbTag);
|
||||
}
|
||||
@ -37,12 +59,23 @@ class Model_Tag extends RedBean_SimpleModel
|
||||
if (strlen($tag) > $maxLength)
|
||||
throw new SimpleException('Tag must have at most ' . $maxLength . ' characters');
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9_-]+$/i', $tag))
|
||||
if (!preg_match('/^[a-zA-Z0-9_.-]+$/i', $tag))
|
||||
throw new SimpleException('Invalid tag "' . $tag . '"');
|
||||
|
||||
if (preg_match('/^\.\.?$/', $tag))
|
||||
throw new SimpleException('Invalid tag "' . $tag . '"');
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
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);
|
||||
@ -58,4 +91,14 @@ class Model_Tag extends RedBean_SimpleModel
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'tag';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Tag_Querybuilder';
|
||||
}
|
||||
}
|
||||
|
37
src/Models/Model_Tag_QueryBuilder.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
class model_Tag_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
$limitQuery = false;
|
||||
$dbQuery
|
||||
->addSql(', COUNT(post_tag.post_id)')
|
||||
->as('post_count')
|
||||
->from('tag')
|
||||
->innerJoin('post_tag')
|
||||
->on('tag.id = post_tag.tag_id')
|
||||
->innerJoin('post')
|
||||
->on('post.id = post_tag.post_id')
|
||||
->where('safety IN (' . R::genSlots($allowedSafety) . ')');
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
|
||||
if ($query !== null)
|
||||
{
|
||||
$limitQuery = true;
|
||||
if (strlen($query) >= 3)
|
||||
$query = '%' . $query;
|
||||
$query .= '%';
|
||||
$dbQuery
|
||||
->and('LOWER(tag.name)')
|
||||
->like('LOWER(?)')
|
||||
->put($query);
|
||||
}
|
||||
|
||||
$dbQuery->groupBy('tag.id');
|
||||
|
||||
if ($limitQuery)
|
||||
$dbQuery->limit(15);
|
||||
}
|
||||
}
|
25
src/Models/Model_Token.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
class Model_Token extends AbstractModel
|
||||
{
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
if (empty($key))
|
||||
throw new SimpleException('Invalid security token');
|
||||
|
||||
$token = R::findOne('usertoken', 'token = ?', [$key]);
|
||||
if ($token === null)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('No user with security token');
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($token->used)
|
||||
throw new SimpleException('This token was already used');
|
||||
|
||||
if ($token->expires !== null and time() > $token->expires)
|
||||
throw new SimpleException('This token has expired');
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
@ -1,12 +1,20 @@
|
||||
<?php
|
||||
class Model_User extends RedBean_SimpleModel
|
||||
class Model_User extends AbstractModel
|
||||
{
|
||||
public static function locate($key)
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$user = R::findOne('user', 'name = ?', [$key]);
|
||||
if (!$user)
|
||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
||||
$user = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
||||
if ($user)
|
||||
return $user;
|
||||
|
||||
$user = R::findOne(self::getTableName(), 'LOWER(email_confirmed) = LOWER(?)', [trim($key)]);
|
||||
if ($user)
|
||||
return $user;
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAvatarUrl($size = 32)
|
||||
@ -37,34 +45,59 @@ class Model_User extends RedBean_SimpleModel
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
|
||||
const SETTING_SAFETY = 1;
|
||||
const SETTING_ENDLESS_SCROLLING = 2;
|
||||
|
||||
public function hasEnabledSafety($safety)
|
||||
{
|
||||
return $this->getSetting('safety-' . $safety) !== false;
|
||||
$all = $this->getSetting(self::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
return $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)
|
||||
{
|
||||
$this->setSetting('safety-' . $safety, false);
|
||||
$anythingEnabled = false;
|
||||
foreach (PostSafety::getAll() as $safety)
|
||||
if (self::hasEnabledSafety($safety))
|
||||
$anythingEnabled = true;
|
||||
if (!$anythingEnabled)
|
||||
$this->setSetting('safety-' . PostSafety::Safe, true);
|
||||
$new &= ~PostSafety::toFlag($safety);
|
||||
if (!$new)
|
||||
$new = PostSafety::toFlag(PostSafety::Safe);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setSetting('safety-' . $safety, true);
|
||||
$new |= PostSafety::toFlag($safety);
|
||||
}
|
||||
|
||||
$this->setSetting(self::SETTING_SAFETY, $new);
|
||||
}
|
||||
|
||||
public function hasEnabledEndlessScrolling()
|
||||
{
|
||||
$ret = $this->getSetting(self::SETTING_ENDLESS_SCROLLING);
|
||||
if ($ret === null)
|
||||
$ret = \Chibi\Registry::getConfig()->browsing->endlessScrollingDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enableEndlessScrolling($enabled)
|
||||
{
|
||||
$this->setSetting(self::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateUserName($userName)
|
||||
{
|
||||
$userName = trim($userName);
|
||||
|
||||
$dbUser = R::findOne('user', 'name = ?', [$userName]);
|
||||
$dbUser = R::findOne(self::getTableName(), 'name = ?', [$userName]);
|
||||
if ($dbUser !== null)
|
||||
{
|
||||
if (!$dbUser->email_confirmed and \Chibi\Registry::getConfig()->registration->needEmailForRegistering)
|
||||
@ -131,8 +164,17 @@ class Model_User extends RedBean_SimpleModel
|
||||
|
||||
public static function hashPassword($pass, $salt2)
|
||||
{
|
||||
$salt1 = \Chibi\Registry::getConfig()->registration->salt;
|
||||
$salt1 = \Chibi\Registry::getConfig()->main->salt;
|
||||
return sha1($salt1 . $salt2 . $pass);
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_User_QueryBuilder';
|
||||
}
|
||||
}
|
||||
|
31
src/Models/Model_User_QueryBuilder.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
class Model_User_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$sortStyle = $query;
|
||||
$dbQuery->from('user');
|
||||
|
||||
switch ($sortStyle)
|
||||
{
|
||||
case 'alpha,asc':
|
||||
$dbQuery->orderBy('name')->asc();
|
||||
break;
|
||||
case 'alpha,desc':
|
||||
$dbQuery->orderBy('name')->desc();
|
||||
break;
|
||||
case 'date,asc':
|
||||
$dbQuery->orderBy('join_date')->asc();
|
||||
break;
|
||||
case 'date,desc':
|
||||
$dbQuery->orderBy('join_date')->desc();
|
||||
break;
|
||||
case 'pending':
|
||||
$dbQuery->where('staff_confirmed IS NULL');
|
||||
$dbQuery->or('staff_confirmed = 0');
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown sort style');
|
||||
}
|
||||
}
|
||||
}
|
@ -4,4 +4,9 @@ class PostSafety extends Enum
|
||||
const Safe = 1;
|
||||
const Sketchy = 2;
|
||||
const Unsafe = 3;
|
||||
|
||||
public static function toFlag($safety)
|
||||
{
|
||||
return pow(2, $safety);
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,5 @@ class PostType extends Enum
|
||||
{
|
||||
const Image = 1;
|
||||
const Flash = 2;
|
||||
const Youtube = 3;
|
||||
}
|
||||
|
@ -10,9 +10,12 @@ class Privilege extends Enum
|
||||
const EditPostTags = 7;
|
||||
const EditPostThumb = 8;
|
||||
const EditPostSource = 26;
|
||||
const EditPostRelations = 30;
|
||||
const HidePost = 9;
|
||||
const DeletePost = 10;
|
||||
const FeaturePost = 25;
|
||||
const ScorePost = 31;
|
||||
const FlagPost = 34;
|
||||
|
||||
const ListUsers = 11;
|
||||
const ViewUser = 12;
|
||||
@ -23,7 +26,9 @@ class Privilege extends Enum
|
||||
const ChangeUserAccessRank = 16;
|
||||
const ChangeUserEmail = 17;
|
||||
const ChangeUserName = 18;
|
||||
const ChangeUserSettings = 28;
|
||||
const DeleteUser = 19;
|
||||
const FlagUser = 35;
|
||||
|
||||
const ListComments = 20;
|
||||
const AddComment = 23;
|
||||
@ -32,4 +37,8 @@ class Privilege extends Enum
|
||||
const ListTags = 21;
|
||||
const MergeTags = 27;
|
||||
const RenameTags = 27;
|
||||
const MassTag = 29;
|
||||
|
||||
const ListLogs = 32;
|
||||
const ViewLog = 33;
|
||||
}
|
||||
|
1
src/Upgrades/Upgrade2.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE user ADD COLUMN banned INTEGER;
|
10
src/Upgrades/Upgrade3.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE crossref
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
post_id INTEGER,
|
||||
post2_id INTEGER,
|
||||
FOREIGN KEY(post_id) REFERENCES post(id) ON DELETE CASCADE ON UPDATE SET NULL,
|
||||
FOREIGN KEY(post2_id) REFERENCES post(id) ON DELETE CASCADE ON UPDATE SET NULL
|
||||
);
|
||||
CREATE INDEX idx_fk_crossref_post_id ON crossref(post_id);
|
||||
CREATE INDEX idx_fk_crossref_post2_id ON crossref(post2_id);
|
30
src/Upgrades/Upgrade4.sql
Normal file
@ -0,0 +1,30 @@
|
||||
ALTER TABLE post ADD COLUMN score INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE post SET score = 0;
|
||||
|
||||
CREATE TABLE post_score
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
post_id INTEGER,
|
||||
user_id INTEGER,
|
||||
score INTEGER,
|
||||
FOREIGN KEY(post_id) REFERENCES post(id) ON DELETE CASCADE ON UPDATE SET NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE SET NULL
|
||||
);
|
||||
CREATE INDEX idx_fk_post_score_post_id ON post_score(post_id);
|
||||
CREATE INDEX idx_fk_post_score_user_id ON post_score(user_id);
|
||||
|
||||
CREATE TRIGGER post_score_update AFTER UPDATE ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score - old.score + new.score WHERE post.id = new.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER post_score_insert AFTER INSERT ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score + new.score WHERE post.id = new.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER post_score_delete BEFORE DELETE ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score - old.score WHERE post.id = old.post_id;
|
||||
END;
|
1
src/Upgrades/Upgrade5.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE post_score RENAME TO postscore
|
53
src/Upgrades/Upgrade6.sql
Normal file
@ -0,0 +1,53 @@
|
||||
CREATE TABLE user2
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT,
|
||||
pass_salt TEXT,
|
||||
pass_hash TEXT,
|
||||
staff_confirmed INTEGER,
|
||||
email_unconfirmed TEXT,
|
||||
email_confirmed TEXT,
|
||||
join_date INTEGER,
|
||||
access_rank INTEGER,
|
||||
settings TEXT,
|
||||
banned INTEGER
|
||||
);
|
||||
|
||||
INSERT INTO user2
|
||||
(id,
|
||||
name,
|
||||
pass_salt,
|
||||
pass_hash,
|
||||
staff_confirmed,
|
||||
email_unconfirmed,
|
||||
email_confirmed,
|
||||
join_date,
|
||||
access_rank,
|
||||
settings,
|
||||
banned)
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
pass_salt,
|
||||
pass_hash,
|
||||
staff_confirmed,
|
||||
email_unconfirmed,
|
||||
email_confirmed,
|
||||
join_date,
|
||||
access_rank,
|
||||
settings,
|
||||
banned
|
||||
FROM user;
|
||||
|
||||
DROP TABLE user;
|
||||
ALTER TABLE user2 RENAME TO user;
|
||||
|
||||
CREATE TABLE usertoken
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
token VARCHAR(32),
|
||||
used BOOLEAN,
|
||||
expires INTEGER --TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_fk_usertoken_user_id ON usertoken(user_id);
|
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);
|
||||
|
@ -1,12 +1,11 @@
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth aligned" method="post">
|
||||
|
||||
<div>
|
||||
<p>If you don't have an account yet,<br/><a href="<?php echo \Chibi\UrlHelper::route('user', 'registration'); ?>">click here</a> to create a new one.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="name">User name:</label>
|
||||
<div class="input-wrapper"><input id="name" name="name"/></div>
|
||||
<div class="input-wrapper"><input type="text" id="name" name="name"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -14,12 +13,32 @@
|
||||
<div class="input-wrapper"><input type="password" id="password" name="password"/></div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($this->context->transport->errorMessage)): ?>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<div>
|
||||
<label class="left"></label>
|
||||
<label class="left"> </label>
|
||||
<div class="input-wrapper">
|
||||
<button type="submit">Log in</button>
|
||||
|
||||
<input type="hidden" name="remember" value="0"/>
|
||||
<label>
|
||||
<input type="checkbox" name="remember" value="1"/>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div class="help">
|
||||
<label class="left"> </label>
|
||||
<div>
|
||||
<p>Problems logging in?</p>
|
||||
<ul>
|
||||
<li><a href="<?php echo \Chibi\UrlHelper::route('user', 'password-reset-proxy') ?>">I don't remember my password</a></li>
|
||||
<li><a href="<?php echo \Chibi\UrlHelper::route('user', 'activation-proxy') ?>">I haven't received activation e-mail</a></li>
|
||||
<li><a href="<?php echo \Chibi\UrlHelper::route('user', 'registration') ?>">I don't have an account</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -2,10 +2,10 @@
|
||||
<div class="avatar">
|
||||
<?php if ($this->context->comment->commenter): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->comment->commenter->name]) ?>">
|
||||
<img src="<?php echo htmlspecialchars($this->context->comment->commenter->getAvatarUrl(40)) ?>" alt="<?php echo $this->context->comment->commenter->name ?: '[deleted user]' ?>"/>
|
||||
<img src="<?php echo htmlspecialchars($this->context->comment->commenter->getAvatarUrl(40)) ?>" alt="<?php echo $this->context->comment->commenter->name ?: '[unknown user]' ?>"/>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[deleted user]">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[unknown user]">
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<?php echo $this->context->comment->commenter->name ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
[deleted user]
|
||||
[unknown user]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorHtml ?><br><a href="javascript:history.go(-1)">Go back</a></p>
|
@ -4,6 +4,16 @@
|
||||
|
||||
<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>
|
||||
@ -12,10 +22,15 @@
|
||||
<li>uploaded by David: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'submit:David']) ?>"><code>submit:David</code></a> (note no spaces)</li>
|
||||
<li>favorited by David: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'fav:David']) ?>"><code>fav:David</code></a></li>
|
||||
<li>favorited by at least four users: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'favmin:4']) ?>"><code>favmin:4</code></a></li>
|
||||
<li>exactly from the specified date: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'date:2001']) ?>"><code>date:2001</code></a>, <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'date:2012-09-29']) ?>"><code>date:2012-09-29</code></a> (yyyy-mm-dd format)</li>
|
||||
<li>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>by content type: <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:img']) ?>"><code>type:img</code></a>, <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'type:swf']) ?>"><code>type:swf</code></a> (images and flash files, respectively)</li>
|
||||
<li>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>
|
||||
@ -28,9 +43,10 @@
|
||||
<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>, any of them can be reversed in the same way as negating other tags: by placing a dash before the tag.</p>
|
||||
<p>As shown with <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => '-order:date']) ?>"><code>-order:date</code></a><span class="comma">, </span>any of them can be reversed in the same way as negating other tags: by placing a dash before the tag.</p>
|
||||
|
||||
<h1>Registration</h1>
|
||||
|
||||
@ -38,6 +54,16 @@
|
||||
|
||||
<p>Oh, and you can delete your account at any time. Posts you uploaded will stay, unless some angry admin removes them.</p>
|
||||
|
||||
<h1>Comments</h1>
|
||||
|
||||
<p>Registered users can post comments. Comments support <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a>, extended by some handy tags:</p>
|
||||
|
||||
<ul>
|
||||
<li>permalink to post number 426: <a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => 426]) ?>"><code>@426</code></a></li>
|
||||
<li>link to tag "Dragon_Ball": <a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => 'Dragon_Ball']) ?>"><code>#Dragon_Ball</code></a></li>
|
||||
<li>mark text as spoiler and hide it: <code class="spoiler">[spoiler]There is no spoon.[/spoiler]</code></li>
|
||||
</ul>
|
||||
|
||||
<h1>Uploads</h1>
|
||||
|
||||
<p>After registering, you gain the power to upload files to the service, for everyone else to see. Owners of the site are not responsible for content uploaded by users. You are not allowed to post any form of <a href="http://www.urbandictionary.com/define.php?term=cp">cp</a>. If you possess it, we ask you to leave immediately and never come back.</p>
|
||||
<p>After registering and activating your account, you gain the power to upload files to the service, for everyone else to see. Owners of the site are not responsible for content uploaded by users. You are not allowed to post any form of <a href="http://www.urbandictionary.com/define.php?term=cp">cp</a>. If you possess it, we ask you to leave immediately and never come back.</p>
|
||||
|
@ -1,21 +1,20 @@
|
||||
<div id="sidebar">
|
||||
<div id="welcome">
|
||||
<h1><?php echo $this->config->main->title ?></h1>
|
||||
|
||||
<form name="search" action="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>" method="get">
|
||||
<input type="search" name="query" placeholder="Search…" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>">
|
||||
</form>
|
||||
|
||||
<p>
|
||||
<span>serving <?php echo $this->context->transport->postCount ?> posts</span>
|
||||
<span>powered by <a href="<?php echo SZURU_LINK ?>">szurubooru</a></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($this->context->featuredPost)): ?>
|
||||
<div id="inner-content">
|
||||
<div class="header">
|
||||
Featured image
|
||||
<div class="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>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="left">
|
||||
Tags:
|
||||
<ul class="tags">
|
||||
<?php foreach ($this->context->featuredPost->sharedTag as $tag): ?>
|
||||
<li>
|
||||
@ -25,29 +24,25 @@
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
|
||||
<span class="favs-comments">
|
||||
<?php printf('%d fav%s', $x = $this->context->featuredPost->countOwn('favoritee'), $x == 1 ? '' : 's') ?>, 
|
||||
<?php printf('%d comment%s', $x = $this->context->featuredPost->countOwn('comment'), $x == 1 ? '' : 's') ?>
|
||||
</span>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->featuredPost->id]) ?>">
|
||||
<img src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->featuredPost->name]) ?>" alt="<?php echo $this->context->featuredPost->name ?>"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<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>, 
|
||||
<?php endif ?>
|
||||
<?php printf('%d day%s', $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)), $x == 1 ? '' : 's') ?> ago
|
||||
<div class="clear"></div>
|
||||
<?php $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)) ?>
|
||||
<?php if ($x == 0): ?>
|
||||
today
|
||||
<?php elseif ($x == 1):?>
|
||||
yesterday
|
||||
<?php else: ?>
|
||||
<?php printf('%d days ago', $x) ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
<?php endif ?>
|
||||
|
@ -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>
|
||||
@ -27,9 +34,6 @@
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts))
|
||||
$nav []= ['Browse', \Chibi\UrlHelper::route('post', 'list')];
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts))
|
||||
$nav []= ['Favorites', \Chibi\UrlHelper::route('post', 'favorites')];
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::UploadPost))
|
||||
$nav []= ['Upload', \Chibi\UrlHelper::route('post', 'upload')];
|
||||
|
||||
@ -64,7 +68,7 @@
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($this->context->loggedIn): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($this->context->user))): ?>
|
||||
<li class="safety">
|
||||
<ul>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
@ -82,7 +86,7 @@
|
||||
|
||||
<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) : '' ?>">
|
||||
<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') ?>"/>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
@ -99,12 +103,15 @@
|
||||
|
||||
<footer>
|
||||
<div class="main-wrapper">
|
||||
<span>Load: <?php echo sprintf('%.05f', microtime(true) - trueStartTime()) ?>s</span>
|
||||
<span>Load: <?php echo sprintf('%.05f', microtime(true) - $this->context->startTime) ?>s</span>
|
||||
<span>Queries: <?php echo count(queryLogger()->getLogs()) ?></span>
|
||||
<?php if ($this->config->main->debugQueries): ?>
|
||||
<?php if ($this->config->misc->debugQueries): ?>
|
||||
<pre class="debug"><?php echo join('<br>', array_map(function($x) { return preg_replace('/\s+/', ' ', $x); }, queryLogger()->getLogs())) ?></pre>
|
||||
<?php endif ?>
|
||||
<span><a href="<?php echo SZURU_LINK ?>">szurubooru v<?php echo SZURU_VERSION ?></a></span>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListLogs)): ?>
|
||||
<span><a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Logs</a></span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
13
src/Views/log-list.phtml
Normal file
@ -0,0 +1,13 @@
|
||||
<?php if (empty($this->context->transport->logs)): ?>
|
||||
<p class="alert alert-warning">No logs to show.</p>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->logs as $log): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('log', 'view', ['name' => $log]) ?>">
|
||||
<?php echo $log ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
11
src/Views/log-view.phtml
Normal file
@ -0,0 +1,11 @@
|
||||
<?php if (empty($this->context->transport->log)): ?>
|
||||
<p class="alert alert-warning">This log is empty. <a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Go back</a></p>
|
||||
<?php else: ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('log', 'view', ['name' => $this->context->transport->name]) ?>" method="get">
|
||||
Keep only lines that contain:
|
||||
|
||||
<input type="text" name="filter" value="<?php echo $this->context->transport->filter ?>" placeholder="any text…"/>
|
||||
</form>
|
||||
|
||||
<pre><?php echo $this->context->transport->log ?></pre>
|
||||
<?php endif ?>
|
5
src/Views/message.phtml
Normal file
@ -0,0 +1,5 @@
|
||||
<?php if (!empty($this->context->transport->message)): ?>
|
||||
<p class="alert <?php echo $this->context->transport->success ? 'alert-success' : 'alert-error'; ?>">
|
||||
<?php echo $this->context->transport->messageHtml ?>
|
||||
</p>
|
||||
<?php endif ?>
|
50
src/Views/post-edit.phtml
Normal file
@ -0,0 +1,50 @@
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit-post aligned unit">
|
||||
<h1>edit post</h1>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<label>
|
||||
<input type="radio" name="safety" value="<?php echo $safety ?>" <?php if ($this->context->transport->post->safety == $safety) echo 'checked="checked"' ?>/>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
|
||||
</label>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="tags">
|
||||
<label class="left" for="tags">Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags…" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->sharedTag)) ?>"/></div>
|
||||
</div>
|
||||
<input type="hidden" name="tags-token" id="tags-token" value="<?php echo $this->context->transport->tagsToken ?>"/>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="source">
|
||||
<label class="left" for="source">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" id="source" value="<?php echo $this->context->transport->post->source ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="relations">Relations:</label>
|
||||
<div class="input-wrapper"><input type="text" name="relations" id="relations" placeholder="id1,id2,…" value="<?php echo join(',', array_map(function($post) { return $post->id; }, $this->context->transport->post->via('crossref')->sharedPost)) ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="thumb">Thumb:</label>
|
||||
<div class="input-wrapper"><input type="file" name="thumb" id="thumb"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
33
src/Views/post-list-wrapper.phtml
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
$tabs = [];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['All posts', \Chibi\UrlHelper::route('post', 'list')];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['Random', \Chibi\UrlHelper::route('post', 'random')];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['Favorites', \Chibi\UrlHelper::route('post', 'favorites')];
|
||||
if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs []= ['Mass tag', \Chibi\UrlHelper::route('post', 'list', ['query' => isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '', 'source' => 'mass-tag', 'page' => $this->context->transport->paginator->page])];
|
||||
|
||||
$activeTab = 0;
|
||||
if ($this->context->route->simpleActionName == 'random') $activeTab = 1;
|
||||
if ($this->context->route->simpleActionName == 'favorites') $activeTab = 2;
|
||||
if ($this->context->source == 'mass-tag') $activeTab = 3;
|
||||
?>
|
||||
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<?php foreach ($tabs as $i => $tab): ?>
|
||||
<?php list($name, $url) = $tab ?>
|
||||
<?php if ($i == $activeTab): ?>
|
||||
<li class="selected <?php echo TextHelper::humanCaseToKebabCase($name) ?>">
|
||||
<?php else: ?>
|
||||
<li class="<?php echo TextHelper::humanCaseToKebabCase($name) ?>">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo $url ?>">
|
||||
<?php echo $name ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<?php $this->renderFile('post-list') ?>
|
@ -1,3 +1,7 @@
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag' and PrivilegesHelper::confirm(Privilege::MassTag)): ?>
|
||||
<?php $this->renderFile('tag-mass-tag') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (empty($this->context->transport->posts)): ?>
|
||||
<p class="alert alert-warning">No posts to show.</p>
|
||||
<?php else: ?>
|
||||
|
@ -1,9 +1,40 @@
|
||||
<div class="post post-type-<?php echo TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post->type)) ?>">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
<img class="thumb" src="<?php echo \Chibi\UrlHelper::route('post', 'thumb', ['id' => $this->context->post->id]) ?>" alt="@<?php echo $this->context->post->id ?>"/>
|
||||
<?php $classNames = ['post', 'post-type-' . TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post->type))] ?>
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag'): ?>
|
||||
<?php $classNames []= 'taggable' ?>
|
||||
<?php if (in_array($this->context->additionalInfo, array_map(function($x) { return $x->name; }, $this->context->post->sharedTag))): ?>
|
||||
<?php $classNames []= 'tagged' ?>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="<?php echo implode(' ', $classNames) ?>">
|
||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag'): ?>
|
||||
<a class="toggle-tag" href="<?php echo \Chibi\UrlHelper::route('post', 'toggle-tag', ['id' => $this->context->post->id, 'tag' => $this->context->additionalInfo]) ?>" data-text-tagged="Tagged" data-text-untagged="Untagged">
|
||||
<?php echo in_array('tagged', $classNames) ? 'Tagged' : 'Untagged' ?>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
<a class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
<img class="thumb" src="<?php echo \Chibi\UrlHelper::route('post', 'thumb', ['name' => $this->context->post->name]) ?>" alt="@<?php echo $this->context->post->id ?>"/>
|
||||
<?php
|
||||
$x =
|
||||
[
|
||||
'score' => $this->context->post->score,
|
||||
'comments' => $this->context->post->countOwn('comment'),
|
||||
'favs' => $this->context->post->countOwn('favoritee'),
|
||||
];
|
||||
?>
|
||||
<?php if (!empty($x)): ?>
|
||||
<div class="info-bar">
|
||||
<i class="icon-comments"></i> <span><?php echo $this->context->post->countOwn('comment') ?></span>
|
||||
<i class="icon-favs"></i> <span><?php echo $this->context->post->countOwn('favoritee') ?></span>
|
||||
<?php foreach ($x as $key => $val): ?>
|
||||
<?php if ($val == 0): ?>
|
||||
<span class="inactive">
|
||||
<?php else: ?>
|
||||
<span>
|
||||
<?php endif ?>
|
||||
<i class="icon-<?php echo $key ?>"></i>
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -1,19 +1,34 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p>Post created!</p>
|
||||
<?php else: ?>
|
||||
|
||||
<div id="sidebar">
|
||||
<div id="sidebar">
|
||||
<div class="unit">
|
||||
<h1>file upload</h1>
|
||||
<p>Use tags to describe uploaded images. Try to specify characters, their look and shows they are from.</p>
|
||||
<p>Set proper visibility setting if the image isn’t safe for work or you’re not sure it’s 100% <span class="safety-sfw">safe</span>.</p>
|
||||
<p>Only registered users can view <span class="safety-sketchy">sketchy</span> or <span class="safety-nsfw">NSFW</span> content.</p>
|
||||
<p>Set proper visibility setting if the image isn’t safe for work or you’re not sure it’s 100% <span class="safety-safe">safe</span>.</p>
|
||||
<p>Only registered users can view <span class="safety-sketchy">sketchy</span> or <span class="safety-unsafe">NSFW</span> content.</p>
|
||||
<p>Click submit when you’re done.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="inner-content">
|
||||
<div id="upload-step1">
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="selected file">
|
||||
<a href="#">
|
||||
Upload from file
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="url">
|
||||
<a href="#">
|
||||
Upload from URL
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div id="inner-content">
|
||||
<div id="upload-step1">
|
||||
<div class="tab file">
|
||||
<input type=file multiple style="display: none"/>
|
||||
<div id="file-handler-wrapper">
|
||||
<div id="file-handler">
|
||||
@ -21,11 +36,23 @@
|
||||
Or just click on this box.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab url">
|
||||
<div id="url-handler-wrapper">
|
||||
<div id="url-handler">
|
||||
<div class="input-wrapper"><textarea placeholder="Paste some URLs here, one per line." name="urls"></textarea></div>
|
||||
</div>
|
||||
<button type="submit">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div id="upload-step2" data-redirect-url="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">
|
||||
<hr>
|
||||
|
||||
<div class="posts">
|
||||
</div>
|
||||
|
||||
@ -62,9 +89,20 @@
|
||||
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<label><input type="radio" name="safety" value="<?php echo PostSafety::Safe ?>" checked="checked"/> Safe for work</label>
|
||||
<label><input type="radio" name="safety" value="<?php echo PostSafety::Sketchy ?>"/> Sketchy</label>
|
||||
<label><input type="radio" name="safety" value="<?php echo PostSafety::Unsafe ?>"/> Not safe for work</label>
|
||||
<?php $checked = false ?>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<label>
|
||||
<input type="radio" name="safety" value="<?php echo $safety ?>"<?php if (!$checked) echo ' checked="checked"' ?>/>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
|
||||
<?php $checked = true ?>
|
||||
</label>
|
||||
<?php endforeach ?>
|
||||
<input type="hidden" name="anonymous" value="0"/>
|
||||
<label>
|
||||
<input type="checkbox" name="anonymous" value="1"/>
|
||||
Upload anonymously
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="tags">
|
||||
@ -76,14 +114,9 @@
|
||||
<label class="left">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" placeholder="where did you get this from? (optional)"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-no-posts">
|
||||
<p class="alert alert-warning">Well, that’s disappointing…</p>
|
||||
<p><a href="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">Back to post list</a> or <a href="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>">try uploading again</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
@ -28,13 +28,15 @@
|
||||
<div class="unit tags">
|
||||
<h1>tags (<?php echo count($this->context->transport->post->sharedTag) ?>)</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->sharedTag as $tag): ?>
|
||||
<li>
|
||||
<?php $tags = $this->context->transport->post->sharedTag ?>
|
||||
<?php uasort($tags, function($a, $b) { return strnatcasecmp($a->name, $b->name); }) ?>
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<li title="<?php echo $tag->name ?>">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>">
|
||||
<?php echo $tag->name ?>
|
||||
</a>
|
||||
<span class="count">
|
||||
<?php echo TextHelper::useDecimalUnits($this->context->transport->tagDistribution[$tag->name]) ?>
|
||||
<?php echo TextHelper::useDecimalUnits($tag->getPostCount()) ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
@ -44,28 +46,60 @@
|
||||
<div class="unit details">
|
||||
<h1>details</h1>
|
||||
|
||||
<div class="key-value safety">
|
||||
<span class="key">Safety:</span>
|
||||
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(PostSafety::toString($this->context->transport->post->safety)) ?>">
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value uploader">
|
||||
<span class="key">Uploader:</span>
|
||||
<?php if ($this->context->transport->post->uploader): ?>
|
||||
<span class="value" title="<?php echo $val = $this->context->transport->post->uploader->name ?>">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->transport->post->uploader->name]) ?>">
|
||||
<img src="<?php echo htmlentities($this->context->transport->post->uploader->getAvatarUrl(16)) ?>" alt="<?php echo $this->context->transport->post->uploader->name ?>"/>
|
||||
<?php echo $val ?>
|
||||
</a>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="value" title="[deleted user]">
|
||||
[deleted user]
|
||||
<span class="value" title="[unknown user]">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[unknown user]"/>
|
||||
[unknown user]
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<div class="key-value safety">
|
||||
<span class="key">Safety:</span>
|
||||
<span class="value safety-<?php echo $val = TextHelper::camelCaseToHumanCase(PostSafety::toString($this->context->transport->post->safety)) ?>" title="<?php echo $val ?>">
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value score">
|
||||
<span class="key">Score:</span>
|
||||
<span class="value">
|
||||
<?php echo $this->context->transport->post->score ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ScorePost)): ?>
|
||||
[
|
||||
<?php $scoreLink = 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 $scoreLink(0) ?>">
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo $scoreLink(1) ?>">
|
||||
<?php endif ?>
|
||||
vote up
|
||||
</a>
|
||||
|
||||
,
|
||||
|
||||
<?php if ($this->context->score === -1): ?>
|
||||
<a class="simple-action selected" href="<?php echo $scoreLink(0) ?>">
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo $scoreLink(-1) ?>">
|
||||
<?php endif ?>
|
||||
down
|
||||
</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value date">
|
||||
<span class="key">Date:</span>
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d H:i', $this->context->transport->post->upload_date) ?>">
|
||||
@ -86,22 +120,30 @@
|
||||
|
||||
<div class="key-value source">
|
||||
<span class="key">Source:</span>
|
||||
<span class="value" title="<?php echo $val = htmlspecialchars($this->context->transport->post->source) ?>">
|
||||
<span class="value" title="<?php echo $val = htmlspecialchars($this->context->transport->post->source ?: 'unknown') ?>">
|
||||
<?php if (preg_match('/^((https?|ftp):|)\/\//', $val)): ?>
|
||||
<a href="<?php echo $val ?>"><?php echo $val ?></a>
|
||||
<?php else: ?>
|
||||
<?php echo $val ?>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if ($this->context->transport->post->type != PostType::Youtube): ?>
|
||||
<div class="permalink">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" title="Download">
|
||||
<i class="icon-dl"></i>
|
||||
<span class="ext">
|
||||
<?php echo strtoupper(substr($this->context->transport->post->orig_name, strrpos($this->context->transport->post->orig_name, '.') + 1)) ?>
|
||||
<?php $mimes = ['image/jpeg' => 'JPG', 'image/gif' => 'GIF', 'image/png' => 'PNG', 'application/x-shockwave-flash' => 'SWF'] ?>
|
||||
<?php $mime = $this->context->transport->post->mimeType ?>
|
||||
<?php echo isset($mimes[$mime]) ? $mimes[$mime] : 'unknown' ?>
|
||||
</span>
|
||||
<span class="size">
|
||||
<?php echo TextHelper::useBytesUnits($this->context->transport->post->file_size) ?>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<div class="unit favorites">
|
||||
@ -122,6 +164,21 @@
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?php if (count($this->context->transport->post->via('crossref')->sharedPost)): ?>
|
||||
<div class="relations unit">
|
||||
<h1>related</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->via('crossref')->sharedPost as $relatedPost): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $relatedPost->id]) ?>">
|
||||
@<?php echo $relatedPost->id ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="unit options">
|
||||
<h1>options</h1>
|
||||
|
||||
@ -190,6 +247,20 @@
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::FlagPost)): ?>
|
||||
<li class="flag">
|
||||
<?php if ($this->context->flagged): ?>
|
||||
<a class="simple-action inactive" href="#">
|
||||
Flagged
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo \Chibi\UrlHelper::route('post', 'flag', ['id' => $this->context->transport->post->id]) ?>" data-confirm-text="Are you sure you want to flag this post?">
|
||||
Flag for moderator attention
|
||||
</a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<li class="delete">
|
||||
<a class="simple-action" href="<?php echo \Chibi\UrlHelper::route('post', 'delete', ['id' => $this->context->transport->post->id]) ?>" data-confirm-text="Are you sure you want to delete this post?" data-redirect-url="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>">
|
||||
@ -208,55 +279,21 @@
|
||||
|
||||
<div id="inner-content">
|
||||
<div class="post-wrapper post-type-<?php echo strtolower(PostType::toString($this->context->transport->post->type)) ?>">
|
||||
<?php if ($this->context->transport->post->type == PostType::Image): ?>
|
||||
<?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 elseif ($this->context->transport->post->type == PostType::Flash): ?>
|
||||
<?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 endif ?>
|
||||
<?php break ?>
|
||||
<?php case PostType::Youtube: ?>
|
||||
<iframe width="800" height="600" src="//www.youtube.com/embed/<?php echo $this->context->transport->post->orig_name ?>" frameborder="0" allowfullscreen></iframe>
|
||||
<?php break ?>
|
||||
<?php endswitch ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canEditAnything): ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit-post aligned unit">
|
||||
<h1>edit post</h1>
|
||||
<?php if ($editPostPrivileges[Privilege::EditPostSafety]): ?>
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<label>
|
||||
<input type="radio" name="safety" value="<?php echo $safety ?>" <?php if ($this->context->transport->post->safety == $safety) echo 'checked="checked"' ?>/>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?>
|
||||
</label>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($editPostPrivileges[Privilege::EditPostTags]): ?>
|
||||
<div class="tags">
|
||||
<label class="left" for="tags">Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags…" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->sharedTag)) ?>"/></div>
|
||||
</div>
|
||||
<input type="hidden" name="tags-token" id="tags-token" value="<?php echo $this->context->transport->tagsToken ?>"/>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($editPostPrivileges[Privilege::EditPostSource]): ?>
|
||||
<div class="source">
|
||||
<label class="left" for="source">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" id="suorce" value="<?php echo $this->context->transport->post->source ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($editPostPrivileges[Privilege::EditPostThumb]): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="thumb">Thumb:</label>
|
||||
<div class="input-wrapper"><input type="file" name="thumb" id="thumb"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php $this->renderFile('post-edit') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="comments unit">
|
||||
@ -284,6 +321,8 @@
|
||||
<div class="input-wrapper"><textarea name="text" cols="50" rows="3"></textarea></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<button name="sender" type="submit" value="preview">Preview</button>
|
||||
<button name="sender" type="submit" value="submit">Submit</button>
|
||||
|
41
src/Views/tag-list-wrapper.phtml
Normal file
@ -0,0 +1,41 @@
|
||||
<?php $tabs = [] ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListTags)) $tabs['list'] = 'List'; ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::RenameTags)) $tabs['rename'] = 'Rename'; ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::MergeTags)) $tabs['merge'] = 'Merge'; ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs['mass-tag-redirect'] = 'Mass tag'; ?>
|
||||
|
||||
<?php if (count(array_diff($tabs, ['list'])) > 1): ?>
|
||||
<div class="tabs">
|
||||
<nav>
|
||||
<ul>
|
||||
<?php foreach ($tabs as $tab => $name): ?>
|
||||
<?php if ($this->context->route->simpleActionName == $tab): ?>
|
||||
<li class="selected <?php echo $tab ?>">
|
||||
<?php else: ?>
|
||||
<li class="<?php echo $tab ?>">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('tag', $tab) ?>">
|
||||
<?php echo $name ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'merge'): ?>
|
||||
<?php $this->renderFile('tag-merge') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'rename'): ?>
|
||||
<?php $this->renderFile('tag-rename') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'list'): ?>
|
||||
<?php $this->renderFile('tag-list') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'mass-tag-redirect'): ?>
|
||||
<?php $this->renderFile('tag-mass-tag') ?>
|
||||
<?php endif ?>
|
@ -1,55 +1,17 @@
|
||||
<?php $max = max([0]+array_map(function($x) { return $x['post_count']; }, $this->context->transport->tags)); ?>
|
||||
<?php $add = 0.25 ?>
|
||||
<?php $mul = 0.75 / max(1, log(max(1, $max))) ?>
|
||||
<?php $url = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']) ?>
|
||||
<div class="tags">
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->tagDistribution as $tagName => $count): ?>
|
||||
<li class="tag">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tagName]) ?>">
|
||||
<?php echo $tagName . ' (' . $count . ')' ?>
|
||||
<?php foreach ($this->context->transport->tags as $tag): ?>
|
||||
<?php $name = $tag['name'] ?>
|
||||
<?php $count = $tag['post_count'] ?>
|
||||
<li class="tag" title="<?php echo $name ?> (<?php echo $count ?>)">
|
||||
<a href="<?php echo str_replace('_query_', $name, $url) ?>" style="opacity: <?php printf('%.02f', $add + $mul * log($count)) ?>">
|
||||
<?php echo $name . ' (' . $count . ')' ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::MergeTags)): ?>
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'merge') ?>">
|
||||
<h1>merge tags</h1>
|
||||
<div>
|
||||
<label class="left" for="merge-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="merge-source-tag"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="merge-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="target-tag" id="merge-target-tag"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Merge!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::RenameTags)): ?>
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned simple-action" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'rename') ?>">
|
||||
<h1>rename tags</h1>
|
||||
<div>
|
||||
<label class="left" for="rename-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="rename-source-tag"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="rename-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="target-tag" id="rename-target-tag"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Rename!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
21
src/Views/tag-mass-tag.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'mass-tag-redirect') ?>">
|
||||
<h1>mass tag</h1>
|
||||
<div>
|
||||
<label class="left" for="mass-tag-query">Search query:</label>
|
||||
<div class="input-wrapper"><input type="text" name="query" id="mass-tag-query" value="<?php echo isset($this->context->massTagQuery) ? $this->context->massTagQuery : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="mass-tag-tag">Tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tag" id="mass-tag-tag" value="<?php echo isset($this->context->massTagTag) ? $this->context->massTagTag : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Tag!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
21
src/Views/tag-merge.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'merge') ?>">
|
||||
<h1>merge tags</h1>
|
||||
<div>
|
||||
<label class="left" for="merge-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="merge-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="merge-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="target-tag" id="merge-target-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Merge!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
21
src/Views/tag-rename.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="form-wrapper">
|
||||
<form class="aligned simple-action" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'rename') ?>">
|
||||
<h1>rename tags</h1>
|
||||
<div>
|
||||
<label class="left" for="rename-source-tag">Source tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source-tag" id="rename-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="rename-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="target-tag" id="rename-target-tag"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Rename!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,6 +0,0 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Activation completed successfully.
|
||||
<?php if ($this->config->registration->staffActivation): ?>
|
||||
<br>However, your account still must be confirmed by staff.
|
||||
<?php endif ?></p>
|
||||
<?php endif ?>
|
17
src/Views/user-delete.phtml
Normal file
@ -0,0 +1,17 @@
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name]) ?>" method="post" class="delete aligned" autocomplete="off" data-confirm-text="Are you sure you want to delete your account?">
|
||||
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
|
||||
<div class="current-password">
|
||||
<label class="left" for="current-password">Current password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Delete account</button>
|
||||
</div>
|
||||
</form>
|
61
src/Views/user-edit.phtml
Normal file
@ -0,0 +1,61 @@
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'edit', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit aligned" autocomplete="off">
|
||||
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
|
||||
<div class="current-password">
|
||||
<label class="left" for="current-password">Current password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
||||
</div>
|
||||
<hr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="nickname">
|
||||
<label class="left" for="name">Name:</label>
|
||||
<div class="input-wrapper"><input type="text" name="name" id="name" placeholder="New name…" value="<?php echo $this->context->suppliedName ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="email">
|
||||
<label class="left" for="name">E-mail:</label>
|
||||
<div class="input-wrapper"><input type="text" name="email" id="email" placeholder="New e-mail…" value="<?php echo $this->context->suppliedEmail ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="password1">
|
||||
<label class="left" for="password1">New password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="password1" id="password1" placeholder="New password…" value="<?php echo $this->context->suppliedPassword1 ?>"/></div>
|
||||
</div>
|
||||
<div class="password2">
|
||||
<label class="left" for="password2"></label>
|
||||
<div class="input-wrapper"><input type="password" name="password2" id="password2" placeholder="New password… (repeat)" value="<?php echo $this->context->suppliedPassword2 ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="access-rank">
|
||||
<label class="left" for="access-rank">Access rank:</label>
|
||||
<div class="input-wrapper"><select name="access-rank" id="access-rank">
|
||||
<?php foreach (AccessRank::getAll() as $rank): ?>
|
||||
<?php if ($rank == AccessRank::Nobody) continue ?>
|
||||
<?php if (($this->context->suppliedAccessRank != '' and $rank == $this->context->suppliedAccessRank) or ($this->context->suppliedAccessRank == '' and $rank == $this->context->transport->user->access_rank)): ?>
|
||||
<option value="<?php echo $rank ?>" selected="selected">
|
||||
<?php else: ?>
|
||||
<option value="<?php echo $rank ?>">
|
||||
<?php endif ?>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(AccessRank::toString($rank)) ?>
|
||||
</option>
|
||||
<?php endforeach ?>
|
||||
</select></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
@ -1,13 +1,5 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Congratulations, your account was created.
|
||||
<?php if (!empty($this->context->mailSent)): ?>
|
||||
<br>Please wait for activation e-mail.
|
||||
<?php if ($this->config->registration->staffActivation): ?>
|
||||
<br>After this, your registration must be confirmed by staff.
|
||||
<?php endif ?>
|
||||
<?php elseif ($this->config->registration->staffActivation): ?>
|
||||
<br>Your registration must be now confirmed by staff.
|
||||
<?php endif ?></p>
|
||||
<?php $this->renderFile('message') ?>
|
||||
<?php else: ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'register') ?>" class="auth aligned" method="post">
|
||||
<div>
|
||||
@ -16,7 +8,7 @@
|
||||
|
||||
<div>
|
||||
<label class="left" for="name">User name:</label>
|
||||
<div class="input-wrapper"><input id="name" name="name" value="<?php echo $this->context->suppliedName ?>" placeholder="e.g. darth_vader" autocomplete="off"/></div>
|
||||
<div class="input-wrapper"><input type="text" id="name" name="name" value="<?php echo $this->context->suppliedName ?>" placeholder="e.g. darth_vader" autocomplete="off"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -31,18 +23,18 @@
|
||||
|
||||
<div>
|
||||
<label class="left" for="email">E-mail address:</label>
|
||||
<div class="input-wrapper"><input id="email" name="email" value="<?php echo $this->context->suppliedEmail ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/></div>
|
||||
<div class="input-wrapper"><input type="text" id="email" name="email" value="<?php echo $this->context->suppliedEmail ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p id="email-info">Your e-mail will be used to show your <a href="http://gravatar.com/">Gravatar</a>.<br/>Leave blank for random Gravatar.</p>
|
||||
</div>
|
||||
|
||||
<?php if (isset($this->context->transport->errorMessage)): ?>
|
||||
<div>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php $this->renderFile('message') ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"></label>
|
||||
|
21
src/Views/user-select.phtml
Normal file
@ -0,0 +1,21 @@
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<?php $this->renderFile('message') ?>
|
||||
<?php else: ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route($this->context->route->simpleControllerName, $this->context->route->simpleActionName) ?>" method="post" class="auth aligned" autocomplete="off">
|
||||
<div>
|
||||
<label class="left">User:</label>
|
||||
<div class="input-wrapper">
|
||||
<input name="name" placeholder="Name or e-mail address" type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Continue</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif ?>
|
33
src/Views/user-settings.phtml
Normal file
@ -0,0 +1,33 @@
|
||||
<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">Safety:</label>
|
||||
<div class="input-wrapper">
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety))): ?>
|
||||
<label><input type="checkbox" name="safety[]" value="<?php echo $safety ?>"<?php if ($this->context->transport->user->hasEnabledSafety($safety)) echo ' checked="checked"' ?>/>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(PostSafety::toString($safety), true) ?></label>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endless-scrolling">
|
||||
<label class="left" for="endless-scrolling">Endless scrolling:</label>
|
||||
<div class="input-wrapper">
|
||||
<label>
|
||||
<input type="checkbox" id="endless-scrolling" name="endless-scrolling" <?php if ($this->context->transport->user->hasEnabledEndlessScrolling()) echo ' checked="checked"' ?>/>
|
||||
Enabled
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Update settings</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(AccessRank::toString($this->context->transport->user->access_rank)) ?>"><?php echo $val ?></span>
|
||||
</div>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ViewUserEmail)): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ViewUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="key-value email">
|
||||
<span class="key">E-mail:</span>
|
||||
<span class="value" title="<?php echo $val = ($this->context->transport->user->email_unconfirmed ? '(unconfirmed) ' . $this->context->transport->user->email_unconfirmed : $this->context->transport->user->email_confirmed ?: 'none specified') ?>"><?php echo $val ?></span>
|
||||
@ -65,6 +65,20 @@
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::FlagUser)): ?>
|
||||
<li class="flag">
|
||||
<?php if ($this->context->flagged): ?>
|
||||
<a class="simple-action inactive" href="#">
|
||||
Flagged
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a class="simple-action" href="<?php echo \Chibi\UrlHelper::route('user', 'flag', ['name' => $this->context->transport->user->name]) ?>" data-confirm-text="Are you sure you want to flag this user?">
|
||||
Flag for moderator attention
|
||||
</a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<?php if (!$this->context->transport->user->banned): ?>
|
||||
<li class="ban">
|
||||
@ -121,6 +135,18 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<?php if ($this->context->transport->tab == 'settings'): ?>
|
||||
<li class="selected settings">
|
||||
<?php else: ?>
|
||||
<li class="settings">
|
||||
<?php endif ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'settings', ['name' => $this->context->transport->user->name]) ?>">
|
||||
Browsing settings
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($canModifyAnything): ?>
|
||||
<?php if ($this->context->transport->tab == 'edit'): ?>
|
||||
<li class="selected edit">
|
||||
@ -152,95 +178,12 @@
|
||||
<?php $this->renderFile('post-list') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->transport->tab == 'edit'): ?>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'edit', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit aligned" autocomplete="off">
|
||||
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
|
||||
<div class="current-password">
|
||||
<label class="left" for="current-password">Current password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
||||
</div>
|
||||
<hr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="nickname">
|
||||
<label class="left" for="name">Name:</label>
|
||||
<div class="input-wrapper"><input type="text" name="name" id="name" placeholder="New name…" value="<?php echo $this->context->suppliedName ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="email">
|
||||
<label class="left" for="name">E-mail:</label>
|
||||
<div class="input-wrapper"><input type="text" name="email" id="email" placeholder="New e-mail…" value="<?php echo $this->context->suppliedEmail ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="password1">
|
||||
<label class="left" for="password1">New password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="password1" id="password1" placeholder="New password…" value="<?php echo $this->context->suppliedPassword1 ?>"/></div>
|
||||
</div>
|
||||
<div class="password2">
|
||||
<label class="left" for="password2"></label>
|
||||
<div class="input-wrapper"><input type="password" name="password2" id="password2" placeholder="New password… (repeat)" value="<?php echo $this->context->suppliedPassword2 ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="access-rank">
|
||||
<label class="left" for="access-rank">Access rank:</label>
|
||||
<div class="input-wrapper"><select name="access-rank" id="access-rank">
|
||||
<?php foreach (AccessRank::getAll() as $rank): ?>
|
||||
<?php if ($rank == AccessRank::Nobody) continue ?>
|
||||
<?php if (($this->context->suppliedAccessRank != '' and $rank == $this->context->suppliedAccessRank) or ($this->context->suppliedAccessRank == '' and $rank == $this->context->transport->user->access_rank)): ?>
|
||||
<option value="<?php echo $rank ?>" selected="selected">
|
||||
<?php else: ?>
|
||||
<option value="<?php echo $rank ?>">
|
||||
<?php endif ?>
|
||||
<?php echo TextHelper::camelCaseToHumanCase(AccessRank::toString($rank)) ?>
|
||||
</option>
|
||||
<?php endforeach ?>
|
||||
</select></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Account settings updated! <?php if (!empty($this->context->mailSent)) echo 'You will be sent new e-mail address confirmation message soon.' ?></p>
|
||||
<?php elseif (isset($this->context->transport->errorMessage)): ?>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($this->context->transport->tab == 'settings'): ?>
|
||||
<?php $this->renderFile('user-settings') ?>
|
||||
<?php elseif ($this->context->transport->tab == 'edit'): ?>
|
||||
<?php $this->renderFile('user-edit') ?>
|
||||
<?php elseif ($this->context->transport->tab == 'delete'): ?>
|
||||
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit aligned" autocomplete="off" data-confirm-text="Are you sure you want to delete your account?">
|
||||
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
|
||||
<div class="current-password">
|
||||
<label class="left" for="current-password">Current password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<input type="hidden" name="remove" value="1"/>
|
||||
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<p class="alert alert-success">Account settings updated!</p>
|
||||
<?php elseif (isset($this->context->transport->errorMessage)): ?>
|
||||
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<button type="submit">Delete account</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php $this->renderFile('user-delete') ?>
|
||||
<?php endif ?>
|
||||
|
||||
</div>
|
||||
|
54
src/core.php
@ -1,56 +1,36 @@
|
||||
<?php
|
||||
define('SZURU_VERSION', '0.1.0');
|
||||
define('SZURU_VERSION', '0.4.1');
|
||||
define('SZURU_LINK', 'http://github.com/rr-/szurubooru');
|
||||
|
||||
function trueStartTime()
|
||||
{
|
||||
static $time = null;
|
||||
if ($time === null)
|
||||
$time = microtime(true);
|
||||
return $time;
|
||||
}
|
||||
trueStartTime();
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
$startTime = microtime(true);
|
||||
$rootDir = __DIR__ . DS . '..' . DS;
|
||||
|
||||
require_once __DIR__ . '/../lib/php-markdown/Michelf/Markdown.php';
|
||||
require_once __DIR__ . '/../lib/redbean/RedBean/redbean.inc.php';
|
||||
require_once __DIR__ . '/../lib/chibi-core/Facade.php';
|
||||
require_once __DIR__ . '/../lib/chibi-core/Registry.php';
|
||||
$requiredExtensions = ['pdo', 'pdo_sqlite', 'gd', 'openssl'];
|
||||
foreach ($requiredExtensions as $ext)
|
||||
if (!extension_loaded($ext))
|
||||
die('PHP extension "' . $ext . '" must be enabled to continue.' . PHP_EOL);
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
setlocale(LC_CTYPE, 'en_US.UTF-8');
|
||||
ini_set('memory_limit', '128M');
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
|
||||
function configFactory()
|
||||
{
|
||||
static $config = null;
|
||||
require_once $rootDir . 'lib' . DS . 'php-markdown' . DS . 'Michelf' . DS . 'Markdown.php';
|
||||
require_once $rootDir . 'lib' . DS . 'redbean' . DS . 'RedBean' . DS . 'redbean.inc.php';
|
||||
require_once $rootDir . 'lib' . DS . 'chibi-core' . DS . 'Facade.php';
|
||||
|
||||
if ($config === null)
|
||||
{
|
||||
$config = new \Chibi\Config();
|
||||
$configPaths =
|
||||
[
|
||||
__DIR__ . DS . '../config.ini',
|
||||
__DIR__ . DS . '../local.ini',
|
||||
];
|
||||
$configPaths = array_filter($configPaths, 'file_exists');
|
||||
\Chibi\AutoLoader::init(__DIR__);
|
||||
\Chibi\Facade::init();
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$context->startTime = $startTime;
|
||||
$context->rootDir = $rootDir;
|
||||
|
||||
foreach ($configPaths as $path)
|
||||
{
|
||||
$config->loadIni($path);
|
||||
}
|
||||
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
$config = configFactory();
|
||||
R::setup('sqlite:' . $config->main->dbPath);
|
||||
R::freeze(true);
|
||||
R::dependencies(['tag' => ['post'], 'favoritee' => ['post', 'user'], 'comment' => ['post', 'user']]);
|
||||
|
||||
//wire models
|
||||
\Chibi\AutoLoader::init([__DIR__ . '/../' . $config->chibi->userCodeDir, __DIR__]);
|
||||
foreach (\Chibi\AutoLoader::getAllIncludablePaths() as $path)
|
||||
if (preg_match('/Model/', $path))
|
||||
\Chibi\AutoLoader::safeInclude($path);
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once 'src/core.php';
|
||||
$config = configFactory();
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
$dbVersion = Model_Property::get('db-version');
|
||||
printf('DB version = %d' . PHP_EOL, $dbVersion);
|
||||
@ -17,6 +17,7 @@ foreach ($upgrades as $upgradePath)
|
||||
{
|
||||
printf('Executing %s...' . PHP_EOL, $upgradePath);
|
||||
$upgradeSql = file_get_contents($upgradePath);
|
||||
$upgradeSql = preg_replace('/^[ \t]+(.*);/m', '\0--', $upgradeSql);
|
||||
$queries = preg_split('/;\s*[\r\n]+/s', $upgradeSql);
|
||||
$queries = array_map('trim', $queries);
|
||||
foreach ($queries as $query)
|
||||
|