mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
96 Commits
Author | SHA1 | Date | |
---|---|---|---|
66229e86be | |||
6d0ee4e03a | |||
94412a25bb | |||
426e104bbe | |||
fa251e60b6 | |||
34b9a80ba7 | |||
82b0d9a63a | |||
06cdebaccb | |||
c29a002c06 | |||
cb489d1eca | |||
a1378c98b4 | |||
e725f8d554 | |||
e43881e03f | |||
ff8bb761ee | |||
3a2a686b6c | |||
e6b37afa8c | |||
b144321c76 | |||
ae09f20910 | |||
ec16073539 | |||
0b10221fed | |||
2aefafa473 | |||
72946c3922 | |||
975da67d33 | |||
f2510ac8c0 | |||
4455284bdb | |||
5827626deb | |||
4ce4ea6f70 | |||
a4fadb218b | |||
f59b92e06c | |||
9eee8ba612 | |||
f783552820 | |||
c0f52ecf28 | |||
395ac3033f | |||
6af3a0e42b | |||
1baceb5816 | |||
0b6a0337fe | |||
4b08686393 | |||
2bac28a553 | |||
28037af029 | |||
4420fa588d | |||
db8e13ec35 | |||
1624fd5f63 | |||
705e3dfba1 | |||
dd498cf18d | |||
ddbecdb16f | |||
b879a7c38b | |||
38771eb7be | |||
b86aaf90a3 | |||
4469767d8f | |||
43a33e579d | |||
2bad17ebdb | |||
1352aba438 | |||
eee6421775 | |||
65c6caa13c | |||
e7a0fdae26 | |||
f3a5de67e7 | |||
532fe9f7e6 | |||
18bfd6605d | |||
0c5fc7e03f | |||
3e99a6336c | |||
80b9542c2d | |||
4a69084a8b | |||
7a5d97e153 | |||
e36498f709 | |||
5148f9162d | |||
620d1204f7 | |||
1a3f77175b | |||
db1d8383fd | |||
27c780602c | |||
83a966f1af | |||
8161bc9c88 | |||
c99596d12b | |||
b22e74c0e9 | |||
6a407fc87a | |||
91b0432067 | |||
f01f55cc8b | |||
0b55dfad04 | |||
35cdc0cf3a | |||
d170e3b526 | |||
ac1997d4d0 | |||
d085ffe39a | |||
d2946e0148 | |||
d01a087b30 | |||
36e2e5827c | |||
752cbbd016 | |||
37cc858821 | |||
a869c1da1e | |||
100303173e | |||
fd9433a2e3 | |||
be3b39bf42 | |||
15486b6e9a | |||
1fcced20f1 | |||
56622b8e9d | |||
4a9cc4b3bc | |||
b1fb329fc7 | |||
306c6478b4 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -4,3 +4,6 @@
|
|||||||
[submodule "php-markdown"]
|
[submodule "php-markdown"]
|
||||||
path = lib/php-markdown
|
path = lib/php-markdown
|
||||||
url = https://github.com/michelf/php-markdown.git
|
url = https://github.com/michelf/php-markdown.git
|
||||||
|
[submodule "lib/chibi-sql"]
|
||||||
|
path = lib/chibi-sql
|
||||||
|
url = https://github.com/rr-/chibi-sql.git
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[chibi]
|
[chibi]
|
||||||
prettyPrint=1
|
enableCache=1
|
||||||
|
|
||||||
[main]
|
[main]
|
||||||
dbDriver = "sqlite"
|
dbDriver = "sqlite"
|
||||||
@ -30,6 +30,8 @@ paths[privacy]=./data/privacy.md
|
|||||||
[browsing]
|
[browsing]
|
||||||
usersPerPage=8
|
usersPerPage=8
|
||||||
postsPerPage=20
|
postsPerPage=20
|
||||||
|
logsPerPage=250
|
||||||
|
tagsPerPage=100
|
||||||
thumbWidth=150
|
thumbWidth=150
|
||||||
thumbHeight=150
|
thumbHeight=150
|
||||||
thumbStyle=outside
|
thumbStyle=outside
|
||||||
@ -42,7 +44,8 @@ maxRelatedPosts=50
|
|||||||
[comments]
|
[comments]
|
||||||
minLength = 5
|
minLength = 5
|
||||||
maxLength = 2000
|
maxLength = 2000
|
||||||
commentsPerPage = 20
|
commentsPerPage = 10
|
||||||
|
maxCommentsInList = 5
|
||||||
|
|
||||||
[registration]
|
[registration]
|
||||||
staffActivation = 0
|
staffActivation = 0
|
||||||
@ -70,7 +73,7 @@ uploadPost=registered
|
|||||||
listPosts=anonymous
|
listPosts=anonymous
|
||||||
listPosts.sketchy=registered
|
listPosts.sketchy=registered
|
||||||
listPosts.unsafe=registered
|
listPosts.unsafe=registered
|
||||||
listPosts.hidden=nobody
|
listPosts.hidden=admin
|
||||||
viewPost=anonymous
|
viewPost=anonymous
|
||||||
viewPost.sketchy=registered
|
viewPost.sketchy=registered
|
||||||
viewPost.unsafe=registered
|
viewPost.unsafe=registered
|
||||||
@ -84,12 +87,11 @@ editPostThumb=moderator
|
|||||||
editPostSource=moderator
|
editPostSource=moderator
|
||||||
editPostRelations.own=registered
|
editPostRelations.own=registered
|
||||||
editPostRelations.all=moderator
|
editPostRelations.all=moderator
|
||||||
editPostFile.all=moderator
|
editPostFile=moderator
|
||||||
editPostFile.own=moderator
|
massTag.own=registered
|
||||||
hidePost.own=moderator
|
massTag.all=power-user
|
||||||
hidePost.all=moderator
|
hidePost=moderator
|
||||||
deletePost.own=moderator
|
deletePost=moderator
|
||||||
deletePost.all=moderator
|
|
||||||
featurePost=moderator
|
featurePost=moderator
|
||||||
scorePost=registered
|
scorePost=registered
|
||||||
flagPost=registered
|
flagPost=registered
|
||||||
@ -117,11 +119,12 @@ listComments=anonymous
|
|||||||
addComment=registered
|
addComment=registered
|
||||||
deleteComment.own=registered
|
deleteComment.own=registered
|
||||||
deleteComment.all=moderator
|
deleteComment.all=moderator
|
||||||
|
editComment.own=registered
|
||||||
|
editComment.all=admin
|
||||||
|
|
||||||
listTags=anonymous
|
listTags=anonymous
|
||||||
mergeTags=moderator
|
mergeTags=moderator
|
||||||
renameTags=moderator
|
renameTags=moderator
|
||||||
massTag=moderator
|
|
||||||
|
|
||||||
listLogs=moderator
|
listLogs=moderator
|
||||||
viewLog=moderator
|
viewLog=moderator
|
||||||
|
Submodule lib/chibi-core updated: 971fba5cb9...c660801c2b
1
lib/chibi-sql
Submodule
1
lib/chibi-sql
Submodule
Submodule lib/chibi-sql added at a5d7a03965
@ -1,30 +1,30 @@
|
|||||||
form.auth {
|
#content form {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.auth label.left {
|
#content form label {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.auth p {
|
#content form p {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.auth .help {
|
#content form .help {
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
form.auth .help p {
|
#content form .help p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
form.auth .help label+div {
|
#content form .help label+div {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
form.auth .help ul {
|
#content form .help ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
12
public_html/media/css/comment-edit.css
Normal file
12
public_html/media/css/comment-edit.css
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.preview {
|
||||||
|
border: 1px solid yellow;
|
||||||
|
background: url('../img/preview.png') lemonchiffon;
|
||||||
|
padding: 0.5em;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.edit-comment textarea,
|
||||||
|
form.add-comment textarea {
|
||||||
|
width: 50em;
|
||||||
|
height: 8em;
|
||||||
|
}
|
@ -26,3 +26,8 @@
|
|||||||
.small-screen .comment-group .comments {
|
.small-screen .comment-group .comments {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hellip {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
@ -22,27 +22,35 @@
|
|||||||
.comment {
|
.comment {
|
||||||
clear: left;
|
clear: left;
|
||||||
}
|
}
|
||||||
.comment .date:before {
|
|
||||||
content: ' on ';
|
|
||||||
margin: 0 0.2em;
|
|
||||||
}
|
|
||||||
.comment .date {
|
.comment .date {
|
||||||
|
margin: 0 0.2em 0 0.75em;
|
||||||
color: silver;
|
color: silver;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment .date,
|
.comment .date,
|
||||||
|
.comment .edit,
|
||||||
.comment .delete {
|
.comment .delete {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
.comment .edit:before,
|
||||||
.comment .delete:before {
|
.comment .delete:before {
|
||||||
margin-left: 0.2em;
|
margin-left: 0.2em;
|
||||||
content: ' [';
|
content: ' [';
|
||||||
color: silver;
|
color: silver;
|
||||||
}
|
}
|
||||||
|
.comment .edit:after,
|
||||||
.comment .delete:after {
|
.comment .delete:after {
|
||||||
content: ']';
|
content: ']';
|
||||||
color: silver;
|
color: silver;
|
||||||
}
|
}
|
||||||
|
.comment .edit a,
|
||||||
.comment .delete a {
|
.comment .delete a {
|
||||||
color: silver;
|
color: silver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment .edit a:hover,
|
||||||
|
.comment .delete a:hover,
|
||||||
|
.comment .edit a:focus,
|
||||||
|
.comment .delete a:focus {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
@ -17,6 +17,8 @@ body {
|
|||||||
color: black;
|
color: black;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: scroll;
|
||||||
font-family: 'Droid Sans', sans-serif;
|
font-family: 'Droid Sans', sans-serif;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
@ -33,7 +35,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-wrapper {
|
.main-wrapper {
|
||||||
margin: 0 1.5em;
|
margin: 0 auto;
|
||||||
|
padding: 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -70,8 +73,8 @@ body {
|
|||||||
}
|
}
|
||||||
#top-nav li.main-nav-item a:focus,
|
#top-nav li.main-nav-item a:focus,
|
||||||
#top-nav li.main-nav-item a:hover {
|
#top-nav li.main-nav-item a:hover {
|
||||||
color: firebrick;
|
color: hsl(0,70%,45%);
|
||||||
border-bottom: 3px solid firebrick;
|
border-bottom: 3px solid hsl(0,70%,45%);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,11 +88,10 @@ body {
|
|||||||
}
|
}
|
||||||
#top-nav li.search input {
|
#top-nav li.search input {
|
||||||
border: 0;
|
border: 0;
|
||||||
height: 20px;
|
height: 28px;
|
||||||
line-height: 20px;
|
line-height: 28px;
|
||||||
padding: 4px 10px;
|
padding: 0 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#top-nav li.safety {
|
#top-nav li.safety {
|
||||||
@ -131,11 +133,9 @@ body {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
footer {
|
footer .main-wrapper {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 1em 0;
|
margin-top: 1em;
|
||||||
padding-top: 0.5em;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
font-size: small;
|
font-size: small;
|
||||||
color: silver;
|
color: silver;
|
||||||
}
|
}
|
||||||
@ -151,11 +151,16 @@ footer a {
|
|||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
float: left;
|
float: left;
|
||||||
width: 256px;
|
width: 240px;
|
||||||
margin-right: 2em;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
#sidebar h1 {
|
#sidebar h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
#sidebar+#inner-content {
|
||||||
|
margin-left: 255px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -169,34 +174,25 @@ footer a {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#inner-content {
|
.unit {
|
||||||
overflow: hidden;
|
margin: 2.5em 0;
|
||||||
padding-bottom: 2em;
|
|
||||||
}
|
}
|
||||||
|
#sidebar .unit:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
#small-screen { display: none; }
|
||||||
|
|
||||||
.small-screen #sidebar {
|
@media only screen and (max-width:700px) {
|
||||||
|
#small-screen { display: block; }
|
||||||
|
body #sidebar {
|
||||||
float: none;
|
float: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 0 1em 0;
|
}
|
||||||
}
|
#inner-content {
|
||||||
.small-screen #inner-content {
|
|
||||||
float: none;
|
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
margin-left: 0;
|
||||||
|
margin-bottom: 2em;
|
||||||
.bottom-unit {
|
}
|
||||||
padding: 0.5em 1em;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
margin: 1em 0 2em 0;
|
|
||||||
}
|
|
||||||
.left-unit {
|
|
||||||
margin: 0 0 1.5em 0;
|
|
||||||
padding: 0.75em;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
padding-left: 0;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -217,7 +213,7 @@ hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: firebrick;
|
color: hsl(0,70%,45%);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
@ -232,7 +228,7 @@ i[class*='icon-'] {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
a i[class*='icon-'] {
|
a i[class*='icon-'] {
|
||||||
background-color: firebrick;
|
background-color: hsl(0,70%,45%);
|
||||||
}
|
}
|
||||||
a:focus i[class*='icon-'],
|
a:focus i[class*='icon-'],
|
||||||
a:hover i[class*='icon-'] {
|
a:hover i[class*='icon-'] {
|
||||||
@ -241,42 +237,38 @@ a:hover i[class*='icon-'] {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
form.aligned input,
|
.form-row>label {
|
||||||
form.aligned button {
|
display: inline-block;
|
||||||
vertical-align: text-top;
|
|
||||||
}
|
|
||||||
form.aligned label {
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
|
||||||
form.aligned label.left {
|
|
||||||
display: inline-block;
|
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
width: 5em;
|
width: 7em;
|
||||||
min-height: 1em;
|
min-height: 1em;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
form.aligned>div {
|
label,
|
||||||
margin-bottom: 0.5em;
|
input:not([type=radio]):not([type=checkbox]):not([type=file]),
|
||||||
clear: left;
|
select,
|
||||||
}
|
button {
|
||||||
form.aligned label,
|
-webkit-box-sizing: border-box !important;
|
||||||
form.aligned input,
|
-moz-box-sizing: border-box !important;
|
||||||
form.aligned select,
|
box-sizing: border-box !important;
|
||||||
form.aligned button {
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 20px;
|
line-height: 24px;
|
||||||
|
height: 34px;
|
||||||
}
|
}
|
||||||
form.aligned label,
|
label,
|
||||||
form.aligned input,
|
input,
|
||||||
form.aligned select {
|
select {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 11pt;
|
||||||
}
|
}
|
||||||
form.aligned input[type=file] {
|
input[type=file] {
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
form.aligned input[type=radio],
|
input[type=radio],
|
||||||
form.aligned input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
width: auto;
|
width: auto;
|
||||||
max-width: auto;
|
max-width: auto;
|
||||||
margin: 0 10px 0 0;
|
margin: 0 10px 0 0;
|
||||||
@ -284,60 +276,58 @@ form.aligned input[type=checkbox] {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 12pt;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
color: white;
|
||||||
|
background: hsl(0,70%,60%);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: hsl(0,75%,50%);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.input-wrapper ul.tagit,
|
|
||||||
.input-wrapper input,
|
|
||||||
.input-wrapper textarea,
|
|
||||||
.input-wrapper select {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
label,
|
|
||||||
input,
|
|
||||||
select,
|
|
||||||
button {
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 11pt;
|
|
||||||
}
|
|
||||||
ul.tagit,
|
ul.tagit,
|
||||||
select,
|
select,
|
||||||
textarea,
|
textarea,
|
||||||
input:not([type=radio]):not([type=checkbox]):not([type=file]) {
|
input:not([type=radio]):not([type=checkbox]):not([type=file]) {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 3px;
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tagit {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
ul.tagit input {
|
ul.tagit input {
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
}
|
line-height: auto !important;
|
||||||
button {
|
height: auto !important;
|
||||||
font-size: 115%;
|
margin: -4px 0 !important;
|
||||||
padding: 0.2em 0.7em;
|
|
||||||
color: white;
|
|
||||||
background: cornflowerblue;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: royalblue;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.tabs ul {
|
.tabs ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin: -4px 0 1em 0;
|
margin: 0 0 1em 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 3px solid #eee;
|
||||||
}
|
}
|
||||||
.tabs li {
|
.tabs li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -346,22 +336,22 @@ button:hover {
|
|||||||
.tabs li a {
|
.tabs li a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
margin: 5px 0 -1px 0;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border: 1px none;
|
border: 3px solid rgba(238, 238, 238, 0);
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 3px solid #eee;
|
||||||
color: silver;
|
color: silver;
|
||||||
|
margin: 0 0 -3px 0;
|
||||||
}
|
}
|
||||||
.tabs li.selected a {
|
.tabs li.selected a {
|
||||||
border: 1px solid #ccc;
|
border: 3px solid #eee;
|
||||||
border-bottom: none;
|
border-bottom-color: rgba(238, 238, 238, 0);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs li a:hover,
|
.tabs li a:hover,
|
||||||
.tabs li a:focus {
|
.tabs li a:focus {
|
||||||
color: firebrick;
|
color: hsl(0,70%,45%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -372,7 +362,7 @@ button:hover {
|
|||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 2em auto !important;
|
margin: 2em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-success {
|
.alert-success {
|
||||||
@ -398,15 +388,7 @@ button:hover {
|
|||||||
clear: both;
|
clear: both;
|
||||||
height: 1px; /* ghost top margin in firefox */
|
height: 1px; /* ghost top margin in firefox */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 0 -1px 0;
|
margin: -1px 0 0 0;
|
||||||
}
|
|
||||||
|
|
||||||
pre.debug {
|
|
||||||
margin-left: 1em;
|
|
||||||
text-align: left;
|
|
||||||
color: black;
|
|
||||||
white-space: normal;
|
|
||||||
text-indent: -1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.spoiler:before,
|
.spoiler:before,
|
||||||
@ -443,3 +425,9 @@ blockquote>*:first-child {
|
|||||||
blockquote>*:last-child {
|
blockquote>*:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-state-default,
|
||||||
|
.ui-widget-content .ui-state-default,
|
||||||
|
.ui-widget-header .ui-state-default {
|
||||||
|
color: hsla(0,70%,45%,0.8) !important;
|
||||||
|
}
|
||||||
|
30
public_html/media/css/debug.css
Normal file
30
public_html/media/css/debug.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
div.debug {
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-size: 90%;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 1em;
|
||||||
|
color: black;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
div.debug pre {
|
||||||
|
margin-left: 1em;
|
||||||
|
white-space: normal;
|
||||||
|
text-indent: -1em;
|
||||||
|
}
|
||||||
|
div.debug pre.query {
|
||||||
|
color: maroon;
|
||||||
|
}
|
||||||
|
div.debug pre.bindings {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
div.debug pre.bindings .value {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
div.debug pre.query span {
|
||||||
|
background: rgba(255, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
div.debug pre.query span:hover {
|
||||||
|
background: white;
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
code {
|
code {
|
||||||
margin: 0 0.5em;
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
@ -16,16 +16,15 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content .main-wrapper>* {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
min-width: 500px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.small-screen #content {
|
@media only screen and (max-width:700px) {
|
||||||
|
#content .main-wrapper>* {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
}
|
||||||
max-width: 500px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#content .body {
|
#content .body {
|
||||||
@ -45,7 +44,7 @@
|
|||||||
#content .footer {
|
#content .footer {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
color: dimgray;
|
color: dimgray;
|
||||||
margin: 0.5em 0 3em 0;
|
margin: 0.5em auto 3em auto;
|
||||||
}
|
}
|
||||||
#content .footer .left {
|
#content .footer .left {
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
#content form {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
#content input {
|
#content input {
|
||||||
margin: 0 1em;
|
margin: 0 1em;
|
||||||
height: 25px;
|
max-width: 50%;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre strong {
|
pre strong {
|
||||||
|
@ -34,6 +34,6 @@
|
|||||||
|
|
||||||
.paginator li a:focus,
|
.paginator li a:focus,
|
||||||
.paginator li a:hover {
|
.paginator li a:hover {
|
||||||
border: 1px solid firebrick;
|
border: 1px solid hsl(0,70%,50%);
|
||||||
background: pink;
|
background: pink;
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
.post {
|
.post {
|
||||||
margin: 0.5em;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
.posts-wrapper {
|
.posts-wrapper {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.posts {
|
.posts {
|
||||||
margin: 0 auto;
|
margin: -8px auto 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-wrapper {
|
.form-wrapper {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
.small-screen .form-wrapper {
|
.small-screen .form-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
form.aligned {
|
#content form {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 24em;
|
width: 24em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
form.aligned label.left {
|
#content form label {
|
||||||
width: 7em;
|
width: 9em;
|
||||||
}
|
}
|
||||||
form h1 {
|
#content form h1 {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
.post .link:focus,
|
.post .link:focus,
|
||||||
.post .link:hover {
|
.post .link:hover {
|
||||||
border: 1px solid firebrick;
|
border: 1px solid hsl(0,70%,50%);
|
||||||
box-shadow: 0.25em 0.25em pink;
|
box-shadow: 0.25em 0.25em pink;
|
||||||
}
|
}
|
||||||
.post .link:focus img.thumb,
|
.post .link:focus img.thumb,
|
||||||
@ -83,7 +83,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.post .info-bar:before {
|
.post .info-bar:before {
|
||||||
border-top: 1px solid firebrick;
|
border-top: 1px solid hsl(0,70%,50%);
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -8,13 +8,10 @@
|
|||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
#upload-step1 {
|
||||||
margin-bottom: 1em;
|
display: table;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.tab.url {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-handler-wrapper {
|
#file-handler-wrapper {
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -30,13 +27,21 @@
|
|||||||
}
|
}
|
||||||
#file-handler.active {
|
#file-handler.active {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
border-color: firebrick;
|
border-color: hsl(0,70%,50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#url-handler textarea {
|
#url-handler {
|
||||||
width: 100%;
|
margin-top: 0.5em;
|
||||||
height: 10em;
|
position: relative;
|
||||||
margin-bottom: 0.5em;
|
}
|
||||||
|
#url-handler .input-wrapper {
|
||||||
|
margin-right: 8.5em;
|
||||||
|
}
|
||||||
|
#url-handler button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post .thumbnail {
|
.post .thumbnail {
|
||||||
@ -109,19 +114,6 @@
|
|||||||
font-size: 130%;
|
font-size: 130%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post label {
|
|
||||||
line-height: 33px;
|
|
||||||
}
|
|
||||||
.post label.left {
|
|
||||||
display: inline-block;
|
|
||||||
width: 60px;
|
|
||||||
padding-right: 10px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.post .safety label:not(.left) {
|
|
||||||
margin-right: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post .file-name strong {
|
.post .file-name strong {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -129,7 +121,7 @@
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 33px;
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.safety-safe {
|
.safety-safe {
|
||||||
@ -149,10 +141,30 @@ ul.tagit {
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submit-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
#the-submit {
|
#the-submit {
|
||||||
margin: 0 0 0 205px;
|
margin: 0 auto;
|
||||||
|
font-size: 14.5pt;
|
||||||
|
padding: 0.35em 2em;
|
||||||
|
height: auto;
|
||||||
|
line-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post .form-wrapper {
|
.post .form-wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#lightbox {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
max-width: 400px;
|
||||||
|
max-height: 400px;
|
||||||
|
margin-top: -50%;
|
||||||
|
margin-left: -50%;
|
||||||
|
background: white;
|
||||||
|
border: 0.5em solid white;
|
||||||
|
box-shadow: 0 0 0 3px #eee;
|
||||||
|
}
|
@ -55,13 +55,24 @@ embed {
|
|||||||
background-color: silver;
|
background-color: silver;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar .uploader img {
|
#sidebar .uploader .date {
|
||||||
vertical-align: middle;
|
font-size: 9pt !important;
|
||||||
margin: 0 0.5em 0 0;
|
color: gray;
|
||||||
width: 16px;
|
display: inline-block;
|
||||||
height: 16px;
|
position: relative;
|
||||||
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=16');
|
top: -5px;
|
||||||
}
|
}
|
||||||
|
#sidebar .uploader img {
|
||||||
|
vertical-align: text-top;
|
||||||
|
float: left;
|
||||||
|
margin: 3px 8px 0 0;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=25');
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .unit.details { margin-bottom: 1.5em; }
|
||||||
|
#sidebar .unit.hl-options { margin-top: 1.5em; }
|
||||||
|
|
||||||
#sidebar .safety-safe {
|
#sidebar .safety-safe {
|
||||||
color: #43aa43;
|
color: #43aa43;
|
||||||
@ -80,17 +91,19 @@ embed {
|
|||||||
|
|
||||||
i.icon-prev {
|
i.icon-prev {
|
||||||
background-position: -12px -1px;
|
background-position: -12px -1px;
|
||||||
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
i.icon-next {
|
i.icon-next {
|
||||||
background-position: -1px -1px;
|
background-position: -1px -1px;
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
i.icon-prev,
|
i.icon-prev,
|
||||||
i.icon-next {
|
i.icon-next {
|
||||||
margin: 0 8px;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
i.icon-dl {
|
i.icon-dl {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
@ -98,14 +111,33 @@ i.icon-dl {
|
|||||||
background-position: -22px -1px;
|
background-position: -22px -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.permalink {
|
i.icon-edit {
|
||||||
margin: 1em 0;
|
margin: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: -43px -22px;
|
||||||
}
|
}
|
||||||
.permalink .icon-dl {
|
|
||||||
|
i.icon-fav {
|
||||||
|
margin: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.add-fav i.icon-fav {
|
||||||
|
background-position: -1px -22px;
|
||||||
|
}
|
||||||
|
.rem-fav i.icon-fav {
|
||||||
|
background-position: -22px -22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hl-option {
|
||||||
|
margin: 0.4em 0;
|
||||||
|
}
|
||||||
|
.hl-option i[class^='icon'] {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 1em;
|
|
||||||
}
|
}
|
||||||
.permalink span {
|
.hl-option span {
|
||||||
|
padding-left: 0.6em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.permalink .ext:after {
|
.permalink .ext:after {
|
||||||
@ -133,11 +165,27 @@ i.icon-dl {
|
|||||||
margin: 2px;
|
margin: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.edit-post {
|
#inner-content {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.unit.edit-post {
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 1em;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
box-shadow: 0 0 1em 1em rgba(255, 255, 255, 0.8);
|
||||||
|
z-index: 99;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
form.edit-post .safety label:not(.left) {
|
.unit.edit-post ul.tagit,
|
||||||
margin-right: 0.75em;
|
.unit.edit-post input:not([type=file]) {
|
||||||
|
background: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
.unit.edit-post ul.tagit input {
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
ul.tagit {
|
ul.tagit {
|
||||||
display: block;
|
display: block;
|
||||||
@ -145,15 +193,3 @@ ul.tagit {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
|
||||||
border: 1px solid yellow;
|
|
||||||
background: url('../img/preview.png') lemonchiffon;
|
|
||||||
padding: 0.5em;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.add-comment textarea {
|
|
||||||
width: 50em;
|
|
||||||
height: 8em;
|
|
||||||
}
|
|
||||||
|
@ -17,22 +17,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-wrapper {
|
.form-wrapper {
|
||||||
width: 50%;
|
|
||||||
max-width: 24em;
|
max-width: 24em;
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
.small-screen .form-wrapper {
|
.small-screen .form-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
form.aligned {
|
#content form label {
|
||||||
text-align: left;
|
width: 9em;
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
form.aligned label.left {
|
#content form h1 {
|
||||||
width: 7em;
|
|
||||||
}
|
|
||||||
form h1 {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,5 +54,5 @@ nav.sort-styles li {
|
|||||||
padding-bottom: 0.2em;
|
padding-bottom: 0.2em;
|
||||||
}
|
}
|
||||||
nav.sort-styles li.active {
|
nav.sort-styles li.active {
|
||||||
border-bottom: 3px solid firebrick;
|
border-bottom: 3px solid hsl(0,70%,50%);
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,3 @@
|
|||||||
.user img {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
float: left;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
.user h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
|
||||||
line-height: 1.5em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
margin-right: 1em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user .details {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 25em;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.sort-styles ul {
|
nav.sort-styles ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin: 0 0 2.5em 0;
|
margin: 0 0 2.5em 0;
|
||||||
@ -35,5 +11,41 @@ nav.sort-styles li {
|
|||||||
padding-bottom: 0.2em;
|
padding-bottom: 0.2em;
|
||||||
}
|
}
|
||||||
nav.sort-styles li.active {
|
nav.sort-styles li.active {
|
||||||
border-bottom: 3px solid firebrick;
|
border-bottom: 3px solid hsl(0,70%,50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.users {
|
||||||
|
column-width: 20em;
|
||||||
|
-moz-column-width: 20em;
|
||||||
|
-webkit-column-width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
text-align: initial;
|
||||||
|
line-height: 1.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user a.avatar {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.user img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
.user .details {
|
||||||
|
display: inline-block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#sidebar {
|
#sidebar {
|
||||||
width: 220px;
|
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,22 +12,12 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.settings label.left,
|
#content form {
|
||||||
form.delete label.left,
|
max-width: 30em;
|
||||||
form.edit label.left {
|
|
||||||
width: 9em;
|
|
||||||
}
|
}
|
||||||
|
#content form label {
|
||||||
form.settings .alert,
|
width: 10em;
|
||||||
form.delete .alert,
|
}
|
||||||
form.edit .alert {
|
#content form .alert {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.settings input,
|
|
||||||
form.delete input,
|
|
||||||
form.edit select,
|
|
||||||
form.edit input {
|
|
||||||
width: 16em;
|
|
||||||
max-width: 90%;
|
|
||||||
}
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 1.0 KiB |
125
public_html/media/js/comment-edit.js
Normal file
125
public_html/media/js/comment-edit.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
$(function()
|
||||||
|
{
|
||||||
|
function onDomUpdate()
|
||||||
|
{
|
||||||
|
$('form.edit-comment textarea, form.add-comment textarea')
|
||||||
|
.bindOnce('exit-confirmation', 'change keyp', function(e)
|
||||||
|
{
|
||||||
|
enableExitConfirmation();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('form.edit-comment, form.add-comment')
|
||||||
|
.bindOnce('comment-submit', 'submit', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
rememberLastSearchQuery();
|
||||||
|
|
||||||
|
var formDom = $(this);
|
||||||
|
if (formDom.hasClass('inactive'))
|
||||||
|
return;
|
||||||
|
formDom.addClass('inactive');
|
||||||
|
formDom.find(':input').attr('readonly', true);
|
||||||
|
|
||||||
|
var url = formDom.attr('action') + '?json';
|
||||||
|
var fd = new FormData(formDom[0]);
|
||||||
|
|
||||||
|
var preview = false;
|
||||||
|
$.each(formDom.serializeArray(), function(i, x)
|
||||||
|
{
|
||||||
|
if (x.name == 'sender' && x.value == 'preview')
|
||||||
|
preview = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var ajaxData =
|
||||||
|
{
|
||||||
|
url: url,
|
||||||
|
data: fd,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
type: 'POST',
|
||||||
|
|
||||||
|
success: function(data)
|
||||||
|
{
|
||||||
|
if (data['success'])
|
||||||
|
{
|
||||||
|
if (preview)
|
||||||
|
{
|
||||||
|
formDom.find('.preview').html(data['textPreview']).show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
disableExitConfirmation();
|
||||||
|
|
||||||
|
formDom.find('.preview').hide();
|
||||||
|
var cb = function()
|
||||||
|
{
|
||||||
|
$.get(window.location.href, function(data)
|
||||||
|
{
|
||||||
|
$('.comments-wrapper').replaceWith($(data).find('.comments-wrapper'));
|
||||||
|
$('body').trigger('dom-update');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (formDom.hasClass('add-comment'))
|
||||||
|
{
|
||||||
|
cb();
|
||||||
|
formDom.find('textarea').val('');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
formDom.slideUp(function()
|
||||||
|
{
|
||||||
|
cb();
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formDom.find(':input').attr('readonly', false);
|
||||||
|
formDom.removeClass('inactive');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alert(data['message']);
|
||||||
|
formDom.find(':input').attr('readonly', false);
|
||||||
|
formDom.removeClass('inactive');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function()
|
||||||
|
{
|
||||||
|
alert('Fatal error');
|
||||||
|
formDom.find(':input').attr('readonly', false);
|
||||||
|
formDom.removeClass('inactive');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax(ajaxData);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.comment .edit a').bindOnce('edit-comment', 'click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
var commentDom = $(this).parents('.comment');
|
||||||
|
var formDom = commentDom.find('form.edit-comment');
|
||||||
|
var cb = function(formDom)
|
||||||
|
{
|
||||||
|
formDom.slideToggle();
|
||||||
|
$('body').trigger('dom-update');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (formDom.length == 0)
|
||||||
|
{
|
||||||
|
$.get($(this).attr('href'), function(data)
|
||||||
|
{
|
||||||
|
var otherForm = $(data).find('form.edit-comment');
|
||||||
|
otherForm.hide();
|
||||||
|
commentDom.find('.body').append(otherForm);
|
||||||
|
formDom = commentDom.find('form.edit-comment');
|
||||||
|
cb(formDom);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cb(formDom);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('body').bind('dom-update', onDomUpdate);
|
||||||
|
});
|
@ -8,7 +8,6 @@ function setCookie(name, value, exdays)
|
|||||||
|
|
||||||
function getCookie(name)
|
function getCookie(name)
|
||||||
{
|
{
|
||||||
console.log(document.cookie);
|
|
||||||
var value = document.cookie;
|
var value = document.cookie;
|
||||||
var start = value.indexOf(' ' + name + '=');
|
var start = value.indexOf(' ' + name + '=');
|
||||||
|
|
||||||
@ -38,6 +37,17 @@ $.fn.hasAttr = function(name)
|
|||||||
return this.attr(name) !== undefined;
|
return this.attr(name) !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$.fn.bindOnce = function(name, eventName, callback)
|
||||||
|
{
|
||||||
|
$.each(this, function(i, item)
|
||||||
|
{
|
||||||
|
if ($(item).data(name) == name)
|
||||||
|
return;
|
||||||
|
$(item).data(name, name);
|
||||||
|
$(item).on(eventName, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//safety trigger
|
//safety trigger
|
||||||
@ -83,14 +93,14 @@ $(function()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$('form.confirmable').submit(confirmEvent);
|
$('form.confirmable').bindOnce('confirmation', 'submit', confirmEvent);
|
||||||
$('a.confirmable').click(confirmEvent);
|
$('a.confirmable').bindOnce('confirmation', 'click', confirmEvent);
|
||||||
|
|
||||||
|
|
||||||
//simple action buttons
|
//simple action buttons
|
||||||
$('a.simple-action').click(function(e)
|
$('a.simple-action').bindOnce('simple-action', 'click', function(e)
|
||||||
{
|
{
|
||||||
if(e.isPropagationStopped())
|
if (e.isPropagationStopped())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -125,7 +135,7 @@ $(function()
|
|||||||
//attach data from submit buttons to forms before .submit() gets called
|
//attach data from submit buttons to forms before .submit() gets called
|
||||||
$('.submit').each(function()
|
$('.submit').each(function()
|
||||||
{
|
{
|
||||||
$(this).click(function()
|
$(this).bindOnce('submit-faux-input', 'click', function()
|
||||||
{
|
{
|
||||||
var form = $(this).closest('form');
|
var form = $(this).closest('form');
|
||||||
form.find('.faux-submit').remove();
|
form.find('.faux-submit').remove();
|
||||||
@ -148,32 +158,39 @@ $(function()
|
|||||||
//modify DOM on small viewports
|
//modify DOM on small viewports
|
||||||
function processSidebar()
|
function processSidebar()
|
||||||
{
|
{
|
||||||
$('#inner-content .unit').addClass('bottom-unit');
|
if ($('#small-screen').is(':visible'))
|
||||||
if ($('body').width() < 600)
|
|
||||||
{
|
|
||||||
$('body').addClass('small-screen');
|
|
||||||
$('#sidebar').insertAfter($('#inner-content'));
|
$('#sidebar').insertAfter($('#inner-content'));
|
||||||
$('#sidebar .unit').removeClass('left-unit').addClass('bottom-unit');
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
$('body').removeClass('small-screen');
|
|
||||||
$('#sidebar').insertBefore($('#inner-content'));
|
$('#sidebar').insertBefore($('#inner-content'));
|
||||||
$('#sidebar .unit').removeClass('bottom-unit').addClass('left-unit');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$(function()
|
$(function()
|
||||||
{
|
{
|
||||||
$(window).resize(function()
|
$(window).resize(function()
|
||||||
{
|
{
|
||||||
|
fixSize();
|
||||||
if ($('body').width() == $('body').data('last-width'))
|
if ($('body').width() == $('body').data('last-width'))
|
||||||
return;
|
return;
|
||||||
$('body').data('last-width', $('body').width());
|
$('body').data('last-width', $('body').width());
|
||||||
$('body').trigger('dom-update');
|
$('body').trigger('dom-update');
|
||||||
});
|
});
|
||||||
$('body').bind('dom-update', processSidebar);
|
$('body').bind('dom-update', processSidebar);
|
||||||
|
fixSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var fixedEvenOnce = false;
|
||||||
|
function fixSize()
|
||||||
|
{
|
||||||
|
var multiply = 168;
|
||||||
|
var oldWidth = $('.main-wrapper:eq(0)').width();
|
||||||
|
$('.main-wrapper:eq(0)').width('');
|
||||||
|
var newWidth = $('.main-wrapper:eq(0)').width();
|
||||||
|
if (oldWidth != newWidth || !fixedEvenOnce)
|
||||||
|
{
|
||||||
|
$('.main-wrapper').width(multiply * Math.floor(newWidth / multiply));
|
||||||
|
fixedEvenOnce = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//autocomplete
|
//autocomplete
|
||||||
@ -187,6 +204,25 @@ function extractLast(term)
|
|||||||
return split(term).pop();
|
return split(term).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function retrieveTags(searchTerm, cb)
|
||||||
|
{
|
||||||
|
var options = { filter: searchTerm + ' order:popularity,desc' };
|
||||||
|
$.getJSON('/tags?json', options, function(data)
|
||||||
|
{
|
||||||
|
var tags = $.map(data.tags.slice(0, 15), function(tag)
|
||||||
|
{
|
||||||
|
var ret =
|
||||||
|
{
|
||||||
|
label: tag.name + ' (' + tag.count + ')',
|
||||||
|
value: tag.name,
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
|
||||||
|
cb(tags);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(function()
|
$(function()
|
||||||
{
|
{
|
||||||
$('.autocomplete').each(function()
|
$('.autocomplete').each(function()
|
||||||
@ -198,10 +234,7 @@ $(function()
|
|||||||
{
|
{
|
||||||
var term = extractLast(request.term);
|
var term = extractLast(request.term);
|
||||||
if (term != '')
|
if (term != '')
|
||||||
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term + ' order:popularity,desc'}, function(data)
|
retrieveTags(term, response);
|
||||||
{
|
|
||||||
response($.map(data.tags, function(tag) { return { label: tag.name + ' (' + tag.count + ')', value: tag.name }; }));
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
focus: function(e)
|
focus: function(e)
|
||||||
{
|
{
|
||||||
@ -239,34 +272,34 @@ $(function()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getTagItOptions()
|
function attachTagIt(element)
|
||||||
{
|
{
|
||||||
return {
|
var tagItOptions =
|
||||||
|
{
|
||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
autocomplete:
|
autocomplete:
|
||||||
{
|
{
|
||||||
source:
|
source:
|
||||||
function(request, response)
|
function(request, response)
|
||||||
{
|
{
|
||||||
var term = request.term.toLowerCase();
|
var tagit = this;
|
||||||
var tags = $.map(this.options.availableTags, function(a)
|
retrieveTags(request.term.toLowerCase(), function(tags)
|
||||||
{
|
{
|
||||||
return a.name;
|
if (!tagit.options.allowDuplicates)
|
||||||
});
|
|
||||||
var results = $.grep(tags, function(a)
|
|
||||||
{
|
{
|
||||||
if (term.length < 3)
|
tags = $.grep(tags, function(tag)
|
||||||
return a.toLowerCase().indexOf(term) == 0;
|
{
|
||||||
else
|
return tagit.assignedTags().indexOf(tag.value) == -1;
|
||||||
return a.toLowerCase().indexOf(term) != -1;
|
});
|
||||||
|
}
|
||||||
|
response(tags);
|
||||||
});
|
});
|
||||||
results = results.slice(0, 15);
|
|
||||||
if (!this.options.allowDuplicates)
|
|
||||||
results = this._subtractArray(results, this.assignedTags());
|
|
||||||
response(results);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tagItOptions.placeholderText = element.attr('placeholder');
|
||||||
|
element.tagit(tagItOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -281,3 +314,18 @@ $(function()
|
|||||||
Mousetrap.bind('d', function() { var url = $('.paginator:visible .next:not(.disabled) a').attr('href'); if (typeof url !== 'undefined') window.location.href = url; }, 'keyup');
|
Mousetrap.bind('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');
|
Mousetrap.bind('p', function() { $('.post a').eq(0).focus(); return false; }, 'keyup');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function enableExitConfirmation()
|
||||||
|
{
|
||||||
|
$(window).bind('beforeunload', function(e)
|
||||||
|
{
|
||||||
|
return 'There are unsaved changes.';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableExitConfirmation()
|
||||||
|
{
|
||||||
|
$(window).unbind('beforeunload');
|
||||||
|
}
|
||||||
|
@ -2,13 +2,9 @@ $(function()
|
|||||||
{
|
{
|
||||||
$('body').bind('dom-update', function()
|
$('body').bind('dom-update', function()
|
||||||
{
|
{
|
||||||
$('.post a.toggle-tag').click(function(e)
|
$('.post a.toggle-tag').bindOnce('toggle-tag', 'click', function(e)
|
||||||
{
|
{
|
||||||
if(e.isPropagationStopped())
|
|
||||||
return;
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
var aDom = $(this);
|
var aDom = $(this);
|
||||||
if (aDom.hasClass('inactive'))
|
if (aDom.hasClass('inactive'))
|
||||||
|
@ -6,14 +6,8 @@ $(function()
|
|||||||
var className = $(this).parents('li').attr('class').replace('selected', '').replace(/^\s+|\s+$/, '');
|
var className = $(this).parents('li').attr('class').replace('selected', '').replace(/^\s+|\s+$/, '');
|
||||||
$('.tabs li').removeClass('selected');
|
$('.tabs li').removeClass('selected');
|
||||||
$(this).parents('li').addClass('selected');
|
$(this).parents('li').addClass('selected');
|
||||||
$('.tab').hide();
|
$('.tab-content').hide();
|
||||||
$('.tab.' + className).show();
|
$('.tab-content.' + className).show();
|
||||||
});
|
|
||||||
|
|
||||||
var tags = [];
|
|
||||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
|
||||||
{
|
|
||||||
tags = data['tags'];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#file-handler').on('dragenter', function(e)
|
$('#file-handler').on('dragenter', function(e)
|
||||||
@ -42,18 +36,22 @@ $(function()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$('#url-handler-wrapper input').keydown(function(e)
|
||||||
|
{
|
||||||
|
if (e.which == 13)
|
||||||
|
{
|
||||||
|
$('#url-handler-wrapper button').trigger('click');
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
$('#url-handler-wrapper button').click(function(e)
|
$('#url-handler-wrapper button').click(function(e)
|
||||||
{
|
{
|
||||||
var urls = [];
|
var url = $('#url-handler-wrapper input').val();
|
||||||
$.each($('#url-handler-wrapper textarea').val().split(/\s+/), function(i, url)
|
|
||||||
{
|
|
||||||
url = url.replace(/^\s+|\s+$/, '');
|
url = url.replace(/^\s+|\s+$/, '');
|
||||||
if (url == '')
|
if (url == '')
|
||||||
return;
|
return;
|
||||||
urls.push(url);
|
$('#url-handler-wrapper input').val('');
|
||||||
});
|
handleURLs([url]);
|
||||||
$('#url-handler-wrapper textarea').val('');
|
|
||||||
handleURLs(urls);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -93,7 +91,6 @@ $(function()
|
|||||||
|
|
||||||
var postDom = posts.first();
|
var postDom = posts.first();
|
||||||
var url = postDom.find('form').attr('action') + '?json';
|
var url = postDom.find('form').attr('action') + '?json';
|
||||||
console.log(postDom.find('form').get(0));
|
|
||||||
var fd = new FormData(postDom.find('form').get(0));
|
var fd = new FormData(postDom.find('form').get(0));
|
||||||
|
|
||||||
fd.append('file', postDom.data('file'));
|
fd.append('file', postDom.data('file'));
|
||||||
@ -136,6 +133,7 @@ $(function()
|
|||||||
|
|
||||||
function uploadFinished()
|
function uploadFinished()
|
||||||
{
|
{
|
||||||
|
disableExitConfirmation();
|
||||||
window.location.href = $('#upload-step2').attr('data-redirect-url');
|
window.location.href = $('#upload-step2').attr('data-redirect-url');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,8 +180,7 @@ $(function()
|
|||||||
{
|
{
|
||||||
return function(e)
|
return function(e)
|
||||||
{
|
{
|
||||||
img.css('background-image', 'none');
|
changeThumb(img, e.target.result);
|
||||||
img.attr('src', e.target.result);
|
|
||||||
};
|
};
|
||||||
})(file, img);
|
})(file, img);
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
@ -191,6 +188,14 @@ $(function()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeThumb(img, url)
|
||||||
|
{
|
||||||
|
$(img)
|
||||||
|
.css('background-image', 'none')
|
||||||
|
.attr('src', url)
|
||||||
|
.data('custom-thumb', true);
|
||||||
|
}
|
||||||
|
|
||||||
function handleURLs(urls)
|
function handleURLs(urls)
|
||||||
{
|
{
|
||||||
handleInputs(urls, function(postDom, url)
|
handleInputs(urls, function(postDom, url)
|
||||||
@ -204,18 +209,13 @@ $(function()
|
|||||||
{
|
{
|
||||||
postDom.find('.file-name strong')
|
postDom.find('.file-name strong')
|
||||||
.text(data.data.title);
|
.text(data.data.title);
|
||||||
postDom.find('img')
|
changeThumb(postDom.find('img'), data.data.thumbnail.hqDefault);
|
||||||
.css('background-image', 'none')
|
|
||||||
.attr('src', data.data.thumbnail.hqDefault);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
postDom.find('.file-name strong')
|
postDom.find('.file-name strong').text(url);
|
||||||
.text(url);
|
changeThumb(postDom.find('img'), url);
|
||||||
postDom.find('img')
|
|
||||||
.css('background-image', 'none')
|
|
||||||
.attr('src', url);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -232,16 +232,39 @@ $(function()
|
|||||||
$('.posts').append(postDom);
|
$('.posts').append(postDom);
|
||||||
|
|
||||||
postDom.show();
|
postDom.show();
|
||||||
var tagItOptions = getTagItOptions();
|
attachTagIt($('.tags input', postDom));
|
||||||
tagItOptions.availableTags = tags;
|
|
||||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
|
||||||
$('.tags input', postDom).tagit(tagItOptions);
|
|
||||||
|
|
||||||
callback(postDom, input);
|
callback(postDom, input);
|
||||||
}
|
}
|
||||||
if ($('.posts .post').length == 0)
|
if ($('.posts .post').length == 0)
|
||||||
|
{
|
||||||
|
disableExitConfirmation();
|
||||||
$('#upload-step2').fadeOut();
|
$('#upload-step2').fadeOut();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
enableExitConfirmation();
|
||||||
$('#upload-step2').fadeIn();
|
$('#upload-step2').fadeIn();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.post img').mouseenter(function(e)
|
||||||
|
{
|
||||||
|
if ($(this).data('custom-thumb') != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$('#lightbox')
|
||||||
|
.attr('src', $(this).attr('src'))
|
||||||
|
.show()
|
||||||
|
.position({
|
||||||
|
of: $(this),
|
||||||
|
my: 'center center',
|
||||||
|
at: 'center center',
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
$('.post img').mouseleave(function(e)
|
||||||
|
{
|
||||||
|
$('#lightbox').hide();
|
||||||
|
});
|
||||||
});
|
});
|
@ -1,6 +1,8 @@
|
|||||||
function onDomUpdate()
|
$(function()
|
||||||
{
|
{
|
||||||
$('li.edit a').click(function(e)
|
function onDomUpdate()
|
||||||
|
{
|
||||||
|
$('#sidebar a.edit-post').bindOnce('edit-post', 'click', function(e)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -9,25 +11,58 @@ function onDomUpdate()
|
|||||||
return;
|
return;
|
||||||
aDom.addClass('inactive');
|
aDom.addClass('inactive');
|
||||||
|
|
||||||
var tags = [];
|
|
||||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
|
||||||
{
|
|
||||||
aDom.removeClass('inactive');
|
|
||||||
var formDom = $('form.edit-post');
|
var formDom = $('form.edit-post');
|
||||||
tags = data['tags'];
|
if (formDom.find('.tagit').length == 0)
|
||||||
|
{
|
||||||
|
attachTagIt($('.tags input'));
|
||||||
|
aDom.removeClass('inactive');
|
||||||
|
|
||||||
|
formDom.find('input[type=text]:visible:eq(0)').focus();
|
||||||
|
formDom.find('textarea, input').bind('change keyup', function()
|
||||||
|
{
|
||||||
|
if (formDom.serialize() != formDom.data('original-data'))
|
||||||
|
enableExitConfirmation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
aDom.removeClass('inactive');
|
||||||
|
|
||||||
|
var editUnit = formDom.parents('.unit');
|
||||||
|
var postUnit = $('.post-wrapper');
|
||||||
if (!$(formDom).is(':visible'))
|
if (!$(formDom).is(':visible'))
|
||||||
{
|
{
|
||||||
var tagItOptions = getTagItOptions();
|
formDom.data('original-data', formDom.serialize());
|
||||||
tagItOptions.availableTags = tags;
|
|
||||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
editUnit.show();
|
||||||
$('.tags input').tagit(tagItOptions);
|
var editUnitHeight = formDom.height();
|
||||||
formDom.show().css('height', formDom.height()).hide().slideDown();
|
editUnit.css('height', editUnitHeight);
|
||||||
|
editUnit.hide();
|
||||||
|
|
||||||
|
if (postUnit.height() < editUnitHeight)
|
||||||
|
postUnit.animate({height: editUnitHeight + 'px'}, 'fast');
|
||||||
|
|
||||||
|
editUnit.slideDown('fast', function()
|
||||||
|
{
|
||||||
|
$(this).css('height', 'auto');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
editUnit.slideUp('fast');
|
||||||
|
|
||||||
|
var postUnitOldHeight = postUnit.height();
|
||||||
|
postUnit.height('auto');
|
||||||
|
var postUnitHeight = postUnit.height();
|
||||||
|
postUnit.height(postUnitOldHeight);
|
||||||
|
if (postUnitHeight != postUnitOldHeight)
|
||||||
|
postUnit.animate({height: postUnitHeight + 'px'});
|
||||||
|
|
||||||
|
if ($('.post-wrapper').height() < editUnitHeight)
|
||||||
|
$('.post-wrapper').animate({height: editUnitHeight + 'px'});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
formDom.find('input[type=text]:visible:eq(0)').focus();
|
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()
|
$('.comments.unit a.simple-action').data('callback', function()
|
||||||
@ -47,10 +82,8 @@ function onDomUpdate()
|
|||||||
$('body').trigger('dom-update');
|
$('body').trigger('dom-update');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function()
|
|
||||||
{
|
|
||||||
$('body').bind('dom-update', onDomUpdate);
|
$('body').bind('dom-update', onDomUpdate);
|
||||||
|
|
||||||
$('form.edit-post').submit(function(e)
|
$('form.edit-post').submit(function(e)
|
||||||
@ -79,82 +112,22 @@ $(function()
|
|||||||
{
|
{
|
||||||
if (data['success'])
|
if (data['success'])
|
||||||
{
|
{
|
||||||
window.location.reload();
|
disableExitConfirmation();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
alert(data['message']);
|
|
||||||
formDom.find(':input').attr('readonly', false);
|
|
||||||
formDom.removeClass('inactive');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function()
|
|
||||||
{
|
|
||||||
alert('Fatal error');
|
|
||||||
formDom.find(':input').attr('readonly', false);
|
|
||||||
formDom.removeClass('inactive');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax(ajaxData);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('form.add-comment').submit(function(e)
|
|
||||||
{
|
|
||||||
e.preventDefault();
|
|
||||||
rememberLastSearchQuery();
|
|
||||||
|
|
||||||
var formDom = $(this);
|
|
||||||
if (formDom.hasClass('inactive'))
|
|
||||||
return;
|
|
||||||
formDom.addClass('inactive');
|
|
||||||
formDom.find(':input').attr('readonly', true);
|
|
||||||
|
|
||||||
var url = formDom.attr('action') + '?json';
|
|
||||||
var fd = new FormData(formDom[0]);
|
|
||||||
|
|
||||||
var preview = false;
|
|
||||||
$.each(formDom.serializeArray(), function(i, x)
|
|
||||||
{
|
|
||||||
if (x.name == 'sender' && x.value == 'preview')
|
|
||||||
preview = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
var ajaxData =
|
|
||||||
{
|
|
||||||
url: url,
|
|
||||||
data: fd,
|
|
||||||
processData: false,
|
|
||||||
contentType: false,
|
|
||||||
type: 'POST',
|
|
||||||
|
|
||||||
success: function(data)
|
|
||||||
{
|
|
||||||
if (data['success'])
|
|
||||||
{
|
|
||||||
if (preview)
|
|
||||||
{
|
|
||||||
formDom.find('.preview').html(data['textPreview']).show();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
formDom.find('.preview').hide();
|
|
||||||
$.get(window.location.href, function(data)
|
$.get(window.location.href, function(data)
|
||||||
{
|
{
|
||||||
$('.comments-wrapper').replaceWith($(data).find('.comments-wrapper'));
|
$('#sidebar').replaceWith($(data).find('#sidebar'));
|
||||||
|
$('#edit-token').replaceWith($(data).find('#edit-token'));
|
||||||
$('body').trigger('dom-update');
|
$('body').trigger('dom-update');
|
||||||
});
|
});
|
||||||
formDom.find('textarea').val('');
|
formDom.parents('.unit').hide();
|
||||||
}
|
|
||||||
formDom.find(':input').attr('readonly', false);
|
|
||||||
formDom.removeClass('inactive');
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
alert(data['message']);
|
alert(data['message']);
|
||||||
|
}
|
||||||
formDom.find(':input').attr('readonly', false);
|
formDom.find(':input').attr('readonly', false);
|
||||||
formDom.removeClass('inactive');
|
formDom.removeClass('inactive');
|
||||||
}
|
|
||||||
},
|
},
|
||||||
error: function()
|
error: function()
|
||||||
{
|
{
|
||||||
@ -169,5 +142,5 @@ $(function()
|
|||||||
|
|
||||||
Mousetrap.bind('a', function() { var a = $('#sidebar .left a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
Mousetrap.bind('a', function() { var a = $('#sidebar .left a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
||||||
Mousetrap.bind('d', function() { var a = $('#sidebar .right a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
Mousetrap.bind('d', function() { var a = $('#sidebar .right a'); var url = a.attr('href'); if (typeof url !== 'undefined') { a.click(); window.location.href = url; } }, 'keyup');
|
||||||
Mousetrap.bind('e', function() { $('li.edit a').trigger('click'); return false; }, 'keyup');
|
Mousetrap.bind('e', function() { $('a.edit-post').trigger('click'); return false; }, 'keyup');
|
||||||
});
|
});
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class Bootstrap
|
class Bootstrap
|
||||||
{
|
{
|
||||||
|
public function render($callback = null)
|
||||||
|
{
|
||||||
|
if ($callback !== null)
|
||||||
|
$callback();
|
||||||
|
else
|
||||||
|
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||||
|
}
|
||||||
|
|
||||||
public function workWrapper($workCallback)
|
public function workWrapper($workCallback)
|
||||||
{
|
{
|
||||||
$this->config->chibi->baseUrl = 'http://' . rtrim($_SERVER['HTTP_HOST'], '/') . '/';
|
$this->config->chibi->baseUrl = 'http://' . rtrim($_SERVER['HTTP_HOST'], '/') . '/';
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$this->context->handleExceptions = false;
|
$this->context->handleExceptions = false;
|
||||||
$this->context->title = $this->config->main->title;
|
CustomAssetViewDecorator::setTitle($this->config->main->title);
|
||||||
$this->context->stylesheets =
|
|
||||||
[
|
|
||||||
'../lib/jquery-ui/jquery-ui.css',
|
|
||||||
'core.css',
|
|
||||||
];
|
|
||||||
$this->context->scripts =
|
|
||||||
[
|
|
||||||
'../lib/jquery/jquery.min.js',
|
|
||||||
'../lib/jquery-ui/jquery-ui.min.js',
|
|
||||||
'../lib/mousetrap/mousetrap.min.js',
|
|
||||||
'core.js',
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->context->json = isset($_GET['json']);
|
$this->context->json = isset($_GET['json']);
|
||||||
$this->context->layoutName = $this->context->json
|
$this->context->layoutName = $this->context->json
|
||||||
@ -32,35 +30,40 @@ class Bootstrap
|
|||||||
|
|
||||||
if (empty($this->context->route))
|
if (empty($this->context->route))
|
||||||
{
|
{
|
||||||
|
http_response_code(404);
|
||||||
$this->context->viewName = 'error-404';
|
$this->context->viewName = 'error-404';
|
||||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
$this->render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->context->viewDecorators []= new CustomAssetViewDecorator();
|
||||||
|
$this->context->viewDecorators []= new \Chibi\PrettyPrintViewDecorator();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$workCallback();
|
$this->render($workCallback);
|
||||||
}
|
}
|
||||||
catch (\Chibi\MissingViewFileException $e)
|
catch (\Chibi\MissingViewFileException $e)
|
||||||
{
|
{
|
||||||
$this->context->json = true;
|
$this->context->json = true;
|
||||||
$this->context->layoutName = 'layout-json';
|
$this->context->layoutName = 'layout-json';
|
||||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
$this->render();
|
||||||
}
|
}
|
||||||
catch (SimpleException $e)
|
catch (SimpleException $e)
|
||||||
{
|
{
|
||||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
if ($e instanceof SimpleNotFoundException)
|
||||||
|
http_response_code(404);
|
||||||
|
StatusHelper::failure($e->getMessage());
|
||||||
if (!$this->context->handleExceptions)
|
if (!$this->context->handleExceptions)
|
||||||
$this->context->viewName = 'message';
|
$this->context->viewName = 'message';
|
||||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
$this->render();
|
||||||
}
|
}
|
||||||
catch (Exception $e)
|
catch (Exception $e)
|
||||||
{
|
{
|
||||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
StatusHelper::failure($e->getMessage());
|
||||||
$this->context->transport->exception = $e;
|
$this->context->transport->exception = $e;
|
||||||
$this->context->transport->queries = Database::getLogs();
|
$this->context->transport->queries = Database::getLogs();
|
||||||
$this->context->viewName = 'error-exception';
|
$this->context->viewName = 'error-exception';
|
||||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
$this->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthController::observeWorkFinish();
|
AuthController::observeWorkFinish();
|
||||||
|
@ -55,8 +55,6 @@ class AuthController
|
|||||||
public function loginAction()
|
public function loginAction()
|
||||||
{
|
{
|
||||||
$this->context->handleExceptions = true;
|
$this->context->handleExceptions = true;
|
||||||
$this->context->stylesheets []= 'auth.css';
|
|
||||||
$this->context->subTitle = 'authentication form';
|
|
||||||
|
|
||||||
//check if already logged in
|
//check if already logged in
|
||||||
if ($this->context->loggedIn)
|
if ($this->context->loggedIn)
|
||||||
@ -106,6 +104,8 @@ class AuthController
|
|||||||
if (!empty($context->user) and $context->user->id)
|
if (!empty($context->user) and $context->user->id)
|
||||||
{
|
{
|
||||||
$dbUser = UserModel::findById($context->user->id);
|
$dbUser = UserModel::findById($context->user->id);
|
||||||
|
$context->user->lastLoginDate = time();
|
||||||
|
UserModel::save($context->user);
|
||||||
$_SESSION['user'] = serialize($dbUser);
|
$_SESSION['user'] = serialize($dbUser);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -8,35 +8,30 @@ class CommentController
|
|||||||
*/
|
*/
|
||||||
public function listAction($page)
|
public function listAction($page)
|
||||||
{
|
{
|
||||||
$this->context->stylesheets []= 'post-small.css';
|
|
||||||
$this->context->stylesheets []= 'comment-list.css';
|
|
||||||
$this->context->stylesheets []= 'comment-small.css';
|
|
||||||
$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);
|
PrivilegesHelper::confirmWithException(Privilege::ListComments);
|
||||||
|
|
||||||
$page = max(1, $page);
|
$page = max(1, intval($page));
|
||||||
$comments = CommentSearchService::getEntities(null, $commentsPerPage, $page);
|
$commentsPerPage = intval($this->config->comments->commentsPerPage);
|
||||||
$commentCount = CommentSearchService::getEntityCount(null, $commentsPerPage, $page);
|
$searchQuery = 'comment_min:1 order:comment_date,desc';
|
||||||
$pageCount = ceil($commentCount / $commentsPerPage);
|
|
||||||
CommentModel::preloadCommenters($comments);
|
$posts = PostSearchService::getEntities($searchQuery, $commentsPerPage, $page);
|
||||||
CommentModel::preloadPosts($comments);
|
$postCount = PostSearchService::getEntityCount($searchQuery);
|
||||||
$posts = array_map(function($comment) { return $comment->getPost(); }, $comments);
|
$pageCount = ceil($postCount / $commentsPerPage);
|
||||||
PostModel::preloadTags($posts);
|
PostModel::preloadTags($posts);
|
||||||
|
PostModel::preloadComments($posts);
|
||||||
|
$comments = [];
|
||||||
|
foreach ($posts as $post)
|
||||||
|
$comments = array_merge($comments, $post->getComments());
|
||||||
|
CommentModel::preloadCommenters($comments);
|
||||||
|
|
||||||
$this->context->postGroups = true;
|
$this->context->postGroups = true;
|
||||||
|
$this->context->transport->posts = $posts;
|
||||||
$this->context->transport->paginator = new StdClass;
|
$this->context->transport->paginator = new StdClass;
|
||||||
$this->context->transport->paginator->page = $page;
|
$this->context->transport->paginator->page = $page;
|
||||||
$this->context->transport->paginator->pageCount = $pageCount;
|
$this->context->transport->paginator->pageCount = $pageCount;
|
||||||
$this->context->transport->paginator->entityCount = $commentCount;
|
$this->context->transport->paginator->entityCount = $postCount;
|
||||||
$this->context->transport->paginator->entities = $comments;
|
$this->context->transport->paginator->entities = $posts;
|
||||||
$this->context->transport->paginator->params = func_get_args();
|
$this->context->transport->paginator->params = func_get_args();
|
||||||
$this->context->transport->comments = $comments;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -52,6 +47,7 @@ class CommentController
|
|||||||
PrivilegesHelper::confirmEmail($this->context->user);
|
PrivilegesHelper::confirmEmail($this->context->user);
|
||||||
|
|
||||||
$post = PostModel::findById($postId);
|
$post = PostModel::findById($postId);
|
||||||
|
$this->context->transport->post = $post;
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
@ -66,6 +62,7 @@ class CommentController
|
|||||||
$comment->setCommenter(null);
|
$comment->setCommenter(null);
|
||||||
$comment->commentDate = time();
|
$comment->commentDate = time();
|
||||||
$comment->text = $text;
|
$comment->text = $text;
|
||||||
|
|
||||||
if (InputHelper::get('sender') != 'preview')
|
if (InputHelper::get('sender') != 'preview')
|
||||||
{
|
{
|
||||||
CommentModel::save($comment);
|
CommentModel::save($comment);
|
||||||
@ -78,6 +75,36 @@ class CommentController
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /comment/{id}/edit
|
||||||
|
* @validate id [0-9]+
|
||||||
|
*/
|
||||||
|
public function editAction($id)
|
||||||
|
{
|
||||||
|
$comment = CommentModel::findById($id);
|
||||||
|
$this->context->transport->comment = $comment;
|
||||||
|
|
||||||
|
PrivilegesHelper::confirmWithException(Privilege::EditComment, PrivilegesHelper::getIdentitySubPrivilege($comment->getCommenter()));
|
||||||
|
|
||||||
|
if (InputHelper::get('submit'))
|
||||||
|
{
|
||||||
|
$text = InputHelper::get('text');
|
||||||
|
$text = CommentModel::validateText($text);
|
||||||
|
|
||||||
|
$comment->text = $text;
|
||||||
|
|
||||||
|
if (InputHelper::get('sender') != 'preview')
|
||||||
|
{
|
||||||
|
CommentModel::save($comment);
|
||||||
|
LogHelper::log('{user} edited comment in {post}', ['post' => TextHelper::reprPost($comment->getPost())]);
|
||||||
|
}
|
||||||
|
$this->context->transport->textPreview = $comment->getText();
|
||||||
|
StatusHelper::success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route /comment/{id}/delete
|
* @route /comment/{id}/delete
|
||||||
* @validate id [0-9]+
|
* @validate id [0-9]+
|
||||||
|
@ -7,8 +7,6 @@ class IndexController
|
|||||||
*/
|
*/
|
||||||
public function indexAction()
|
public function indexAction()
|
||||||
{
|
{
|
||||||
$this->context->subTitle = 'home';
|
|
||||||
$this->context->stylesheets []= 'index-index.css';
|
|
||||||
$this->context->transport->postCount = PostModel::getCount();
|
$this->context->transport->postCount = PostModel::getCount();
|
||||||
|
|
||||||
$featuredPost = $this->getFeaturedPost();
|
$featuredPost = $this->getFeaturedPost();
|
||||||
@ -17,7 +15,6 @@ class IndexController
|
|||||||
$this->context->featuredPost = $featuredPost;
|
$this->context->featuredPost = $featuredPost;
|
||||||
$this->context->featuredPostDate = PropertyModel::get(PropertyModel::FeaturedPostDate);
|
$this->context->featuredPostDate = PropertyModel::get(PropertyModel::FeaturedPostDate);
|
||||||
$this->context->featuredPostUser = UserModel::findByNameOrEmail(PropertyModel::get(PropertyModel::FeaturedPostUserName), false);
|
$this->context->featuredPostUser = UserModel::findByNameOrEmail(PropertyModel::get(PropertyModel::FeaturedPostUserName), false);
|
||||||
$this->context->pageThumb = \Chibi\UrlHelper::route('post', 'thumb', ['name' => $featuredPost->name]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,8 +30,6 @@ class IndexController
|
|||||||
if (!isset($this->config->help->paths[$tab]))
|
if (!isset($this->config->help->paths[$tab]))
|
||||||
throw new SimpleException('Invalid tab');
|
throw new SimpleException('Invalid tab');
|
||||||
$this->context->path = TextHelper::absolutePath($this->config->help->paths[$tab]);
|
$this->context->path = TextHelper::absolutePath($this->config->help->paths[$tab]);
|
||||||
$this->context->stylesheets []= 'index-help.css';
|
|
||||||
$this->context->subTitle = 'help';
|
|
||||||
$this->context->tab = $tab;
|
$this->context->tab = $tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,27 +47,8 @@ class IndexController
|
|||||||
//check if post was deleted
|
//check if post was deleted
|
||||||
$featuredPost = PostModel::findById($featuredPostId, false);
|
$featuredPost = PostModel::findById($featuredPostId, false);
|
||||||
if (!$featuredPost)
|
if (!$featuredPost)
|
||||||
return $this->featureNewPost();
|
return PropertyModel::featureNewPost();
|
||||||
|
|
||||||
return $featuredPost;
|
return $featuredPost;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function featureNewPost()
|
|
||||||
{
|
|
||||||
$query = (new SqlQuery)
|
|
||||||
->select('id')
|
|
||||||
->from('post')
|
|
||||||
->where('type = ?')->put(PostType::Image)
|
|
||||||
->and('safety = ?')->put(PostSafety::Safe)
|
|
||||||
->orderBy($this->config->main->dbDriver == 'sqlite' ? 'random()' : 'rand()')
|
|
||||||
->desc();
|
|
||||||
$featuredPostId = Database::fetchOne($query)['id'];
|
|
||||||
if (!$featuredPostId)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostId, $featuredPostId);
|
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostDate, time());
|
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, null);
|
|
||||||
return PostModel::findById($featuredPostId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ class LogController
|
|||||||
*/
|
*/
|
||||||
public function listAction()
|
public function listAction()
|
||||||
{
|
{
|
||||||
$this->context->subTitle = 'latest logs';
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ListLogs);
|
PrivilegesHelper::confirmWithException(Privilege::ListLogs);
|
||||||
|
|
||||||
$path = TextHelper::absolutePath($this->config->main->logsPath);
|
$path = TextHelper::absolutePath($this->config->main->logsPath);
|
||||||
@ -25,22 +24,40 @@ class LogController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @route /log/{name}
|
* @route /log/{name}
|
||||||
|
* @route /log/{name}/{page}
|
||||||
|
* @route /log/{name}/{page}/{filter}
|
||||||
* @validate name [0-9a-zA-Z._-]+
|
* @validate name [0-9a-zA-Z._-]+
|
||||||
|
* @validate page \d*
|
||||||
|
* @validate filter .*
|
||||||
*/
|
*/
|
||||||
public function viewAction($name)
|
public function viewAction($name, $page = 1, $filter = '')
|
||||||
{
|
{
|
||||||
$this->context->subTitle = 'logs (' . $name . ')';
|
//redirect requests in form of ?query=... to canonical address
|
||||||
$this->context->stylesheets []= 'logs.css';
|
$formQuery = InputHelper::get('query');
|
||||||
$this->context->scripts []= 'logs.js';
|
if ($formQuery !== null)
|
||||||
|
{
|
||||||
|
\Chibi\UrlHelper::forward(
|
||||||
|
\Chibi\UrlHelper::route(
|
||||||
|
'log',
|
||||||
|
'view',
|
||||||
|
[
|
||||||
|
'name' => $name,
|
||||||
|
'filter' => $formQuery,
|
||||||
|
'page' => 1
|
||||||
|
]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ViewLog);
|
PrivilegesHelper::confirmWithException(Privilege::ViewLog);
|
||||||
|
|
||||||
|
//parse input
|
||||||
|
$page = max(1, intval($page));
|
||||||
$name = str_replace(['/', '\\'], '', $name); //paranoia mode
|
$name = str_replace(['/', '\\'], '', $name); //paranoia mode
|
||||||
$path = TextHelper::absolutePath($this->config->main->logsPath . DS . $name);
|
$path = TextHelper::absolutePath($this->config->main->logsPath . DS . $name);
|
||||||
if (!file_exists($path))
|
if (!file_exists($path))
|
||||||
throw new SimpleException('Specified log doesn\'t exist');
|
throw new SimpleNotFoundException('Specified log doesn\'t exist');
|
||||||
|
|
||||||
$filter = InputHelper::get('filter');
|
|
||||||
|
|
||||||
|
//load lines
|
||||||
$lines = file_get_contents($path);
|
$lines = file_get_contents($path);
|
||||||
$lines = explode(PHP_EOL, str_replace(["\r", "\n"], PHP_EOL, $lines));
|
$lines = explode(PHP_EOL, str_replace(["\r", "\n"], PHP_EOL, $lines));
|
||||||
$lines = array_reverse($lines);
|
$lines = array_reverse($lines);
|
||||||
@ -48,6 +65,13 @@ class LogController
|
|||||||
if (!empty($filter))
|
if (!empty($filter))
|
||||||
$lines = array_filter($lines, function($line) use ($filter) { return stripos($line, $filter) !== false; });
|
$lines = array_filter($lines, function($line) use ($filter) { return stripos($line, $filter) !== false; });
|
||||||
|
|
||||||
|
$lineCount = count($lines);
|
||||||
|
$logsPerPage = intval($this->config->browsing->logsPerPage);
|
||||||
|
$pageCount = ceil($lineCount / $logsPerPage);
|
||||||
|
$page = min($pageCount, $page);
|
||||||
|
|
||||||
|
$lines = array_slice($lines, ($page - 1) * $logsPerPage, $logsPerPage);
|
||||||
|
|
||||||
//stylize important lines
|
//stylize important lines
|
||||||
foreach ($lines as &$line)
|
foreach ($lines as &$line)
|
||||||
if (strpos($line, 'flag') !== false)
|
if (strpos($line, 'flag') !== false)
|
||||||
@ -58,8 +82,13 @@ class LogController
|
|||||||
$lines = TextHelper::parseMarkdown($lines, true);
|
$lines = TextHelper::parseMarkdown($lines, true);
|
||||||
$lines = trim($lines);
|
$lines = trim($lines);
|
||||||
|
|
||||||
|
$this->context->transport->paginator = new StdClass;
|
||||||
|
$this->context->transport->paginator->page = $page;
|
||||||
|
$this->context->transport->paginator->pageCount = $pageCount;
|
||||||
|
$this->context->transport->paginator->entityCount = $lineCount;
|
||||||
|
$this->context->transport->paginator->entities = $lines;
|
||||||
|
$this->context->transport->lines = $lines;
|
||||||
$this->context->transport->filter = $filter;
|
$this->context->transport->filter = $filter;
|
||||||
$this->context->transport->name = $name;
|
$this->context->transport->name = $name;
|
||||||
$this->context->transport->log = $lines;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
class PostController
|
class PostController
|
||||||
{
|
{
|
||||||
public function workWrapper($callback)
|
|
||||||
{
|
|
||||||
$this->context->stylesheets []= '../lib/tagit/jquery.tagit.css';
|
|
||||||
$this->context->scripts []= '../lib/tagit/jquery.tagit.js';
|
|
||||||
$callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function serializePost($post)
|
|
||||||
{
|
|
||||||
$x = [];
|
|
||||||
foreach ($post->getTags() as $tag)
|
|
||||||
$x []= TextHelper::reprTag($tag->name);
|
|
||||||
foreach ($post->getRelations() as $relatedPost)
|
|
||||||
$x []= TextHelper::reprPost($relatedPost);
|
|
||||||
$x []= $post->safety;
|
|
||||||
$x []= $post->source;
|
|
||||||
$x []= $post->fileHash;
|
|
||||||
natcasesort($x);
|
|
||||||
$x = join(' ', $x);
|
|
||||||
return md5($x);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function handleUploadErrors($file)
|
private static function handleUploadErrors($file)
|
||||||
{
|
{
|
||||||
switch ($file['error'])
|
switch ($file['error'])
|
||||||
@ -67,14 +45,9 @@ class PostController
|
|||||||
public function listAction($query = null, $page = 1, $source = 'posts', $additionalInfo = null)
|
public function listAction($query = null, $page = 1, $source = 'posts', $additionalInfo = null)
|
||||||
{
|
{
|
||||||
$this->context->viewName = 'post-list-wrapper';
|
$this->context->viewName = 'post-list-wrapper';
|
||||||
$this->context->stylesheets []= 'post-small.css';
|
|
||||||
$this->context->stylesheets []= 'post-list.css';
|
|
||||||
$this->context->stylesheets []= 'paginator.css';
|
|
||||||
$this->context->scripts []= 'post-list.js';
|
|
||||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
|
||||||
$this->context->scripts []= 'paginator-endless.js';
|
|
||||||
$this->context->source = $source;
|
$this->context->source = $source;
|
||||||
$this->context->additionalInfo = $additionalInfo;
|
$this->context->additionalInfo = $additionalInfo;
|
||||||
|
$this->context->handleExceptions = true;
|
||||||
|
|
||||||
//redirect requests in form of /posts/?query=... to canonical address
|
//redirect requests in form of /posts/?query=... to canonical address
|
||||||
$formQuery = InputHelper::get('query');
|
$formQuery = InputHelper::get('query');
|
||||||
@ -90,9 +63,8 @@ class PostController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$query = trim($query);
|
$query = trim($query);
|
||||||
$page = intval($page);
|
$page = max(1, intval($page));
|
||||||
$postsPerPage = intval($this->config->browsing->postsPerPage);
|
$postsPerPage = intval($this->config->browsing->postsPerPage);
|
||||||
$this->context->subTitle = 'posts';
|
|
||||||
$this->context->transport->searchQuery = $query;
|
$this->context->transport->searchQuery = $query;
|
||||||
$this->context->transport->lastSearchQuery = $query;
|
$this->context->transport->lastSearchQuery = $query;
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
||||||
@ -101,11 +73,13 @@ class PostController
|
|||||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||||
$this->context->massTagTag = $additionalInfo;
|
$this->context->massTagTag = $additionalInfo;
|
||||||
$this->context->massTagQuery = $query;
|
$this->context->massTagQuery = $query;
|
||||||
|
|
||||||
|
if (!PrivilegesHelper::confirm(Privilege::MassTag, 'all'))
|
||||||
|
$query = trim($query . ' submit:' . $this->context->user->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
$page = max(1, $page);
|
|
||||||
$posts = PostSearchService::getEntities($query, $postsPerPage, $page);
|
$posts = PostSearchService::getEntities($query, $postsPerPage, $page);
|
||||||
$postCount = PostSearchService::getEntityCount($query, $postsPerPage, $page);
|
$postCount = PostSearchService::getEntityCount($query);
|
||||||
$pageCount = ceil($postCount / $postsPerPage);
|
$pageCount = ceil($postCount / $postsPerPage);
|
||||||
$page = min($pageCount, $page);
|
$page = min($pageCount, $page);
|
||||||
PostModel::preloadTags($posts);
|
PostModel::preloadTags($posts);
|
||||||
@ -133,7 +107,7 @@ class PostController
|
|||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
PrivilegesHelper::confirmWithException(Privilege::MassTag, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||||
|
|
||||||
$tags = $post->getTags();
|
$tags = $post->getTags();
|
||||||
|
|
||||||
@ -196,16 +170,13 @@ class PostController
|
|||||||
*/
|
*/
|
||||||
public function uploadAction()
|
public function uploadAction()
|
||||||
{
|
{
|
||||||
$this->context->stylesheets []= 'upload.css';
|
|
||||||
$this->context->scripts []= 'upload.js';
|
|
||||||
$this->context->subTitle = 'upload';
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::UploadPost);
|
PrivilegesHelper::confirmWithException(Privilege::UploadPost);
|
||||||
if ($this->config->registration->needEmailForUploading)
|
if ($this->config->registration->needEmailForUploading)
|
||||||
PrivilegesHelper::confirmEmail($this->context->user);
|
PrivilegesHelper::confirmEmail($this->context->user);
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
Database::transaction(function()
|
\Chibi\Database::transaction(function()
|
||||||
{
|
{
|
||||||
$post = PostModel::spawn();
|
$post = PostModel::spawn();
|
||||||
LogHelper::bufferChanges();
|
LogHelper::bufferChanges();
|
||||||
@ -261,7 +232,7 @@ class PostController
|
|||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
$editToken = InputHelper::get('edit-token');
|
$editToken = InputHelper::get('edit-token');
|
||||||
if ($editToken != self::serializePost($post))
|
if ($editToken != $post->getEditToken())
|
||||||
throw new SimpleException('This post was already edited by someone else in the meantime');
|
throw new SimpleException('This post was already edited by someone else in the meantime');
|
||||||
|
|
||||||
LogHelper::bufferChanges();
|
LogHelper::bufferChanges();
|
||||||
@ -283,7 +254,7 @@ class PostController
|
|||||||
public function flagAction($id)
|
public function flagAction($id)
|
||||||
{
|
{
|
||||||
$post = PostModel::findByIdOrName($id);
|
$post = PostModel::findByIdOrName($id);
|
||||||
PrivilegesHelper::confirmWithException(Privilege::FlagPost);
|
PrivilegesHelper::confirmWithException(Privilege::FlagPost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
@ -368,13 +339,14 @@ class PostController
|
|||||||
public function addFavoriteAction($id)
|
public function addFavoriteAction($id)
|
||||||
{
|
{
|
||||||
$post = PostModel::findByIdOrName($id);
|
$post = PostModel::findByIdOrName($id);
|
||||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
PrivilegesHelper::confirmWithException(Privilege::FavoritePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
if (!$this->context->loggedIn)
|
if (!$this->context->loggedIn)
|
||||||
throw new SimpleException('Not logged in');
|
throw new SimpleException('Not logged in');
|
||||||
|
|
||||||
|
UserModel::updateUserScore($this->context->user, $post, 1);
|
||||||
UserModel::addToUserFavorites($this->context->user, $post);
|
UserModel::addToUserFavorites($this->context->user, $post);
|
||||||
StatusHelper::success();
|
StatusHelper::success();
|
||||||
}
|
}
|
||||||
@ -387,7 +359,7 @@ class PostController
|
|||||||
public function remFavoriteAction($id)
|
public function remFavoriteAction($id)
|
||||||
{
|
{
|
||||||
$post = PostModel::findByIdOrName($id);
|
$post = PostModel::findByIdOrName($id);
|
||||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
PrivilegesHelper::confirmWithException(Privilege::FavoritePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
@ -408,7 +380,7 @@ class PostController
|
|||||||
public function scoreAction($id, $score)
|
public function scoreAction($id, $score)
|
||||||
{
|
{
|
||||||
$post = PostModel::findByIdOrName($id);
|
$post = PostModel::findByIdOrName($id);
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ScorePost);
|
PrivilegesHelper::confirmWithException(Privilege::ScorePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
@ -428,7 +400,7 @@ class PostController
|
|||||||
public function featureAction($id)
|
public function featureAction($id)
|
||||||
{
|
{
|
||||||
$post = PostModel::findByIdOrName($id);
|
$post = PostModel::findByIdOrName($id);
|
||||||
PrivilegesHelper::confirmWithException(Privilege::FeaturePost);
|
PrivilegesHelper::confirmWithException(Privilege::FeaturePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostId, $post->id);
|
PropertyModel::set(PropertyModel::FeaturedPostId, $post->id);
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostDate, time());
|
PropertyModel::set(PropertyModel::FeaturedPostDate, time());
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, $this->context->user->name);
|
PropertyModel::set(PropertyModel::FeaturedPostUserName, $this->context->user->name);
|
||||||
@ -452,40 +424,32 @@ class PostController
|
|||||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
|
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
|
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
|
||||||
|
|
||||||
PostSearchService::enableTokenLimit(false);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$this->context->transport->lastSearchQuery = InputHelper::get('last-search-query');
|
$this->context->transport->lastSearchQuery = InputHelper::get('last-search-query');
|
||||||
$prevPostQuery = $this->context->transport->lastSearchQuery . ' prev:' . $id;
|
list ($prevPostId, $nextPostId) =
|
||||||
$nextPostQuery = $this->context->transport->lastSearchQuery . ' next:' . $id;
|
PostSearchService::getPostIdsAround(
|
||||||
$prevPost = current(PostSearchService::getEntities($prevPostQuery, 1, 1));
|
$this->context->transport->lastSearchQuery, $id);
|
||||||
$nextPost = current(PostSearchService::getEntities($nextPostQuery, 1, 1));
|
|
||||||
}
|
}
|
||||||
#search for some reason was invalid, e.g. tag was deleted in the meantime
|
#search for some reason was invalid, e.g. tag was deleted in the meantime
|
||||||
catch (Exception $e)
|
catch (Exception $e)
|
||||||
{
|
{
|
||||||
$this->context->transport->lastSearchQuery = '';
|
$this->context->transport->lastSearchQuery = '';
|
||||||
$prevPost = current(PostModel::getEntities('prev:' . $id, 1, 1));
|
list ($prevPostId, $nextPostId) =
|
||||||
$nextPost = current(PostModel::getEntities('next:' . $id, 1, 1));
|
PostSearchService::getPostIdsAround(
|
||||||
|
$this->context->transport->lastSearchQuery, $id);
|
||||||
}
|
}
|
||||||
PostSearchService::enableTokenLimit(true);
|
|
||||||
|
|
||||||
$favorite = $this->context->user->hasFavorited($post);
|
$favorite = $this->context->user->hasFavorited($post);
|
||||||
$score = $this->context->user->getScore($post);
|
$score = $this->context->user->getScore($post);
|
||||||
$flagged = in_array(TextHelper::reprPost($post), SessionHelper::get('flagged', []));
|
$flagged = in_array(TextHelper::reprPost($post), SessionHelper::get('flagged', []));
|
||||||
|
|
||||||
$this->context->pageThumb = \Chibi\UrlHelper::route('post', 'thumb', ['name' => $post->name]);
|
|
||||||
$this->context->stylesheets []= 'post-view.css';
|
|
||||||
$this->context->stylesheets []= 'comment-small.css';
|
|
||||||
$this->context->scripts []= 'post-view.js';
|
|
||||||
$this->context->subTitle = 'showing ' . TextHelper::reprPost($post) . ' – ' . TextHelper::reprTags($post->getTags());
|
|
||||||
$this->context->favorite = $favorite;
|
$this->context->favorite = $favorite;
|
||||||
$this->context->score = $score;
|
$this->context->score = $score;
|
||||||
$this->context->flagged = $flagged;
|
$this->context->flagged = $flagged;
|
||||||
$this->context->transport->post = $post;
|
$this->context->transport->post = $post;
|
||||||
$this->context->transport->prevPostId = $prevPost ? $prevPost->id : null;
|
$this->context->transport->prevPostId = $prevPostId ? $prevPostId : null;
|
||||||
$this->context->transport->nextPostId = $nextPost ? $nextPost->id : null;
|
$this->context->transport->nextPostId = $nextPostId ? $nextPostId : null;
|
||||||
$this->context->transport->editToken = self::serializePost($post);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -536,18 +500,15 @@ class PostController
|
|||||||
|
|
||||||
$path = TextHelper::absolutePath($this->config->main->filesPath . DS . $post->name);
|
$path = TextHelper::absolutePath($this->config->main->filesPath . DS . $post->name);
|
||||||
if (!file_exists($path))
|
if (!file_exists($path))
|
||||||
throw new SimpleException('Post file does not exist');
|
throw new SimpleNotFoundException('Post file does not exist');
|
||||||
if (!is_readable($path))
|
if (!is_readable($path))
|
||||||
throw new SimpleException('Post file is not readable');
|
throw new SimpleException('Post file is not readable');
|
||||||
|
|
||||||
$ext = substr($post->origName, strrpos($post->origName, '.') + 1);
|
|
||||||
if (strpos($post->origName, '.') === false)
|
|
||||||
$ext = 'dat';
|
|
||||||
$fn = sprintf('%s_%s_%s.%s',
|
$fn = sprintf('%s_%s_%s.%s',
|
||||||
$this->config->main->title,
|
$this->config->main->title,
|
||||||
$post->id,
|
$post->id,
|
||||||
join(',', array_map(function($tag) { return $tag->name; }, $post->getTags())),
|
join(',', array_map(function($tag) { return $tag->name; }, $post->getTags())),
|
||||||
$ext);
|
TextHelper::resolveMimeType($post->mimeType) ?: 'dat');
|
||||||
$fn = preg_replace('/[[:^print:]]/', '', $fn);
|
$fn = preg_replace('/[[:^print:]]/', '', $fn);
|
||||||
|
|
||||||
$ttl = 60 * 60 * 24 * 14;
|
$ttl = 60 * 60 * 24 * 14;
|
||||||
@ -575,6 +536,7 @@ class PostController
|
|||||||
|
|
||||||
$srcPath = $suppliedFile['tmp_name'];
|
$srcPath = $suppliedFile['tmp_name'];
|
||||||
$post->setContentFromPath($srcPath);
|
$post->setContentFromPath($srcPath);
|
||||||
|
$post->origName = $suppliedFile['name'];
|
||||||
|
|
||||||
if (!$isNew)
|
if (!$isNew)
|
||||||
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
||||||
@ -586,6 +548,7 @@ class PostController
|
|||||||
|
|
||||||
$url = InputHelper::get('url');
|
$url = InputHelper::get('url');
|
||||||
$post->setContentFromUrl($url);
|
$post->setContentFromUrl($url);
|
||||||
|
$post->origName = $url;
|
||||||
|
|
||||||
if (!$isNew)
|
if (!$isNew)
|
||||||
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
||||||
|
@ -3,19 +3,26 @@ class TagController
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @route /tags
|
* @route /tags
|
||||||
|
* @route /tags/{page}
|
||||||
* @route /tags/{filter}
|
* @route /tags/{filter}
|
||||||
|
* @route /tags/{filter}/{page}
|
||||||
* @validate filter [a-zA-Z\32:,_-]+
|
* @validate filter [a-zA-Z\32:,_-]+
|
||||||
|
* @validate page \d*
|
||||||
*/
|
*/
|
||||||
public function listAction($filter = null)
|
public function listAction($filter = null, $page = 1)
|
||||||
{
|
{
|
||||||
$this->context->stylesheets []= 'tag-list.css';
|
|
||||||
$this->context->subTitle = 'tags';
|
|
||||||
$this->context->viewName = 'tag-list-wrapper';
|
$this->context->viewName = 'tag-list-wrapper';
|
||||||
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ListTags);
|
PrivilegesHelper::confirmWithException(Privilege::ListTags);
|
||||||
$suppliedFilter = $filter ?: InputHelper::get('filter') ?: 'order:alpha,asc';
|
|
||||||
|
|
||||||
$tags = TagSearchService::getEntitiesRows($suppliedFilter, null, null);
|
$suppliedFilter = $filter ?: InputHelper::get('filter') ?: 'order:alpha,asc';
|
||||||
|
$page = max(1, intval($page));
|
||||||
|
$tagsPerPage = intval($this->config->browsing->tagsPerPage);
|
||||||
|
|
||||||
|
$tags = TagSearchService::getEntitiesRows($suppliedFilter, $tagsPerPage, $page);
|
||||||
|
$tagCount = TagSearchService::getEntityCount($suppliedFilter);
|
||||||
|
$pageCount = ceil($tagCount / $tagsPerPage);
|
||||||
|
$page = min($pageCount, $page);
|
||||||
|
|
||||||
$this->context->filter = $suppliedFilter;
|
$this->context->filter = $suppliedFilter;
|
||||||
$this->context->transport->tags = $tags;
|
$this->context->transport->tags = $tags;
|
||||||
|
|
||||||
@ -25,6 +32,15 @@ class TagController
|
|||||||
return ['name' => $tag['name'], 'count' => $tag['post_count']];
|
return ['name' => $tag['name'], 'count' => $tag['post_count']];
|
||||||
}, $this->context->transport->tags));
|
}, $this->context->transport->tags));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->context->highestUsage = TagSearchService::getMostUsedTag()['post_count'];
|
||||||
|
$this->context->transport->paginator = new StdClass;
|
||||||
|
$this->context->transport->paginator->page = $page;
|
||||||
|
$this->context->transport->paginator->pageCount = $pageCount;
|
||||||
|
$this->context->transport->paginator->entityCount = $tagCount;
|
||||||
|
$this->context->transport->paginator->entities = $tags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,9 +48,8 @@ class TagController
|
|||||||
*/
|
*/
|
||||||
public function mergeAction()
|
public function mergeAction()
|
||||||
{
|
{
|
||||||
$this->context->stylesheets []= 'tag-list.css';
|
|
||||||
$this->context->subTitle = 'tags';
|
|
||||||
$this->context->viewName = 'tag-list-wrapper';
|
$this->context->viewName = 'tag-list-wrapper';
|
||||||
|
$this->context->handleExceptions = true;
|
||||||
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
@ -49,9 +64,8 @@ class TagController
|
|||||||
|
|
||||||
TagModel::merge($suppliedSourceTag, $suppliedTargetTag);
|
TagModel::merge($suppliedSourceTag, $suppliedTargetTag);
|
||||||
|
|
||||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
|
||||||
LogHelper::log('{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
LogHelper::log('{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||||
StatusHelper::success();
|
StatusHelper::success('Tags merged successfully.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,9 +74,8 @@ class TagController
|
|||||||
*/
|
*/
|
||||||
public function renameAction()
|
public function renameAction()
|
||||||
{
|
{
|
||||||
$this->context->stylesheets []= 'tag-list.css';
|
|
||||||
$this->context->subTitle = 'tags';
|
|
||||||
$this->context->viewName = 'tag-list-wrapper';
|
$this->context->viewName = 'tag-list-wrapper';
|
||||||
|
$this->context->handleExceptions = true;
|
||||||
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
@ -77,9 +90,8 @@ class TagController
|
|||||||
|
|
||||||
TagModel::rename($suppliedSourceTag, $suppliedTargetTag);
|
TagModel::rename($suppliedSourceTag, $suppliedTargetTag);
|
||||||
|
|
||||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
|
||||||
LogHelper::log('{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
LogHelper::log('{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||||
StatusHelper::success();
|
StatusHelper::success('Tag renamed successfully.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,20 +100,24 @@ class TagController
|
|||||||
*/
|
*/
|
||||||
public function massTagRedirectAction()
|
public function massTagRedirectAction()
|
||||||
{
|
{
|
||||||
$this->context->stylesheets []= 'tag-list.css';
|
|
||||||
$this->context->subTitle = 'tags';
|
|
||||||
$this->context->viewName = 'tag-list-wrapper';
|
$this->context->viewName = 'tag-list-wrapper';
|
||||||
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
|
$suppliedOldPage = intval(InputHelper::get('old-page'));
|
||||||
|
$suppliedOldQuery = InputHelper::get('old-query');
|
||||||
$suppliedQuery = InputHelper::get('query');
|
$suppliedQuery = InputHelper::get('query');
|
||||||
if (!$suppliedQuery)
|
|
||||||
$suppliedQuery = ' ';
|
|
||||||
$suppliedTag = InputHelper::get('tag');
|
$suppliedTag = InputHelper::get('tag');
|
||||||
if (!empty($suppliedTag))
|
|
||||||
$suppliedTag = TagModel::validateTag($suppliedTag);
|
$params = [
|
||||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', ['source' => 'mass-tag', 'query' => $suppliedQuery, 'additionalInfo' => $suppliedTag]));
|
'source' => 'mass-tag',
|
||||||
|
'query' => $suppliedQuery ?: ' ',
|
||||||
|
'additionalInfo' => $suppliedTag ? TagModel::validateTag($suppliedTag) : '',
|
||||||
|
];
|
||||||
|
if ($suppliedOldPage != 0 and $suppliedOldQuery == $suppliedQuery)
|
||||||
|
$params['page'] = $suppliedOldPage;
|
||||||
|
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', $params));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,6 @@ class UserController
|
|||||||
$this->context->transport->user = $user;
|
$this->context->transport->user = $user;
|
||||||
$this->context->handleExceptions = true;
|
$this->context->handleExceptions = true;
|
||||||
$this->context->viewName = 'user-view';
|
$this->context->viewName = 'user-view';
|
||||||
$this->context->stylesheets []= 'user-view.css';
|
|
||||||
$this->context->subTitle = $user->name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function sendTokenizedEmail(
|
private static function sendTokenizedEmail(
|
||||||
@ -102,41 +100,32 @@ class UserController
|
|||||||
/**
|
/**
|
||||||
* @route /users
|
* @route /users
|
||||||
* @route /users/{page}
|
* @route /users/{page}
|
||||||
* @route /users/{sortStyle}
|
* @route /users/{filter}
|
||||||
* @route /users/{sortStyle}/{page}
|
* @route /users/{filter}/{page}
|
||||||
* @validate sortStyle alpha|alpha,asc|alpha,desc|date,asc|date,desc|pending
|
* @validate filter [a-zA-Z\32:,_-]+
|
||||||
* @validate page [0-9]+
|
* @validate page [0-9]+
|
||||||
*/
|
*/
|
||||||
public function listAction($sortStyle, $page)
|
public function listAction($filter, $page)
|
||||||
{
|
{
|
||||||
$this->context->stylesheets []= 'user-list.css';
|
|
||||||
$this->context->stylesheets []= 'paginator.css';
|
|
||||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
|
||||||
$this->context->scripts []= 'paginator-endless.js';
|
|
||||||
|
|
||||||
if ($sortStyle == '' or $sortStyle == 'alpha')
|
|
||||||
$sortStyle = 'alpha,asc';
|
|
||||||
if ($sortStyle == 'date')
|
|
||||||
$sortStyle = 'date,asc';
|
|
||||||
|
|
||||||
$page = intval($page);
|
|
||||||
$usersPerPage = intval($this->config->browsing->usersPerPage);
|
|
||||||
$this->context->subTitle = 'users';
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
||||||
|
|
||||||
$page = max(1, $page);
|
$suppliedFilter = $filter ?: InputHelper::get('filter') ?: 'order:alpha,asc';
|
||||||
$users = UserSearchService::getEntities($sortStyle, $usersPerPage, $page);
|
$page = max(1, intval($page));
|
||||||
$userCount = UserSearchService::getEntityCount($sortStyle, $usersPerPage, $page);
|
$usersPerPage = intval($this->config->browsing->usersPerPage);
|
||||||
$pageCount = ceil($userCount / $usersPerPage);
|
|
||||||
|
|
||||||
$this->context->sortStyle = $sortStyle;
|
$users = UserSearchService::getEntities($suppliedFilter, $usersPerPage, $page);
|
||||||
|
$userCount = UserSearchService::getEntityCount($suppliedFilter);
|
||||||
|
$pageCount = ceil($userCount / $usersPerPage);
|
||||||
|
$page = min($pageCount, $page);
|
||||||
|
|
||||||
|
$this->context->filter = $suppliedFilter;
|
||||||
|
$this->context->transport->users = $users;
|
||||||
$this->context->transport->paginator = new StdClass;
|
$this->context->transport->paginator = new StdClass;
|
||||||
$this->context->transport->paginator->page = $page;
|
$this->context->transport->paginator->page = $page;
|
||||||
$this->context->transport->paginator->pageCount = $pageCount;
|
$this->context->transport->paginator->pageCount = $pageCount;
|
||||||
$this->context->transport->paginator->entityCount = $userCount;
|
$this->context->transport->paginator->entityCount = $userCount;
|
||||||
$this->context->transport->paginator->entities = $users;
|
$this->context->transport->paginator->entities = $users;
|
||||||
$this->context->transport->paginator->params = func_get_args();
|
$this->context->transport->paginator->params = func_get_args();
|
||||||
$this->context->transport->users = $users;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -148,7 +137,7 @@ class UserController
|
|||||||
public function flagAction($name)
|
public function flagAction($name)
|
||||||
{
|
{
|
||||||
$user = UserModel::findByNameOrEmail($name);
|
$user = UserModel::findByNameOrEmail($name);
|
||||||
PrivilegesHelper::confirmWithException(Privilege::FlagUser);
|
PrivilegesHelper::confirmWithException(Privilege::FlagUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
@ -422,12 +411,6 @@ class UserController
|
|||||||
|
|
||||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||||
$this->loadUserView($user);
|
$this->loadUserView($user);
|
||||||
$this->context->stylesheets []= 'post-list.css';
|
|
||||||
$this->context->stylesheets []= 'post-small.css';
|
|
||||||
$this->context->stylesheets []= 'paginator.css';
|
|
||||||
$this->context->scripts []= 'post-list.js';
|
|
||||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
|
||||||
$this->context->scripts []= 'paginator-endless.js';
|
|
||||||
|
|
||||||
$query = '';
|
$query = '';
|
||||||
if ($tab == 'uploads')
|
if ($tab == 'uploads')
|
||||||
@ -483,8 +466,6 @@ class UserController
|
|||||||
public function registrationAction()
|
public function registrationAction()
|
||||||
{
|
{
|
||||||
$this->context->handleExceptions = true;
|
$this->context->handleExceptions = true;
|
||||||
$this->context->stylesheets []= 'auth.css';
|
|
||||||
$this->context->subTitle = 'registration form';
|
|
||||||
|
|
||||||
//check if already logged in
|
//check if already logged in
|
||||||
if ($this->context->loggedIn)
|
if ($this->context->loggedIn)
|
||||||
@ -570,8 +551,8 @@ class UserController
|
|||||||
*/
|
*/
|
||||||
public function activationAction($token)
|
public function activationAction($token)
|
||||||
{
|
{
|
||||||
$this->context->subTitle = 'account activation';
|
|
||||||
$this->context->viewName = 'message';
|
$this->context->viewName = 'message';
|
||||||
|
LayoutHelper::setSubTitle('account activation');
|
||||||
|
|
||||||
$dbToken = TokenModel::findByToken($token);
|
$dbToken = TokenModel::findByToken($token);
|
||||||
TokenModel::checkValidity($dbToken);
|
TokenModel::checkValidity($dbToken);
|
||||||
@ -603,8 +584,8 @@ class UserController
|
|||||||
*/
|
*/
|
||||||
public function passwordResetAction($token)
|
public function passwordResetAction($token)
|
||||||
{
|
{
|
||||||
$this->context->subTitle = 'password reset';
|
|
||||||
$this->context->viewName = 'message';
|
$this->context->viewName = 'message';
|
||||||
|
LayoutHelper::setSubTitle('password reset');
|
||||||
|
|
||||||
$dbToken = TokenModel::findByToken($token);
|
$dbToken = TokenModel::findByToken($token);
|
||||||
TokenModel::checkValidity($dbToken);
|
TokenModel::checkValidity($dbToken);
|
||||||
@ -637,9 +618,8 @@ class UserController
|
|||||||
*/
|
*/
|
||||||
public function passwordResetProxyAction()
|
public function passwordResetProxyAction()
|
||||||
{
|
{
|
||||||
$this->context->subTtile = 'password reset';
|
|
||||||
$this->context->viewName = 'user-select';
|
$this->context->viewName = 'user-select';
|
||||||
$this->context->stylesheets []= 'auth.css';
|
LayoutHelper::setSubTitle('password reset');
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
@ -658,9 +638,8 @@ class UserController
|
|||||||
*/
|
*/
|
||||||
public function activationProxyAction()
|
public function activationProxyAction()
|
||||||
{
|
{
|
||||||
$this->context->subTitle = 'account activation';
|
|
||||||
$this->context->viewName = 'user-select';
|
$this->context->viewName = 'user-select';
|
||||||
$this->context->stylesheets []= 'auth.css';
|
LayoutHelper::setSubTitle('account activation');
|
||||||
|
|
||||||
if (InputHelper::get('submit'))
|
if (InputHelper::get('submit'))
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@ class CustomMarkdown extends \Michelf\Markdown
|
|||||||
{
|
{
|
||||||
$this->simple = $simple;
|
$this->simple = $simple;
|
||||||
$this->no_markup = true;
|
$this->no_markup = true;
|
||||||
$this->block_gamut += ['doSpoilers' => 71];
|
$this->span_gamut += ['doSpoilers' => 71];
|
||||||
$this->span_gamut += ['doSearchPermalinks' => 72];
|
$this->span_gamut += ['doSearchPermalinks' => 72];
|
||||||
$this->span_gamut += ['doStrike' => 6];
|
$this->span_gamut += ['doStrike' => 6];
|
||||||
$this->span_gamut += ['doUsers' => 7];
|
$this->span_gamut += ['doUsers' => 7];
|
||||||
@ -27,6 +27,15 @@ class CustomMarkdown extends \Michelf\Markdown
|
|||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//make atx-style headers require space after hash
|
||||||
|
protected function doHeaders($text)
|
||||||
|
{
|
||||||
|
$text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', [&$this, '_doHeaders_callback_setext'], $text);
|
||||||
|
$text = preg_replace_callback('{^(\#{1,6})[ ]+(.+?)[ ]*\#*\n+}xm', [&$this, '_doHeaders_callback_atx'], $text);
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
//disable paragraph forming when using simple markdown
|
||||||
protected function formParagraphs($text)
|
protected function formParagraphs($text)
|
||||||
{
|
{
|
||||||
if ($this->simple)
|
if ($this->simple)
|
||||||
@ -56,6 +65,7 @@ class CustomMarkdown extends \Michelf\Markdown
|
|||||||
return $parser->transform($text);
|
return $parser->transform($text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//automatically form links out of http://(...) and www.(...)
|
||||||
protected function doAutoLinks2($text)
|
protected function doAutoLinks2($text)
|
||||||
{
|
{
|
||||||
$text = preg_replace_callback('{(?<!<)((https?|ftp):[^\'"><\s(){}]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
$text = preg_replace_callback('{(?<!<)((https?|ftp):[^\'"><\s(){}]+)}i', [&$this, '_doAutoLinks_url_callback'], $text);
|
||||||
@ -63,6 +73,7 @@ class CustomMarkdown extends \Michelf\Markdown
|
|||||||
return $text;
|
return $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//extend anchors callback for doAutolinks2
|
||||||
protected function _doAnchors_inline_callback($matches)
|
protected function _doAnchors_inline_callback($matches)
|
||||||
{
|
{
|
||||||
if ($matches[3] == '')
|
if ($matches[3] == '')
|
||||||
@ -74,7 +85,10 @@ class CustomMarkdown extends \Michelf\Markdown
|
|||||||
return parent::_doAnchors_inline_callback($matches);
|
return parent::_doAnchors_inline_callback($matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _doCodeBlocks_callback($matches) {
|
//handle white characters inside code blocks
|
||||||
|
//so that they won't be optimized away by prettifying HTML
|
||||||
|
protected function _doCodeBlocks_callback($matches)
|
||||||
|
{
|
||||||
$codeblock = $matches[1];
|
$codeblock = $matches[1];
|
||||||
|
|
||||||
$codeblock = $this->outdent($codeblock);
|
$codeblock = $this->outdent($codeblock);
|
||||||
@ -89,7 +103,8 @@ class CustomMarkdown extends \Michelf\Markdown
|
|||||||
return "\n\n".$this->hashBlock($codeblock)."\n\n";
|
return "\n\n".$this->hashBlock($codeblock)."\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//change hard breaks trigger - simple \n followed by text
|
||||||
|
//instead of two spaces followed by \n
|
||||||
protected function doHardBreaks($text)
|
protected function doHardBreaks($text)
|
||||||
{
|
{
|
||||||
return preg_replace_callback('/\n(?=[\[\]\(\)\w])/', [&$this, '_doHardBreaks_callback'], $text);
|
return preg_replace_callback('/\n(?=[\[\]\(\)\w])/', [&$this, '_doHardBreaks_callback'], $text);
|
||||||
@ -122,7 +137,7 @@ class CustomMarkdown extends \Michelf\Markdown
|
|||||||
protected function doTags($text)
|
protected function doTags($text)
|
||||||
{
|
{
|
||||||
$link = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']);
|
$link = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']);
|
||||||
return preg_replace_callback('/(?:(?<![^\s\(\)\[\]]))#([a-zA-Z0-9_-]+)/', function($x) use ($link)
|
return preg_replace_callback('/(?:(?<![^\s\(\)\[\]]))#([()\[\]a-zA-Z0-9_.-]+)/', function($x) use ($link)
|
||||||
{
|
{
|
||||||
return $this->hashPart('<a href="' . str_replace('_query_', $x[1], $link) . '">' . $x[0] . '</a>');
|
return $this->hashPart('<a href="' . str_replace('_query_', $x[1], $link) . '">' . $x[0] . '</a>');
|
||||||
}, $text);
|
}, $text);
|
||||||
|
118
src/Database.php
118
src/Database.php
@ -1,118 +0,0 @@
|
|||||||
<?php
|
|
||||||
class Database
|
|
||||||
{
|
|
||||||
protected static $pdo = null;
|
|
||||||
protected static $queries = [];
|
|
||||||
|
|
||||||
public static function connect($driver, $location, $user, $pass)
|
|
||||||
{
|
|
||||||
if (self::connected())
|
|
||||||
throw new Exception('Database is already connected');
|
|
||||||
|
|
||||||
$dsn = $driver . ':' . $location;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
self::$pdo = new PDO($dsn, $user, $pass);
|
|
||||||
self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
self::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
|
||||||
self::$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
|
||||||
}
|
|
||||||
catch (Exception $e)
|
|
||||||
{
|
|
||||||
self::$pdo = null;
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function makeStatement(SqlQuery $sqlQuery)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$stmt = self::$pdo->prepare($sqlQuery->getSql());
|
|
||||||
}
|
|
||||||
catch (Exception $e)
|
|
||||||
{
|
|
||||||
throw new Exception('Problem with ' . $sqlQuery->getSql() . ' (' . $e->getMessage() . ')');
|
|
||||||
}
|
|
||||||
foreach ($sqlQuery->getBindings() as $key => $value)
|
|
||||||
$stmt->bindValue(is_numeric($key) ? $key + 1 : $key, $value);
|
|
||||||
return $stmt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function disconnect()
|
|
||||||
{
|
|
||||||
self::$pdo = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function connected()
|
|
||||||
{
|
|
||||||
return self::$pdo !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function query(SqlQuery $sqlQuery)
|
|
||||||
{
|
|
||||||
if (!self::connected())
|
|
||||||
throw new Exception('Database is not connected');
|
|
||||||
$statement = self::makeStatement($sqlQuery);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$statement->execute();
|
|
||||||
}
|
|
||||||
catch (Exception $e)
|
|
||||||
{
|
|
||||||
throw new Exception('Problem with ' . $sqlQuery->getSql() . ' (' . $e->getMessage() . ')');
|
|
||||||
}
|
|
||||||
self::$queries []= $sqlQuery;
|
|
||||||
return $statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fetchOne(SqlQuery $sqlQuery)
|
|
||||||
{
|
|
||||||
$statement = self::query($sqlQuery);
|
|
||||||
return $statement->fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fetchAll(SqlQuery $sqlQuery)
|
|
||||||
{
|
|
||||||
$statement = self::query($sqlQuery);
|
|
||||||
return $statement->fetchAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getLogs()
|
|
||||||
{
|
|
||||||
return self::$queries;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function inTransaction()
|
|
||||||
{
|
|
||||||
return self::$pdo->inTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function lastInsertId()
|
|
||||||
{
|
|
||||||
return self::$pdo->lastInsertId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function transaction($func)
|
|
||||||
{
|
|
||||||
if (self::inTransaction())
|
|
||||||
{
|
|
||||||
return $func();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
self::$pdo->beginTransaction();
|
|
||||||
$ret = $func();
|
|
||||||
self::$pdo->commit();
|
|
||||||
return $ret;
|
|
||||||
}
|
|
||||||
catch (Exception $e)
|
|
||||||
{
|
|
||||||
self::$pdo->rollBack();
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
40
src/Helpers/CustomAssetViewDecorator.php
Normal file
40
src/Helpers/CustomAssetViewDecorator.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
class CustomAssetViewDecorator extends \Chibi\AssetViewDecorator
|
||||||
|
{
|
||||||
|
private static $pageThumb = null;
|
||||||
|
private static $subTitle = null;
|
||||||
|
|
||||||
|
public static function setSubTitle($text)
|
||||||
|
{
|
||||||
|
self::$subTitle = $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setPageThumb($path)
|
||||||
|
{
|
||||||
|
self::$pageThumb = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transformHtml($html)
|
||||||
|
{
|
||||||
|
self::$title = isset(self::$subTitle)
|
||||||
|
? sprintf('%s – %s', self::$title, self::$subTitle)
|
||||||
|
: self::$title;
|
||||||
|
|
||||||
|
$html = parent::transformHtml($html);
|
||||||
|
|
||||||
|
$headSnippet = '<meta property="og:title" content="' . self::$title . '"/>';
|
||||||
|
$headSnippet .= '<meta property="og:url" content="' . \Chibi\UrlHelper::currentUrl() . '"/>';
|
||||||
|
if (!empty(self::$pageThumb))
|
||||||
|
$headSnippet .= '<meta property="og:image" content="' . self::$pageThumb . '"/>';
|
||||||
|
|
||||||
|
$bodySnippet = '<script type="text/javascript">';
|
||||||
|
$bodySnippet .= '$(function() {';
|
||||||
|
$bodySnippet .= '$(\'body\').trigger(\'dom-update\');';
|
||||||
|
$bodySnippet .= '});';
|
||||||
|
$bodySnippet .= '</script>';
|
||||||
|
|
||||||
|
$html = str_replace('</head>', $headSnippet . '</head>', $html);
|
||||||
|
$html = str_replace('</body>', $bodySnippet . '</body>', $html);
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,12 @@ class PrivilegesHelper
|
|||||||
|
|
||||||
$minAccessRank = TextHelper::resolveConstant($minAccessRankName, 'AccessRank');
|
$minAccessRank = TextHelper::resolveConstant($minAccessRankName, 'AccessRank');
|
||||||
self::$privileges[$key] = $minAccessRank;
|
self::$privileges[$key] = $minAccessRank;
|
||||||
|
|
||||||
|
if (!isset(self::$privileges[$privilegeName]) or
|
||||||
|
self::$privileges[$privilegeName] > $minAccessRank)
|
||||||
|
{
|
||||||
|
self::$privileges[$privilegeName] = $minAccessRank;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,10 +54,8 @@ class PrivilegesHelper
|
|||||||
public static function confirmWithException($privilege, $subPrivilege = null)
|
public static function confirmWithException($privilege, $subPrivilege = null)
|
||||||
{
|
{
|
||||||
if (!self::confirm($privilege, $subPrivilege))
|
if (!self::confirm($privilege, $subPrivilege))
|
||||||
{
|
|
||||||
throw new SimpleException('Insufficient privileges');
|
throw new SimpleException('Insufficient privileges');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static function getIdentitySubPrivilege($user)
|
public static function getIdentitySubPrivilege($user)
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,9 @@ class StatusHelper
|
|||||||
$context = \Chibi\Registry::getContext();
|
$context = \Chibi\Registry::getContext();
|
||||||
if (!empty($message))
|
if (!empty($message))
|
||||||
{
|
{
|
||||||
|
if (!preg_match('/[.?!]$/', $message))
|
||||||
|
$message .= '.';
|
||||||
|
|
||||||
$context->transport->message = $message;
|
$context->transport->message = $message;
|
||||||
$context->transport->messageHtml = TextHelper::parseMarkdown($message, true);
|
$context->transport->messageHtml = TextHelper::parseMarkdown($message, true);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class TextHelper
|
|||||||
//todo: convert to enum and make one method
|
//todo: convert to enum and make one method
|
||||||
public static function snakeCaseToCamelCase($string, $lower = false)
|
public static function snakeCaseToCamelCase($string, $lower = false)
|
||||||
{
|
{
|
||||||
$string = preg_split('/_/', $string);
|
$string = explode('_', $string);
|
||||||
$string = array_map('trim', $string);
|
$string = array_map('trim', $string);
|
||||||
$string = array_map('ucfirst', $string);
|
$string = array_map('ucfirst', $string);
|
||||||
$string = join('', $string);
|
$string = join('', $string);
|
||||||
@ -31,7 +31,7 @@ class TextHelper
|
|||||||
|
|
||||||
public static function kebabCaseToCamelCase($string)
|
public static function kebabCaseToCamelCase($string)
|
||||||
{
|
{
|
||||||
$string = preg_split('/-/', $string);
|
$string = explode('-', $string);
|
||||||
$string = array_map('trim', $string);
|
$string = array_map('trim', $string);
|
||||||
$string = array_map('ucfirst', $string);
|
$string = array_map('ucfirst', $string);
|
||||||
$string = join('', $string);
|
$string = join('', $string);
|
||||||
@ -87,33 +87,44 @@ class TextHelper
|
|||||||
{
|
{
|
||||||
$suffix = substr($string, -1, 1);
|
$suffix = substr($string, -1, 1);
|
||||||
$index = array_search($suffix, $suffixes);
|
$index = array_search($suffix, $suffixes);
|
||||||
if ($index === false)
|
return floatval($string) * pow($base, $index !== false ? $index : 0);
|
||||||
return $string;
|
|
||||||
$number = intval($string);
|
|
||||||
for ($i = 0; $i < $index; $i ++)
|
|
||||||
$number *= $base;
|
|
||||||
return $number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function useUnits($number, $base, $suffixes)
|
private static function useUnits($number, $base, $suffixes, $fmtCallback = null)
|
||||||
{
|
{
|
||||||
$suffix = array_shift($suffixes);
|
$suffix = array_shift($suffixes);
|
||||||
if ($number < $base)
|
|
||||||
{
|
while ($number >= $base and !empty($suffixes))
|
||||||
return sprintf('%d%s', $number, $suffix);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
$suffix = array_shift($suffixes);
|
$suffix = array_shift($suffixes);
|
||||||
$number /= (float) $base;
|
$number /= (float) $base;
|
||||||
}
|
}
|
||||||
while ($number >= $base and !empty($suffixes));
|
|
||||||
|
if ($fmtCallback === null)
|
||||||
|
{
|
||||||
|
$fmtCallback = function($number, $suffix)
|
||||||
|
{
|
||||||
|
if ($suffix == '')
|
||||||
|
return $number;
|
||||||
return sprintf('%.01f%s', $number, $suffix);
|
return sprintf('%.01f%s', $number, $suffix);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fmtCallback($number, $suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function useBytesUnits($number)
|
public static function useBytesUnits($number)
|
||||||
{
|
{
|
||||||
return self::useUnits($number, 1024, ['B', 'K', 'M', 'G']);
|
return self::useUnits(
|
||||||
|
$number,
|
||||||
|
1024,
|
||||||
|
['B', 'K', 'M', 'G'],
|
||||||
|
function($number, $suffix)
|
||||||
|
{
|
||||||
|
if ($number < 20 and $suffix != 'B')
|
||||||
|
return sprintf('%.01f%s', $number, $suffix);
|
||||||
|
return sprintf('%.0f%s', $number, $suffix);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function useDecimalUnits($number)
|
public static function useDecimalUnits($number)
|
||||||
@ -251,6 +262,13 @@ class TextHelper
|
|||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function secureWhitespace($text)
|
||||||
|
{
|
||||||
|
$text = str_replace(["\r\n", "\r", "\n"], ' ', $text);
|
||||||
|
$text = str_replace(' ', ' ', $text);
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
const HTML_OPEN = 1;
|
const HTML_OPEN = 1;
|
||||||
const HTML_CLOSE = 2;
|
const HTML_CLOSE = 2;
|
||||||
const HTML_LEAF = 3;
|
const HTML_LEAF = 3;
|
||||||
@ -277,4 +295,60 @@ class TextHelper
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function formatDate($date, $plain = true)
|
||||||
|
{
|
||||||
|
if (!$date)
|
||||||
|
return 'Unknown';
|
||||||
|
if ($plain)
|
||||||
|
return date('Y-m-d H:i:s', $date);
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
$diff = abs($now - $date);
|
||||||
|
$future = $now < $date;
|
||||||
|
|
||||||
|
$mul = 60;
|
||||||
|
if ($diff < $mul)
|
||||||
|
return $future ? 'in a few seconds' : 'just now';
|
||||||
|
if ($diff < $mul * 2)
|
||||||
|
return $future ? 'in a minute' : 'a minute ago';
|
||||||
|
|
||||||
|
$prevMul = $mul; $mul *= 60;
|
||||||
|
if ($diff < $mul)
|
||||||
|
return $future ? 'in ' . round($diff / $prevMul) . ' minutes' : round($diff / $prevMul) . ' minutes ago';
|
||||||
|
if ($diff < $mul * 2)
|
||||||
|
return $future ? 'in an hour' : 'an hour ago';
|
||||||
|
|
||||||
|
$prevMul = $mul; $mul *= 24;
|
||||||
|
if ($diff < $mul)
|
||||||
|
return $future ? 'in ' . round($diff / $prevMul) . ' hours' : round($diff / $prevMul) . ' hours ago';
|
||||||
|
if ($diff < $mul * 2)
|
||||||
|
return $future ? 'tomorrow' : 'yesterday';
|
||||||
|
|
||||||
|
$prevMul = $mul; $mul *= 30.42;
|
||||||
|
if ($diff < $mul)
|
||||||
|
return $future ? 'in ' . round($diff / $prevMul) . ' days' : round($diff / $prevMul) . ' days ago';
|
||||||
|
if ($diff < $mul * 2)
|
||||||
|
return $future ? 'in a month' : 'a month ago';
|
||||||
|
|
||||||
|
$prevMul = $mul; $mul *= 12;
|
||||||
|
if ($diff < $mul)
|
||||||
|
return $future ? 'in ' . round($diff / $prevMul) . ' months' : round($diff / $prevMul) . ' months ago';
|
||||||
|
if ($diff < $mul * 2)
|
||||||
|
return $future ? 'in a year' : 'a year ago';
|
||||||
|
|
||||||
|
return $future ? 'in ' . round($diff / $mul) . ' years' : round($diff / $prevMul) . ' years ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resolveMimeType($mimeType)
|
||||||
|
{
|
||||||
|
$mimeTypes = [
|
||||||
|
'image/jpeg' => 'jpg',
|
||||||
|
'image/gif' => 'gif',
|
||||||
|
'image/png' => 'png',
|
||||||
|
'application/x-shockwave-flash' => 'swf'];
|
||||||
|
return isset($mimeTypes[$mimeType])
|
||||||
|
? $mimeTypes[$mimeType]
|
||||||
|
: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
abstract class AbstractCrudModel implements IModel
|
abstract class AbstractCrudModel implements IModel
|
||||||
{
|
{
|
||||||
public static function spawn()
|
public static function spawn()
|
||||||
@ -21,28 +24,28 @@ abstract class AbstractCrudModel implements IModel
|
|||||||
|
|
||||||
public static function findById($key, $throw = true)
|
public static function findById($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from(static::getTableName())
|
$stmt->setTable(static::getTableName());
|
||||||
->where('id = ?')->put($key);
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
if ($throw)
|
if ($throw)
|
||||||
throw new SimpleException('Invalid ' . static::getTableName() . ' ID "' . $key . '"');
|
throw new SimpleNotFoundException('Invalid ' . static::getTableName() . ' ID "' . $key . '"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findByIds(array $ids)
|
public static function findByIds(array $ids)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from(static::getTableName())
|
$stmt->setTable(static::getTableName());
|
||||||
->where('id')->in()->genSlots($ids)->put($ids);
|
$stmt->setCriterion(Sql\InFunctor::fromArray('id', Sql\Binding::fromArray(array_unique($ids))));
|
||||||
|
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
if ($rows)
|
if ($rows)
|
||||||
return self::convertRows($rows);
|
return self::convertRows($rows);
|
||||||
|
|
||||||
@ -51,9 +54,10 @@ abstract class AbstractCrudModel implements IModel
|
|||||||
|
|
||||||
public static function getCount()
|
public static function getCount()
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new Sql\SelectStatement();
|
||||||
$query->select('count(1)')->as('count')->from(static::getTableName());
|
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||||
return Database::fetchOne($query)['count'];
|
$stmt->setTable(static::getTableName());
|
||||||
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -79,9 +83,22 @@ abstract class AbstractCrudModel implements IModel
|
|||||||
|
|
||||||
public static function convertRows(array $rows)
|
public static function convertRows(array $rows)
|
||||||
{
|
{
|
||||||
|
$keyCache = [];
|
||||||
|
$entities = [];
|
||||||
foreach ($rows as $i => $row)
|
foreach ($rows as $i => $row)
|
||||||
$rows[$i] = self::convertRow($row);
|
{
|
||||||
return $rows;
|
$entity = self::spawn();
|
||||||
|
foreach ($row as $key => $val)
|
||||||
|
{
|
||||||
|
if (isset($keyCache[$key]))
|
||||||
|
$key = $keyCache[$key];
|
||||||
|
else
|
||||||
|
$key = $keyCache[$key] = TextHelper::snakeCaseToCamelCase($key, true);
|
||||||
|
$entity->$key = $val;
|
||||||
|
}
|
||||||
|
$entities[$i] = $entity;
|
||||||
|
}
|
||||||
|
return $entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,13 +110,9 @@ abstract class AbstractCrudModel implements IModel
|
|||||||
throw new Exception('Can be run only within transaction');
|
throw new Exception('Can be run only within transaction');
|
||||||
if (!$entity->id)
|
if (!$entity->id)
|
||||||
{
|
{
|
||||||
$config = \Chibi\Registry::getConfig();
|
$stmt = new Sql\InsertStatement();
|
||||||
$query = (new SqlQuery);
|
$stmt->setTable($table);
|
||||||
if ($config->main->dbDriver == 'sqlite')
|
Database::exec($stmt);
|
||||||
$query->insertInto($table)->defaultValues();
|
|
||||||
else
|
|
||||||
$query->insertInto($table)->values()->open()->close();
|
|
||||||
Database::query($query);
|
|
||||||
$entity->id = Database::lastInsertId();
|
$entity->id = Database::lastInsertId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class CommentModel extends AbstractCrudModel
|
class CommentModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
public static function getTableName()
|
public static function getTableName()
|
||||||
@ -25,13 +28,14 @@ class CommentModel extends AbstractCrudModel
|
|||||||
'comment_date' => $comment->commentDate,
|
'comment_date' => $comment->commentDate,
|
||||||
'commenter_id' => $comment->commenterId];
|
'commenter_id' => $comment->commenterId];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\UpdateStatement();
|
||||||
->update('comment')
|
$stmt->setTable('comment');
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($comment->id)));
|
||||||
->put(array_values($bindings))
|
|
||||||
->where('id = ?')->put($comment->id);
|
|
||||||
|
|
||||||
Database::query($query);
|
foreach ($bindings as $key => $val)
|
||||||
|
$stmt->setColumn($key, new Sql\Binding($val));
|
||||||
|
|
||||||
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,10 +43,10 @@ class CommentModel extends AbstractCrudModel
|
|||||||
{
|
{
|
||||||
Database::transaction(function() use ($comment)
|
Database::transaction(function() use ($comment)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('comment')
|
$stmt->setTable('comment');
|
||||||
->where('id = ?')->put($comment->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($comment->id)));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,14 +54,12 @@ class CommentModel extends AbstractCrudModel
|
|||||||
|
|
||||||
public static function findAllByPostId($key)
|
public static function findAllByPostId($key)
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new Sql\SelectStatement();
|
||||||
$query
|
$stmt->setColumn('comment.*');
|
||||||
->select('comment.*')
|
$stmt->setTable('comment');
|
||||||
->from('comment')
|
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', new Sql\Binding($key)));
|
||||||
->where('post_id = ?')
|
|
||||||
->put($key);
|
|
||||||
|
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
if ($rows)
|
if ($rows)
|
||||||
return self::convertRows($rows);
|
return self::convertRows($rows);
|
||||||
return [];
|
return [];
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class PostEntity extends AbstractEntity
|
class PostEntity extends AbstractEntity
|
||||||
{
|
{
|
||||||
public $type;
|
public $type;
|
||||||
@ -14,6 +17,9 @@ class PostEntity extends AbstractEntity
|
|||||||
public $imageHeight;
|
public $imageHeight;
|
||||||
public $uploaderId;
|
public $uploaderId;
|
||||||
public $source;
|
public $source;
|
||||||
|
public $commentCount;
|
||||||
|
public $favCount;
|
||||||
|
public $score;
|
||||||
|
|
||||||
public function getUploader()
|
public function getUploader()
|
||||||
{
|
{
|
||||||
@ -48,12 +54,12 @@ class PostEntity extends AbstractEntity
|
|||||||
{
|
{
|
||||||
if ($this->hasCache('favoritee'))
|
if ($this->hasCache('favoritee'))
|
||||||
return $this->getCache('favoritee');
|
return $this->getCache('favoritee');
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('user.*')
|
$stmt->setColumn('user.*');
|
||||||
->from('user')
|
$stmt->setTable('user');
|
||||||
->innerJoin('favoritee')->on('favoritee.user_id = user.id')
|
$stmt->addInnerJoin('favoritee', new Sql\EqualsFunctor('favoritee.user_id', 'user.id'));
|
||||||
->where('favoritee.post_id = ?')->put($this->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('favoritee.post_id', new Sql\Binding($this->id)));
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
$favorites = UserModel::convertRows($rows);
|
$favorites = UserModel::convertRows($rows);
|
||||||
$this->setCache('favoritee', $favorites);
|
$this->setCache('favoritee', $favorites);
|
||||||
return $favorites;
|
return $favorites;
|
||||||
@ -66,13 +72,20 @@ class PostEntity extends AbstractEntity
|
|||||||
if ($this->hasCache('relations'))
|
if ($this->hasCache('relations'))
|
||||||
return $this->getCache('relations');
|
return $this->getCache('relations');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('post.*')
|
$stmt->setColumn('post.*');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->innerJoin('crossref')
|
$binding = new Sql\Binding($this->id);
|
||||||
->on()->open()->raw('post.id = crossref.post2_id')->and('crossref.post_id = ?')->close()->put($this->id)
|
$stmt->addInnerJoin('crossref', (new Sql\DisjunctionFunctor)
|
||||||
->or()->open()->raw('post.id = crossref.post_id')->and('crossref.post2_id = ?')->close()->put($this->id);
|
->add(
|
||||||
$rows = Database::fetchAll($query);
|
(new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('post.id', 'crossref.post2_id'))
|
||||||
|
->add(new Sql\EqualsFunctor('crossref.post_id', $binding)))
|
||||||
|
->add(
|
||||||
|
(new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('post.id', 'crossref.post_id'))
|
||||||
|
->add(new Sql\EqualsFunctor('crossref.post2_id', $binding))));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
$posts = PostModel::convertRows($rows);
|
$posts = PostModel::convertRows($rows);
|
||||||
$this->setCache('relations', $posts);
|
$this->setCache('relations', $posts);
|
||||||
return $posts;
|
return $posts;
|
||||||
@ -231,7 +244,7 @@ class PostEntity extends AbstractEntity
|
|||||||
if ($this->type == PostType::Youtube)
|
if ($this->type == PostType::Youtube)
|
||||||
{
|
{
|
||||||
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
|
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
|
||||||
$contents = file_get_contents('http://img.youtube.com/vi/' . $this->origName . '/mqdefault.jpg');
|
$contents = file_get_contents('http://img.youtube.com/vi/' . $this->fileHash . '/mqdefault.jpg');
|
||||||
file_put_contents($tmpPath, $contents);
|
file_put_contents($tmpPath, $contents);
|
||||||
if (file_exists($tmpPath))
|
if (file_exists($tmpPath))
|
||||||
$srcImage = imagecreatefromjpeg($tmpPath);
|
$srcImage = imagecreatefromjpeg($tmpPath);
|
||||||
@ -330,7 +343,6 @@ class PostEntity extends AbstractEntity
|
|||||||
throw new SimpleException('Invalid file type "' . $this->mimeType . '"');
|
throw new SimpleException('Invalid file type "' . $this->mimeType . '"');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->origName = basename($srcPath);
|
|
||||||
$duplicatedPost = PostModel::findByHash($this->fileHash, false);
|
$duplicatedPost = PostModel::findByHash($this->fileHash, false);
|
||||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||||
@ -349,19 +361,16 @@ class PostEntity extends AbstractEntity
|
|||||||
|
|
||||||
public function setContentFromUrl($srcUrl)
|
public function setContentFromUrl($srcUrl)
|
||||||
{
|
{
|
||||||
$this->origName = $srcUrl;
|
|
||||||
|
|
||||||
if (!preg_match('/^https?:\/\//', $srcUrl))
|
if (!preg_match('/^https?:\/\//', $srcUrl))
|
||||||
throw new SimpleException('Invalid URL "' . $srcUrl . '"');
|
throw new SimpleException('Invalid URL "' . $srcUrl . '"');
|
||||||
|
|
||||||
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $srcUrl, $matches))
|
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $srcUrl, $matches))
|
||||||
{
|
{
|
||||||
$origName = $matches[1];
|
$youtubeId = $matches[1];
|
||||||
$this->origName = $origName;
|
|
||||||
$this->type = PostType::Youtube;
|
$this->type = PostType::Youtube;
|
||||||
$this->mimeType = null;
|
$this->mimeType = null;
|
||||||
$this->fileSize = null;
|
$this->fileSize = null;
|
||||||
$this->fileHash = $origName;
|
$this->fileHash = $youtubeId;
|
||||||
$this->imageWidth = null;
|
$this->imageWidth = null;
|
||||||
$this->imageHeight = null;
|
$this->imageHeight = null;
|
||||||
|
|
||||||
@ -369,7 +378,7 @@ class PostEntity extends AbstractEntity
|
|||||||
if (file_exists($thumbPath))
|
if (file_exists($thumbPath))
|
||||||
unlink($thumbPath);
|
unlink($thumbPath);
|
||||||
|
|
||||||
$duplicatedPost = PostModel::findByHash($origName, false);
|
$duplicatedPost = PostModel::findByHash($youtubeId, false);
|
||||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||||
return;
|
return;
|
||||||
@ -419,4 +428,21 @@ class PostEntity extends AbstractEntity
|
|||||||
unlink($srcPath);
|
unlink($srcPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function getEditToken()
|
||||||
|
{
|
||||||
|
$x = [];
|
||||||
|
foreach ($this->getTags() as $tag)
|
||||||
|
$x []= TextHelper::reprTag($tag->name);
|
||||||
|
foreach ($this->getRelations() as $relatedPost)
|
||||||
|
$x []= TextHelper::reprPost($relatedPost);
|
||||||
|
$x []= $this->safety;
|
||||||
|
$x []= $this->source;
|
||||||
|
$x []= $this->fileHash;
|
||||||
|
natcasesort($x);
|
||||||
|
$x = join(' ', $x);
|
||||||
|
return md5($x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class TagEntity extends AbstractEntity
|
class TagEntity extends AbstractEntity
|
||||||
{
|
{
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
public function getPostCount()
|
public function getPostCount()
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('count(*)')->as('count')
|
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||||
->from('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->where('tag_id = ?')->put($this->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('tag_id', new Sql\Binding($this->id)));
|
||||||
return Database::fetchOne($query)['count'];
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class UserEntity extends AbstractEntity
|
class UserEntity extends AbstractEntity
|
||||||
{
|
{
|
||||||
public $name;
|
public $name;
|
||||||
@ -8,6 +11,7 @@ class UserEntity extends AbstractEntity
|
|||||||
public $emailUnconfirmed;
|
public $emailUnconfirmed;
|
||||||
public $emailConfirmed;
|
public $emailConfirmed;
|
||||||
public $joinDate;
|
public $joinDate;
|
||||||
|
public $lastLoginDate;
|
||||||
public $accessRank;
|
public $accessRank;
|
||||||
public $settings;
|
public $settings;
|
||||||
public $banned;
|
public $banned;
|
||||||
@ -110,22 +114,24 @@ class UserEntity extends AbstractEntity
|
|||||||
|
|
||||||
public function hasFavorited($post)
|
public function hasFavorited($post)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('count(1)')->as('count')
|
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||||
->from('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->where('user_id = ?')->put($this->id)
|
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
->and('post_id = ?')->put($post->id);
|
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($this->id)))
|
||||||
return Database::fetchOne($query)['count'] == 1;
|
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id))));
|
||||||
|
return Database::fetchOne($stmt)['count'] == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getScore($post)
|
public function getScore($post)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('score')
|
$stmt->setColumn('score');
|
||||||
->from('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('user_id = ?')->put($this->id)
|
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
->and('post_id = ?')->put($post->id);
|
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($this->id)))
|
||||||
$row = Database::fetchOne($query);
|
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id))));
|
||||||
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return intval($row['score']);
|
return intval($row['score']);
|
||||||
return null;
|
return null;
|
||||||
@ -133,19 +139,28 @@ class UserEntity extends AbstractEntity
|
|||||||
|
|
||||||
public function getFavoriteCount()
|
public function getFavoriteCount()
|
||||||
{
|
{
|
||||||
$sqlQuery = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('count(1)')->as('count')
|
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||||
->from('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->where('user_id = ?')->put($this->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('user_id', new Sql\Binding($this->id)));
|
||||||
return Database::fetchOne($sqlQuery)['count'];
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCommentCount()
|
public function getCommentCount()
|
||||||
{
|
{
|
||||||
$sqlQuery = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('count(1)')->as('count')
|
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||||
->from('comment')
|
$stmt->setTable('comment');
|
||||||
->where('commenter_id = ?')->put($this->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('commenter_id', new Sql\Binding($this->id)));
|
||||||
return Database::fetchOne($sqlQuery)['count'];
|
return Database::fetchOne($stmt)['count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPostCount()
|
||||||
|
{
|
||||||
|
$stmt = new Sql\SelectStatement();
|
||||||
|
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||||
|
$stmt->setTable('post');
|
||||||
|
$stmt->setCriterion(new Sql\EqualsFunctor('uploader_id', new Sql\Binding($this->id)));
|
||||||
|
return Database::fetchOne($stmt)['count'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ class Privilege extends Enum
|
|||||||
const ListComments = 20;
|
const ListComments = 20;
|
||||||
const AddComment = 23;
|
const AddComment = 23;
|
||||||
const DeleteComment = 24;
|
const DeleteComment = 24;
|
||||||
|
const EditComment = 37;
|
||||||
|
|
||||||
const ListTags = 21;
|
const ListTags = 21;
|
||||||
const MergeTags = 27;
|
const MergeTags = 27;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class PostModel extends AbstractCrudModel
|
class PostModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
protected static $config;
|
protected static $config;
|
||||||
@ -48,48 +51,50 @@ class PostModel extends AbstractCrudModel
|
|||||||
'source' => $post->source,
|
'source' => $post->source,
|
||||||
];
|
];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\UpdateStatement();
|
||||||
->update('post')
|
$stmt->setTable('post');
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
|
||||||
->put(array_values($bindings))
|
foreach ($bindings as $key => $value)
|
||||||
->where('id = ?')->put($post->id);
|
$stmt->setColumn($key, new Sql\Binding($value));
|
||||||
Database::query($query);
|
|
||||||
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($post->id)));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
//tags
|
//tags
|
||||||
$tags = $post->getTags();
|
$tags = $post->getTags();
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->where('post_id = ?')->put($post->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id)));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
|
|
||||||
foreach ($tags as $postTag)
|
foreach ($tags as $postTag)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\InsertStatement();
|
||||||
->insertInto('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->surround('post_id, tag_id')
|
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('tag_id', new Sql\Binding($postTag->id));
|
||||||
->put([$post->id, $postTag->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//relations
|
//relations
|
||||||
$relations = $post->getRelations();
|
$relations = $post->getRelations();
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('crossref')
|
$stmt->setTable('crossref');
|
||||||
->where('post_id = ?')->put($post->id)
|
$binding = new Sql\Binding($post->id);
|
||||||
->or('post2_id = ?')->put($post->id);
|
$stmt->setCriterion((new Sql\DisjunctionFunctor)
|
||||||
Database::query($query);
|
->add(new Sql\EqualsFunctor('post_id', $binding))
|
||||||
|
->add(new Sql\EqualsFunctor('post2_id', $binding)));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
foreach ($relations as $relatedPost)
|
foreach ($relations as $relatedPost)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\InsertStatement();
|
||||||
->insertInto('crossref')
|
$stmt->setTable('crossref');
|
||||||
->surround('post_id, post2_id')
|
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('post2_id', new Sql\Binding($relatedPost->id));
|
||||||
->put([$post->id, $relatedPost->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -98,36 +103,31 @@ class PostModel extends AbstractCrudModel
|
|||||||
{
|
{
|
||||||
Database::transaction(function() use ($post)
|
Database::transaction(function() use ($post)
|
||||||
{
|
{
|
||||||
$queries = [];
|
$binding = new Sql\Binding($post->id);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('post_id = ?')->put($post->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', $binding));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('post_tag');
|
||||||
->deleteFrom('post_tag')
|
Database::exec($stmt);
|
||||||
->where('post_id = ?')->put($post->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('favoritee');
|
||||||
->deleteFrom('crossref')
|
Database::exec($stmt);
|
||||||
->where('post_id = ?')->put($post->id)
|
|
||||||
->or('post2_id = ?')->put($post->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('comment');
|
||||||
->deleteFrom('favoritee')
|
Database::exec($stmt);
|
||||||
->where('post_id = ?')->put($post->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('crossref');
|
||||||
->update('comment')
|
$stmt->setCriterion((new Sql\DisjunctionFunctor)
|
||||||
->set('post_id = NULL')
|
->add(new Sql\EqualsFunctor('post_id', $binding))
|
||||||
->where('post_id = ?')->put($post->id);
|
->add(new Sql\EqualsFunctor('post_id', $binding)));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('post');
|
||||||
->deleteFrom('post')
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', $binding));
|
||||||
->where('id = ?')->put($post->id);
|
Database::exec($stmt);
|
||||||
|
|
||||||
foreach ($queries as $query)
|
|
||||||
Database::query($query);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,17 +136,17 @@ class PostModel extends AbstractCrudModel
|
|||||||
|
|
||||||
public static function findByName($key, $throw = true)
|
public static function findByName($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->where('name = ?')->put($key);
|
$stmt->setCriterion(new Sql\EqualsFunctor('name', new Sql\Binding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
if ($throw)
|
if ($throw)
|
||||||
throw new SimpleException('Invalid post name "' . $key . '"');
|
throw new SimpleNotFoundException('Invalid post name "' . $key . '"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,22 +161,63 @@ class PostModel extends AbstractCrudModel
|
|||||||
|
|
||||||
public static function findByHash($key, $throw = true)
|
public static function findByHash($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->where('file_hash = ?')->put($key);
|
$stmt->setCriterion(new Sql\EqualsFunctor('file_hash', new Sql\Binding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
if ($throw)
|
if ($throw)
|
||||||
throw new SimpleException('Invalid post hash "' . $hash . '"');
|
throw new SimpleNotFoundException('Invalid post hash "' . $hash . '"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function preloadComments($posts)
|
||||||
|
{
|
||||||
|
if (empty($posts))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$postMap = [];
|
||||||
|
$tagsMap = [];
|
||||||
|
foreach ($posts as $post)
|
||||||
|
{
|
||||||
|
$postId = $post->id;
|
||||||
|
$postMap[$postId] = $post;
|
||||||
|
$commentMap[$postId] = [];
|
||||||
|
}
|
||||||
|
$postIds = array_unique(array_keys($postMap));
|
||||||
|
|
||||||
|
$stmt = new Sql\SelectStatement();
|
||||||
|
$stmt->setTable('comment');
|
||||||
|
$stmt->addColumn('comment.*');
|
||||||
|
$stmt->addColumn('post_id');
|
||||||
|
$stmt->setCriterion(Sql\InFunctor::fromArray('post_id', Sql\Binding::fromArray($postIds)));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
|
|
||||||
|
foreach ($rows as $row)
|
||||||
|
{
|
||||||
|
if (isset($comments[$row['id']]))
|
||||||
|
continue;
|
||||||
|
unset($row['post_id']);
|
||||||
|
$comment = CommentModel::convertRow($row);
|
||||||
|
$comments[$row['id']] = $comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rows as $row)
|
||||||
|
{
|
||||||
|
$postId = $row['post_id'];
|
||||||
|
$commentMap[$postId] []= $comments[$row['id']];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($commentMap as $postId => $comments)
|
||||||
|
$postMap[$postId]->setCache('comments', $comments);
|
||||||
|
}
|
||||||
|
|
||||||
public static function preloadTags($posts)
|
public static function preloadTags($posts)
|
||||||
{
|
{
|
||||||
if (empty($posts))
|
if (empty($posts))
|
||||||
@ -190,14 +231,15 @@ class PostModel extends AbstractCrudModel
|
|||||||
$postMap[$postId] = $post;
|
$postMap[$postId] = $post;
|
||||||
$tagsMap[$postId] = [];
|
$tagsMap[$postId] = [];
|
||||||
}
|
}
|
||||||
$postIds = array_keys($postMap);
|
$postIds = array_unique(array_keys($postMap));
|
||||||
|
|
||||||
$sqlQuery = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('tag.*, post_id')
|
$stmt->setTable('tag');
|
||||||
->from('tag')
|
$stmt->addColumn('tag.*');
|
||||||
->innerJoin('post_tag')->on('post_tag.tag_id = tag.id')
|
$stmt->addColumn('post_id');
|
||||||
->where('post_id')->in()->genSlots($postIds)->put($postIds);
|
$stmt->addInnerJoin('post_tag', new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id'));
|
||||||
$rows = Database::fetchAll($sqlQuery);
|
$stmt->setCriterion(Sql\InFunctor::fromArray('post_id', Sql\Binding::fromArray($postIds)));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
|
|
||||||
foreach ($rows as $row)
|
foreach ($rows as $row)
|
||||||
{
|
{
|
||||||
@ -215,10 +257,8 @@ class PostModel extends AbstractCrudModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($tagsMap as $postId => $tags)
|
foreach ($tagsMap as $postId => $tags)
|
||||||
{
|
|
||||||
$postMap[$postId]->setCache('tags', $tags);
|
$postMap[$postId]->setCache('tags', $tags);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class PropertyModel implements IModel
|
class PropertyModel implements IModel
|
||||||
{
|
{
|
||||||
const FeaturedPostId = 0;
|
const FeaturedPostId = 0;
|
||||||
@ -20,8 +23,10 @@ class PropertyModel implements IModel
|
|||||||
{
|
{
|
||||||
self::$loaded = true;
|
self::$loaded = true;
|
||||||
self::$allProperties = [];
|
self::$allProperties = [];
|
||||||
$query = (new SqlQuery())->select('*')->from('property');
|
$stmt = new Sql\SelectStatement();
|
||||||
foreach (Database::fetchAll($query) as $row)
|
$stmt ->setColumn('*');
|
||||||
|
$stmt ->setTable('property');
|
||||||
|
foreach (Database::fetchAll($stmt) as $row)
|
||||||
self::$allProperties[$row['prop_id']] = $row['value'];
|
self::$allProperties[$row['prop_id']] = $row['value'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,35 +44,47 @@ class PropertyModel implements IModel
|
|||||||
self::loadIfNecessary();
|
self::loadIfNecessary();
|
||||||
Database::transaction(function() use ($propertyId, $value)
|
Database::transaction(function() use ($propertyId, $value)
|
||||||
{
|
{
|
||||||
$row = Database::query((new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('id')
|
$stmt->setColumn('id');
|
||||||
->from('property')
|
$stmt->setTable('property');
|
||||||
->where('prop_id = ?')
|
$stmt->setCriterion(new Sql\EqualsFunctor('prop_id', new Sql\Binding($propertyId)));
|
||||||
->put($propertyId));
|
$row = Database::fetchOne($stmt);
|
||||||
|
|
||||||
$query = (new SqlQuery);
|
|
||||||
|
|
||||||
if ($row)
|
if ($row)
|
||||||
{
|
{
|
||||||
$query
|
$stmt = new Sql\UpdateStatement();
|
||||||
->update('property')
|
$stmt->setCriterion(new Sql\EqualsFunctor('prop_id', new Sql\Binding($propertyId)));
|
||||||
->set('value = ?')
|
|
||||||
->put($value)
|
|
||||||
->where('prop_id = ?')
|
|
||||||
->put($propertyId);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$query
|
$stmt = new Sql\InsertStatement();
|
||||||
->insertInto('property')
|
$stmt->setColumn('prop_id', new Sql\Binding($propertyId));
|
||||||
->open()->raw('prop_id, value_id')->close()
|
|
||||||
->open()->raw('?, ?')->close()
|
|
||||||
->put([$propertyId, $value]);
|
|
||||||
}
|
}
|
||||||
|
$stmt->setTable('property');
|
||||||
|
$stmt->setColumn('value', new Sql\Binding($value));
|
||||||
|
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
|
|
||||||
self::$allProperties[$propertyId] = $value;
|
self::$allProperties[$propertyId] = $value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function featureNewPost()
|
||||||
|
{
|
||||||
|
$stmt = (new Sql\SelectStatement)
|
||||||
|
->setColumn('id')
|
||||||
|
->setTable('post')
|
||||||
|
->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('type', new Sql\Binding(PostType::Image)))
|
||||||
|
->add(new Sql\EqualsFunctor('safety', new Sql\Binding(PostSafety::Safe))))
|
||||||
|
->setOrderBy(new Sql\RandomFunctor(), Sql\SelectStatement::ORDER_DESC);
|
||||||
|
$featuredPostId = Database::fetchOne($stmt)['id'];
|
||||||
|
if (!$featuredPostId)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
self::set(self::FeaturedPostId, $featuredPostId);
|
||||||
|
self::set(self::FeaturedPostDate, time());
|
||||||
|
self::set(self::FeaturedPostUserName, null);
|
||||||
|
return PostModel::findById($featuredPostId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
101
src/Models/SearchParsers/AbstractSearchParser.php
Normal file
101
src/Models/SearchParsers/AbstractSearchParser.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
|
||||||
|
abstract class AbstractSearchParser
|
||||||
|
{
|
||||||
|
protected $statement;
|
||||||
|
|
||||||
|
public function decorate(Sql\SelectStatement $statement, $filterString)
|
||||||
|
{
|
||||||
|
$this->statement = $statement;
|
||||||
|
|
||||||
|
$tokens = preg_split('/\s+/', $filterString);
|
||||||
|
$tokens = array_filter($tokens);
|
||||||
|
$tokens = array_unique($tokens);
|
||||||
|
$this->processSetup($tokens);
|
||||||
|
|
||||||
|
foreach ($tokens as $token)
|
||||||
|
{
|
||||||
|
$neg = false;
|
||||||
|
if ($token{0} == '-')
|
||||||
|
{
|
||||||
|
$token = substr($token, 1);
|
||||||
|
$neg = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($token, ':') !== false)
|
||||||
|
{
|
||||||
|
list ($key, $value) = explode(':', $token, 2);
|
||||||
|
$key = strtolower($key);
|
||||||
|
|
||||||
|
if ($key == 'order')
|
||||||
|
{
|
||||||
|
$this->internalProcessOrderToken($value, $neg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!$this->processComplexToken($key, $value, $neg))
|
||||||
|
throw new SimpleException('Invalid search token: ' . $key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!$this->processSimpleToken($token, $neg))
|
||||||
|
throw new SimpleException('Invalid search token: ' . $token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->processTeardown();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processSetup(&$tokens)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processTeardown()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function internalProcessOrderToken($orderToken, $neg)
|
||||||
|
{
|
||||||
|
$arr = preg_split('/[;,]/', $orderToken);
|
||||||
|
if (count($arr) == 1)
|
||||||
|
$arr []= 'asc';
|
||||||
|
|
||||||
|
if (count($arr) != 2)
|
||||||
|
throw new SimpleException('Invalid search order token: ' . $orderToken);
|
||||||
|
|
||||||
|
$orderByString = strtolower(array_shift($arr));
|
||||||
|
$orderDirString = strtolower(array_shift($arr));
|
||||||
|
if ($orderDirString == 'asc')
|
||||||
|
$orderDir = Sql\SelectStatement::ORDER_ASC;
|
||||||
|
elseif ($orderDirString == 'desc')
|
||||||
|
$orderDir = Sql\SelectStatement::ORDER_DESC;
|
||||||
|
else
|
||||||
|
throw new SimpleException('Invalid search order direction: ' . $searchOrderDir);
|
||||||
|
|
||||||
|
if ($neg)
|
||||||
|
{
|
||||||
|
$orderDir = $orderDir == Sql\SelectStatement::ORDER_ASC
|
||||||
|
? Sql\SelectStatement::ORDER_DESC
|
||||||
|
: Sql\SelectStatement::ORDER_ASC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->processOrderToken($orderByString, $orderDir))
|
||||||
|
throw new SimpleException('Invalid search order type: ' . $orderByString);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processComplexToken($key, $value, $neg)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processSimpleToken($value, $neg)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processOrderToken($orderToken, $orderDir)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
18
src/Models/SearchParsers/CommentSearchParser.php
Normal file
18
src/Models/SearchParsers/CommentSearchParser.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
|
||||||
|
class CommentSearchParser extends AbstractSearchParser
|
||||||
|
{
|
||||||
|
protected function processSetup(&$tokens)
|
||||||
|
{
|
||||||
|
$this->statement->addInnerJoin('post', new Sql\EqualsFunctor('post_id', 'post.id'));
|
||||||
|
|
||||||
|
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||||
|
$this->statement->setCriterion(new Sql\ConjunctionFunctor());
|
||||||
|
$this->statement->getCriterion()->add(Sql\InFunctor::fromArray('post.safety', Sql\Binding::fromArray($allowedSafety)));
|
||||||
|
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||||
|
$this->statement->getCriterion()->add(new Sql\NegationFunctor(new Sql\StringExpression('hidden')));
|
||||||
|
|
||||||
|
$this->statement->addOrderBy('comment.id', Sql\SelectStatement::ORDER_DESC);
|
||||||
|
}
|
||||||
|
}
|
284
src/Models/SearchParsers/PostSearchParser.php
Normal file
284
src/Models/SearchParsers/PostSearchParser.php
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
|
||||||
|
class PostSearchParser extends AbstractSearchParser
|
||||||
|
{
|
||||||
|
private $tags;
|
||||||
|
private $showHidden = false;
|
||||||
|
private $showDisliked = false;
|
||||||
|
|
||||||
|
protected function processSetup(&$tokens)
|
||||||
|
{
|
||||||
|
$config = \Chibi\Registry::getConfig();
|
||||||
|
|
||||||
|
$this->tags = [];
|
||||||
|
$this->statement->setCriterion(new Sql\ConjunctionFunctor());
|
||||||
|
|
||||||
|
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||||
|
$this->statement->getCriterion()->add(Sql\InFunctor::fromArray('safety', Sql\Binding::fromArray($allowedSafety)));
|
||||||
|
|
||||||
|
if (count($tokens) > $config->browsing->maxSearchTokens)
|
||||||
|
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processTeardown()
|
||||||
|
{
|
||||||
|
if (\Chibi\Registry::getContext()->user->hasEnabledHidingDislikedPosts() and !$this->showDisliked)
|
||||||
|
$this->processComplexToken('special', 'disliked', true);
|
||||||
|
|
||||||
|
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden') or !$this->showHidden)
|
||||||
|
$this->processComplexToken('special', 'hidden', true);
|
||||||
|
|
||||||
|
foreach ($this->tags as $item)
|
||||||
|
{
|
||||||
|
list ($tagName, $neg) = $item;
|
||||||
|
$tag = TagModel::findByName($tagName);
|
||||||
|
$innerStmt = new Sql\SelectStatement();
|
||||||
|
$innerStmt->setTable('post_tag');
|
||||||
|
$innerStmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('post_tag.post_id', 'post.id'))
|
||||||
|
->add(new Sql\EqualsFunctor('post_tag.tag_id', new Sql\Binding($tag->id))));
|
||||||
|
$operator = new Sql\ExistsFunctor($innerStmt);
|
||||||
|
if ($neg)
|
||||||
|
$operator = new Sql\NegationFunctor($operator);
|
||||||
|
$this->statement->getCriterion()->add($operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->statement->addOrderBy('post.id',
|
||||||
|
empty($this->statement->getOrderBy())
|
||||||
|
? Sql\SelectStatement::ORDER_DESC
|
||||||
|
: $this->statement->getOrderBy()[0][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processSimpleToken($value, $neg)
|
||||||
|
{
|
||||||
|
$this->tags []= [$value, $neg];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareCriterionForComplexToken($key, $value)
|
||||||
|
{
|
||||||
|
if (in_array($key, ['id', 'ids']))
|
||||||
|
{
|
||||||
|
$ids = preg_split('/[;,]/', $value);
|
||||||
|
$ids = array_map('intval', $ids);
|
||||||
|
return Sql\InFunctor::fromArray('post.id', Sql\Binding::fromArray($ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($key, ['fav', 'favs']))
|
||||||
|
{
|
||||||
|
$user = UserModel::findByNameOrEmail($value);
|
||||||
|
$innerStmt = (new Sql\SelectStatement)
|
||||||
|
->setTable('favoritee')
|
||||||
|
->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('favoritee.post_id', 'post.id'))
|
||||||
|
->add(new Sql\EqualsFunctor('favoritee.user_id', new Sql\Binding($user->id))));
|
||||||
|
return new Sql\ExistsFunctor($innerStmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($key, ['comment', 'commenter']))
|
||||||
|
{
|
||||||
|
$user = UserModel::findByNameOrEmail($value);
|
||||||
|
$innerStmt = (new Sql\SelectStatement)
|
||||||
|
->setTable('comment')
|
||||||
|
->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('comment.post_id', 'post.id'))
|
||||||
|
->add(new Sql\EqualsFunctor('comment.commenter_id', new Sql\Binding($user->id))));
|
||||||
|
return new Sql\ExistsFunctor($innerStmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($key, ['submit', 'upload', 'uploader', 'uploaded']))
|
||||||
|
{
|
||||||
|
$user = UserModel::findByNameOrEmail($value);
|
||||||
|
return new Sql\EqualsFunctor('uploader_id', new Sql\Binding($user->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($key, ['idmin', 'id_min']))
|
||||||
|
return new Sql\EqualsOrGreaterFunctor('post.id', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['idmax', 'id_max']))
|
||||||
|
return new Sql\EqualsOrLesserFunctor('post.id', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['scoremin', 'score_min']))
|
||||||
|
return new Sql\EqualsOrGreaterFunctor('score', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['scoremax', 'score_max']))
|
||||||
|
return new Sql\EqualsOrLesserFunctor('score', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['tagmin', 'tag_min']))
|
||||||
|
return new Sql\EqualsOrGreaterFunctor('tag_count', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['tagmax', 'tag_max']))
|
||||||
|
return new Sql\EqualsOrLesserFunctor('tag_count', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['favmin', 'fav_min']))
|
||||||
|
return new Sql\EqualsOrGreaterFunctor('fav_count', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['favmax', 'fav_max']))
|
||||||
|
return new Sql\EqualsOrLesserFunctor('fav_count', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['commentmin', 'comment_min']))
|
||||||
|
return new Sql\EqualsOrGreaterFunctor('comment_count', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['commentmax', 'comment_max']))
|
||||||
|
return new Sql\EqualsOrLesserFunctor('comment_count', new Sql\Binding(intval($value)));
|
||||||
|
|
||||||
|
elseif (in_array($key, ['date']))
|
||||||
|
{
|
||||||
|
list ($dateMin, $dateMax) = self::parseDate($value);
|
||||||
|
return (new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsOrLesserFunctor('upload_date', new Sql\Binding($dateMax)))
|
||||||
|
->add(new Sql\EqualsOrGreaterFunctor('upload_date', new Sql\Binding($dateMin)));
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($key, ['datemin', 'date_min']))
|
||||||
|
{
|
||||||
|
list ($dateMin, $dateMax) = self::parseDate($value);
|
||||||
|
return new Sql\EqualsOrGreaterFunctor('upload_date', new Sql\Binding($dateMin));
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($key, ['datemax', 'date_max']))
|
||||||
|
{
|
||||||
|
list ($dateMin, $dateMax) = self::parseDate($value);
|
||||||
|
return new Sql\EqualsOrLesserFunctor('upload_date', new Sql\Binding($dateMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif ($key == 'special')
|
||||||
|
{
|
||||||
|
$context = \Chibi\Registry::getContext();
|
||||||
|
$value = strtolower($value);
|
||||||
|
if (in_array($value, ['fav', 'favs', 'favd', 'favorite', 'favorites']))
|
||||||
|
{
|
||||||
|
return $this->prepareCriterionForComplexToken('fav', $context->user->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($value, ['like', 'liked', 'likes']))
|
||||||
|
{
|
||||||
|
if (!$this->statement->isTableJoined('post_score'))
|
||||||
|
{
|
||||||
|
$this->statement->addLeftOuterJoin('post_score', (new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('post_score.post_id', 'post.id'))
|
||||||
|
->add(new Sql\EqualsFunctor('post_score.user_id', new Sql\Binding($context->user->id))));
|
||||||
|
}
|
||||||
|
return new Sql\EqualsFunctor(new Sql\IfNullFunctor('post_score.score', '0'), '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (in_array($value, ['dislike', 'disliked', 'dislikes']))
|
||||||
|
{
|
||||||
|
$this->showDisliked = true;
|
||||||
|
if (!$this->statement->isTableJoined('post_score'))
|
||||||
|
{
|
||||||
|
$this->statement->addLeftOuterJoin('post_score', (new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('post_score.post_id', 'post.id'))
|
||||||
|
->add(new Sql\EqualsFunctor('post_score.user_id', new Sql\Binding($context->user->id))));
|
||||||
|
}
|
||||||
|
return new Sql\EqualsFunctor(new Sql\IfNullFunctor('post_score.score', '0'), '-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif ($value == 'hidden')
|
||||||
|
{
|
||||||
|
$this->showHidden = true;
|
||||||
|
return new Sql\StringExpression('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
throw new SimpleException('Invalid special token: ' . $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif ($key == 'type')
|
||||||
|
{
|
||||||
|
$value = strtolower($value);
|
||||||
|
if ($value == 'swf')
|
||||||
|
$type = PostType::Flash;
|
||||||
|
elseif ($value == 'img')
|
||||||
|
$type = PostType::Image;
|
||||||
|
elseif ($value == 'yt' or $value == 'youtube')
|
||||||
|
$type = PostType::Youtube;
|
||||||
|
else
|
||||||
|
throw new SimpleException('Invalid post type: ' . $value);
|
||||||
|
|
||||||
|
return new Sql\EqualsFunctor('type', new Sql\Binding($type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processComplexToken($key, $value, $neg)
|
||||||
|
{
|
||||||
|
$criterion = $this->prepareCriterionForComplexToken($key, $value);
|
||||||
|
if (!$criterion)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ($neg)
|
||||||
|
$criterion = new Sql\NegationFunctor($criterion);
|
||||||
|
|
||||||
|
$this->statement->getCriterion()->add($criterion);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processOrderToken($orderByString, $orderDir)
|
||||||
|
{
|
||||||
|
$randomReset = true;
|
||||||
|
|
||||||
|
if (in_array($orderByString, ['id']))
|
||||||
|
$orderColumn = 'post.id';
|
||||||
|
|
||||||
|
elseif (in_array($orderByString, ['date']))
|
||||||
|
$orderColumn = 'upload_date';
|
||||||
|
|
||||||
|
elseif (in_array($orderByString, ['comment', 'comments', 'commentcount', 'comment_count']))
|
||||||
|
$orderColumn = 'comment_count';
|
||||||
|
|
||||||
|
elseif (in_array($orderByString, ['fav', 'favs', 'favcount', 'fav_count']))
|
||||||
|
$orderColumn = 'fav_count';
|
||||||
|
|
||||||
|
elseif (in_array($orderByString, ['score']))
|
||||||
|
$orderColumn = 'score';
|
||||||
|
|
||||||
|
elseif (in_array($orderByString, ['tag', 'tags', 'tagcount', 'tag_count']))
|
||||||
|
$orderColumn = 'tag_count';
|
||||||
|
|
||||||
|
elseif (in_array($orderByString, ['commentdate', 'comment_date']))
|
||||||
|
$orderColumn = 'comment_date';
|
||||||
|
|
||||||
|
elseif ($orderByString == '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 = new Sql\SubstrFunctor(
|
||||||
|
new Sql\MultiplicationFunctor('post.id', $seed),
|
||||||
|
new Sql\AdditionFunctor(new Sql\LengthFunctor('post.id'), '2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ($randomReset and isset($_SESSION['browsing-seed']))
|
||||||
|
unset($_SESSION['browsing-seed']);
|
||||||
|
|
||||||
|
$this->statement->setOrderBy($orderColumn, $orderDir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function parseDate($value)
|
||||||
|
{
|
||||||
|
list ($year, $month, $day) = explode('-', $value . '-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];
|
||||||
|
}
|
||||||
|
}
|
39
src/Models/SearchParsers/TagSearchParser.php
Normal file
39
src/Models/SearchParsers/TagSearchParser.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
|
||||||
|
class TagSearchParser extends AbstractSearchParser
|
||||||
|
{
|
||||||
|
protected function processSetup(&$tokens)
|
||||||
|
{
|
||||||
|
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||||
|
$this->statement
|
||||||
|
->addInnerJoin('post_tag', new Sql\EqualsFunctor('tag.id', 'post_tag.tag_id'))
|
||||||
|
->addInnerJoin('post', new Sql\EqualsFunctor('post.id', 'post_tag.post_id'))
|
||||||
|
->setCriterion((new Sql\ConjunctionFunctor)->add(Sql\InFunctor::fromArray('safety', Sql\Binding::fromArray($allowedSafety))))
|
||||||
|
->setGroupBy('tag.id');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processSimpleToken($value, $neg)
|
||||||
|
{
|
||||||
|
if ($neg)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (strlen($value) >= 3)
|
||||||
|
$value = '%' . $value;
|
||||||
|
$value .= '%';
|
||||||
|
|
||||||
|
$this->statement->getCriterion()->add(new Sql\NoCaseFunctor(new Sql\LikeFunctor('tag.name', new Sql\Binding($value))));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processOrderToken($orderByString, $orderDir)
|
||||||
|
{
|
||||||
|
if ($orderByString == 'popularity')
|
||||||
|
$this->statement->setOrderBy('post_count', $orderDir);
|
||||||
|
elseif ($orderByString == 'alpha')
|
||||||
|
$this->statement->setOrderBy('tag.name', $orderDir);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
32
src/Models/SearchParsers/UserSearchParser.php
Normal file
32
src/Models/SearchParsers/UserSearchParser.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
|
||||||
|
class UserSearchParser extends AbstractSearchParser
|
||||||
|
{
|
||||||
|
protected function processSimpleToken($value, $neg)
|
||||||
|
{
|
||||||
|
if ($neg)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ($value == 'pending')
|
||||||
|
{
|
||||||
|
$this->statement->setCriterion((new Sql\DisjunctionFunctor)
|
||||||
|
->add(new Sql\IsFunctor('staff_confirmed', new Sql\NullFunctor()))
|
||||||
|
->add(new Sql\EqualsFunctor('staff_confirmed', '0')));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processOrderToken($orderByString, $orderDir)
|
||||||
|
{
|
||||||
|
if ($orderByString == 'alpha')
|
||||||
|
$this->statement->setOrderBy(new Sql\NoCaseFunctor('name'), $orderDir);
|
||||||
|
elseif ($orderByString == 'date')
|
||||||
|
$this->statement->setOrderBy('join_date', $orderDir);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
abstract class AbstractSearchService
|
|
||||||
{
|
|
||||||
protected static function getModelClassName()
|
|
||||||
{
|
|
||||||
$searchServiceClassName = get_called_class();
|
|
||||||
$modelClassName = str_replace('SearchService', 'Model', $searchServiceClassName);
|
|
||||||
return $modelClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function decoratePager(SqlQuery $sqlQuery, $perPage, $page)
|
|
||||||
{
|
|
||||||
if ($perPage === null)
|
|
||||||
return;
|
|
||||||
$sqlQuery->limit('?')->put($perPage);
|
|
||||||
$sqlQuery->offset('?')->put(($page - 1) * $perPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
static function getEntitiesRows($searchQuery, $perPage = null, $page = 1)
|
|
||||||
{
|
|
||||||
$modelClassName = self::getModelClassName();
|
|
||||||
$table = $modelClassName::getTableName();
|
|
||||||
|
|
||||||
$sqlQuery = new SqlQuery();
|
|
||||||
$sqlQuery->select($table . '.*');
|
|
||||||
static::decorate($sqlQuery, $searchQuery);
|
|
||||||
self::decoratePager($sqlQuery, $perPage, $page);
|
|
||||||
|
|
||||||
$rows = Database::fetchAll($sqlQuery);
|
|
||||||
return $rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
static function getEntities($searchQuery, $perPage = null, $page = 1)
|
|
||||||
{
|
|
||||||
$modelClassName = self::getModelClassName();
|
|
||||||
$rows = static::getEntitiesRows($searchQuery, $perPage, $page);
|
|
||||||
return $modelClassName::convertRows($rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
static function getEntityCount($searchQuery)
|
|
||||||
{
|
|
||||||
$modelClassName = self::getModelClassName();
|
|
||||||
$table = $modelClassName::getTableName();
|
|
||||||
|
|
||||||
$sqlQuery = new SqlQuery();
|
|
||||||
$sqlQuery->select('count(1)')->as('count');
|
|
||||||
static::decorate($sqlQuery, $searchQuery);
|
|
||||||
|
|
||||||
return Database::fetchOne($sqlQuery)['count'];
|
|
||||||
}
|
|
||||||
}
|
|
79
src/Models/SearchServices/AbstractSearchService.php
Normal file
79
src/Models/SearchServices/AbstractSearchService.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
|
abstract class AbstractSearchService
|
||||||
|
{
|
||||||
|
protected static function getModelClassName()
|
||||||
|
{
|
||||||
|
$searchServiceClassName = get_called_class();
|
||||||
|
$modelClassName = str_replace('SearchService', 'Model', $searchServiceClassName);
|
||||||
|
return $modelClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function getParserClassName()
|
||||||
|
{
|
||||||
|
$searchServiceClassName = get_called_class();
|
||||||
|
$parserClassName = str_replace('SearchService', 'SearchParser', $searchServiceClassName);
|
||||||
|
return $parserClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function decorateParser(Sql\SelectStatement $stmt, $searchQuery)
|
||||||
|
{
|
||||||
|
$parserClassName = self::getParserClassName();
|
||||||
|
(new $parserClassName)->decorate($stmt, $searchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function decorateCustom(Sql\SelectStatement $stmt)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function decoratePager(Sql\SelectStatement $stmt, $perPage, $page)
|
||||||
|
{
|
||||||
|
if ($perPage === null)
|
||||||
|
return;
|
||||||
|
$stmt->setLimit(
|
||||||
|
new Sql\Binding($perPage),
|
||||||
|
new Sql\Binding(($page - 1) * $perPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getEntitiesRows($searchQuery, $perPage = null, $page = 1)
|
||||||
|
{
|
||||||
|
$modelClassName = self::getModelClassName();
|
||||||
|
$table = $modelClassName::getTableName();
|
||||||
|
|
||||||
|
$stmt = new Sql\SelectStatement();
|
||||||
|
$stmt->setColumn($table . '.*');
|
||||||
|
$stmt->setTable($table);
|
||||||
|
static::decorateParser($stmt, $searchQuery);
|
||||||
|
static::decorateCustom($stmt);
|
||||||
|
static::decoratePager($stmt, $perPage, $page);
|
||||||
|
|
||||||
|
return Database::fetchAll($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getEntities($searchQuery, $perPage = null, $page = 1)
|
||||||
|
{
|
||||||
|
$modelClassName = self::getModelClassName();
|
||||||
|
$rows = static::getEntitiesRows($searchQuery, $perPage, $page);
|
||||||
|
return $modelClassName::convertRows($rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getEntityCount($searchQuery)
|
||||||
|
{
|
||||||
|
$modelClassName = self::getModelClassName();
|
||||||
|
$table = $modelClassName::getTableName();
|
||||||
|
|
||||||
|
$innerStmt = new Sql\SelectStatement();
|
||||||
|
$innerStmt->setTable($table);
|
||||||
|
static::decorateParser($innerStmt, $searchQuery);
|
||||||
|
static::decorateCustom($innerStmt);
|
||||||
|
$innerStmt->resetOrderBy();
|
||||||
|
|
||||||
|
$stmt = new Sql\SelectStatement();
|
||||||
|
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||||
|
$stmt->setSource($innerStmt);
|
||||||
|
|
||||||
|
return Database::fetchOne($stmt)['count'];
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
class CommentSearchService extends AbstractSearchService
|
class CommentSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
|
||||||
{
|
|
||||||
$sqlQuery
|
|
||||||
->from('comment')
|
|
||||||
->where('post_id')
|
|
||||||
->is()->not('NULL')
|
|
||||||
->orderBy('id')
|
|
||||||
->desc();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,487 +1,50 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class PostSearchService extends AbstractSearchService
|
class PostSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
private static $enableTokenLimit = true;
|
public static function getPostIdsAround($searchQuery, $postId)
|
||||||
|
|
||||||
public static function enableTokenLimit($enable)
|
|
||||||
{
|
{
|
||||||
self::$enableTokenLimit = $enable;
|
return Database::transaction(function() use ($searchQuery, $postId)
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterUserSafety(SqlQuery $sqlQuery)
|
|
||||||
{
|
{
|
||||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
$stmt = new Sql\RawStatement('CREATE TEMPORARY TABLE IF NOT EXISTS post_search(id INTEGER PRIMARY KEY, post_id INTEGER)');
|
||||||
$sqlQuery->raw('safety')->in()->genSlots($allowedSafety);
|
Database::exec($stmt);
|
||||||
foreach ($allowedSafety as $s)
|
|
||||||
$sqlQuery->put($s);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterUserHidden(SqlQuery $sqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
{
|
$stmt->setTable('post_search');
|
||||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
Database::exec($stmt);
|
||||||
$sqlQuery->not('hidden');
|
|
||||||
else
|
|
||||||
$sqlQuery->raw('1');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterChain(SqlQuery $sqlQuery)
|
$innerStmt = new Sql\SelectStatement($searchQuery);
|
||||||
{
|
$innerStmt->setColumn('post.id');
|
||||||
if (isset($sqlQuery->__chained))
|
$innerStmt->setTable('post');
|
||||||
$sqlQuery->and();
|
self::decorateParser($innerStmt, $searchQuery);
|
||||||
else
|
$stmt = new Sql\InsertStatement();
|
||||||
$sqlQuery->where();
|
$stmt->setTable('post_search');
|
||||||
$sqlQuery->__chained = true;
|
$stmt->setSource(['post_id'], $innerStmt);
|
||||||
}
|
Database::exec($stmt);
|
||||||
|
|
||||||
protected static function filterNegate(SqlQuery $sqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
{
|
$stmt->setTable('post_search');
|
||||||
$sqlQuery->not();
|
$stmt->setColumn('id');
|
||||||
}
|
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', new Sql\Binding($postId)));
|
||||||
|
$rowId = Database::fetchOne($stmt)['id'];
|
||||||
|
|
||||||
protected static function filterTag($sqlQuery, $val)
|
//it's possible that given post won't show in search results:
|
||||||
{
|
//it can be hidden, it can have prohibited safety etc.
|
||||||
$tag = TagModel::findByName($val);
|
if (!$rowId)
|
||||||
$sqlQuery
|
return [null, null];
|
||||||
->exists()
|
|
||||||
->open()
|
|
||||||
->select('1')
|
|
||||||
->from('post_tag')
|
|
||||||
->where('post_id = post.id')
|
|
||||||
->and('post_tag.tag_id = ?')->put($tag->id)
|
|
||||||
->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenId($searchContext, SqlQuery $sqlQuery, $val)
|
$rowId = intval($rowId);
|
||||||
{
|
$stmt->setColumn('post_id');
|
||||||
$ids = preg_split('/[;,]/', $val);
|
|
||||||
$ids = array_map('intval', $ids);
|
|
||||||
$sqlQuery->raw('id')->in()->genSlots($ids)->put($ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenIdMin($searchContext, SqlQuery $sqlQuery, $val)
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($rowId - 1)));
|
||||||
{
|
$nextPostId = Database::fetchOne($stmt)['post_id'];
|
||||||
$sqlQuery->raw('id >= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenIdMax($searchContext, SqlQuery $sqlQuery, $val)
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($rowId + 1)));
|
||||||
{
|
$prevPostId = Database::fetchOne($stmt)['post_id'];
|
||||||
$sqlQuery->raw('id <= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenScoreMin($searchContext, SqlQuery $sqlQuery, $val)
|
return [$prevPostId, $nextPostId];
|
||||||
{
|
|
||||||
$sqlQuery->raw('score >= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenScoreMax($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$sqlQuery->raw('score <= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenTagMin($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$sqlQuery->raw('tag_count >= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenTagMax($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$sqlQuery->raw('tag_count <= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenFavMin($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$sqlQuery->raw('fav_count >= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenFavMax($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$sqlQuery->raw('fav_count <= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenCommentMin($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$sqlQuery->raw('comment_count >= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenCommentMax($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$sqlQuery->raw('comment_count <= ?')->put(intval($val));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenSpecial($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$context = \Chibi\Registry::getContext();
|
|
||||||
|
|
||||||
switch (strtolower($val))
|
|
||||||
{
|
|
||||||
case 'liked':
|
|
||||||
case 'likes':
|
|
||||||
$sqlQuery
|
|
||||||
->exists()
|
|
||||||
->open()
|
|
||||||
->select('1')
|
|
||||||
->from('post_score')
|
|
||||||
->where('post_id = post.id')
|
|
||||||
->and('score > 0')
|
|
||||||
->and('user_id = ?')->put($context->user->id)
|
|
||||||
->close();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'disliked':
|
|
||||||
case 'dislikes':
|
|
||||||
$sqlQuery
|
|
||||||
->exists()
|
|
||||||
->open()
|
|
||||||
->select('1')
|
|
||||||
->from('post_score')
|
|
||||||
->where('post_id = post.id')
|
|
||||||
->and('score < 0')
|
|
||||||
->and('user_id = ?')->put($context->user->id)
|
|
||||||
->close();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new SimpleException('Unknown special "' . $val . '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenType($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
switch (strtolower($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 . '"');
|
|
||||||
}
|
|
||||||
$sqlQuery->raw('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($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
|
||||||
$sqlQuery
|
|
||||||
->raw('upload_date >= ?')->put($timeMin)
|
|
||||||
->and('upload_date <= ?')->put($timeMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenDateMin($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
|
||||||
$sqlQuery->raw('upload_date >= ?')->put($timeMin);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenDateMax($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
|
||||||
$sqlQuery->raw('upload_date <= ?')->put($timeMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenFav($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$user = UserModel::findByNameOrEmail($val);
|
|
||||||
$sqlQuery
|
|
||||||
->exists()
|
|
||||||
->open()
|
|
||||||
->select('1')
|
|
||||||
->from('favoritee')
|
|
||||||
->where('post_id = post.id')
|
|
||||||
->and('favoritee.user_id = ?')->put($user->id)
|
|
||||||
->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenFavs($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
return self::filterTokenFav($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenComment($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$user = UserModel::findByNameOrEmail($val);
|
|
||||||
$sqlQuery
|
|
||||||
->exists()
|
|
||||||
->open()
|
|
||||||
->select('1')
|
|
||||||
->from('comment')
|
|
||||||
->where('post_id = post.id')
|
|
||||||
->and('commenter_id = ?')->put($user->id)
|
|
||||||
->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenCommenter($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
return self::filterTokenComment($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenSubmit($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$user = UserModel::findByNameOrEmail($val);
|
|
||||||
$sqlQuery->raw('uploader_id = ?')->put($user->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenUploader($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenUpload($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenUploaded($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenPrev($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function filterTokenNext($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$searchContext->orderDir *= -1;
|
|
||||||
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function __filterTokenPrevNext($searchContext, SqlQuery $sqlQuery, $val)
|
|
||||||
{
|
|
||||||
$op1 = $searchContext->orderDir == 1 ? '<' : '>';
|
|
||||||
$op2 = $searchContext->orderDir != 1 ? '<' : '>';
|
|
||||||
$sqlQuery
|
|
||||||
->open()
|
|
||||||
->open()
|
|
||||||
->raw($searchContext->orderColumn . ' ' . $op1 . ' ')
|
|
||||||
->open()
|
|
||||||
->select($searchContext->orderColumn)
|
|
||||||
->from('post p2')
|
|
||||||
->where('p2.id = ?')->put(intval($val))
|
|
||||||
->close()
|
|
||||||
->and('id != ?')->put($val)
|
|
||||||
->close()
|
|
||||||
->or()
|
|
||||||
->open()
|
|
||||||
->raw($searchContext->orderColumn . ' = ')
|
|
||||||
->open()
|
|
||||||
->select($searchContext->orderColumn)
|
|
||||||
->from('post p2')
|
|
||||||
->where('p2.id = ?')->put(intval($val))
|
|
||||||
->close()
|
|
||||||
->and('id ' . $op1 . ' ?')->put(intval($val))
|
|
||||||
->close()
|
|
||||||
->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected static function parseOrderToken($searchContext, $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 = 'id';
|
|
||||||
break;
|
|
||||||
case 'date':
|
|
||||||
$orderColumn = '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':
|
|
||||||
$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']);
|
|
||||||
|
|
||||||
$searchContext->orderColumn = $orderColumn;
|
|
||||||
$searchContext->orderDir = $orderDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected static function iterateTokens($tokens, $callback)
|
|
||||||
{
|
|
||||||
$unparsedTokens = [];
|
|
||||||
|
|
||||||
foreach ($tokens as $origToken)
|
|
||||||
{
|
|
||||||
$token = $origToken;
|
|
||||||
$neg = false;
|
|
||||||
if ($token{0} == '-')
|
|
||||||
{
|
|
||||||
$token = substr($token, 1);
|
|
||||||
$neg = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$pos = strpos($token, ':');
|
|
||||||
if ($pos === false)
|
|
||||||
{
|
|
||||||
$key = null;
|
|
||||||
$val = $token;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$key = strtolower(substr($token, 0, $pos));
|
|
||||||
$val = substr($token, $pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$parsed = $callback($neg, $key, $val);
|
|
||||||
|
|
||||||
if (!$parsed)
|
|
||||||
$unparsedTokens []= $origToken;
|
|
||||||
}
|
|
||||||
return $unparsedTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
|
||||||
{
|
|
||||||
$config = \Chibi\Registry::getConfig();
|
|
||||||
|
|
||||||
$sqlQuery->from('post');
|
|
||||||
|
|
||||||
self::filterChain($sqlQuery);
|
|
||||||
self::filterUserSafety($sqlQuery);
|
|
||||||
self::filterChain($sqlQuery);
|
|
||||||
self::filterUserHidden($sqlQuery);
|
|
||||||
|
|
||||||
/* query tokens */
|
|
||||||
$tokens = array_filter(array_unique(explode(' ', $searchQuery)), function($x) { return $x != ''; });
|
|
||||||
if (self::$enableTokenLimit and count($tokens) > $config->browsing->maxSearchTokens)
|
|
||||||
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
|
||||||
|
|
||||||
if (\Chibi\Registry::getContext()->user->hasEnabledHidingDislikedPosts())
|
|
||||||
$tokens []= '-special:disliked';
|
|
||||||
|
|
||||||
$searchContext = new StdClass;
|
|
||||||
$searchContext->orderColumn = 'id';
|
|
||||||
$searchContext->orderDir = 1;
|
|
||||||
|
|
||||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery, &$orderToken)
|
|
||||||
{
|
|
||||||
if ($key != 'order')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if ($neg)
|
|
||||||
$orderToken = '-' . $val;
|
|
||||||
else
|
|
||||||
$orderToken = $val;
|
|
||||||
self::parseOrderToken($searchContext, $orderToken);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
|
|
||||||
{
|
|
||||||
if ($key !== null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
self::filterChain($sqlQuery);
|
|
||||||
if ($neg)
|
|
||||||
self::filterNegate($sqlQuery);
|
|
||||||
self::filterTag($sqlQuery, $val);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
|
|
||||||
{
|
|
||||||
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
|
||||||
if (!method_exists(__CLASS__, $methodName))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
self::filterChain($sqlQuery);
|
|
||||||
if ($neg)
|
|
||||||
self::filterNegate($sqlQuery);
|
|
||||||
self::$methodName($searchContext, $sqlQuery, $val);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!empty($tokens))
|
|
||||||
throw new SimpleException('Unknown search token "' . array_shift($tokens) . '"');
|
|
||||||
|
|
||||||
$sqlQuery->orderBy($searchContext->orderColumn);
|
|
||||||
if ($searchContext->orderDir == 1)
|
|
||||||
$sqlQuery->desc();
|
|
||||||
else
|
|
||||||
$sqlQuery->asc();
|
|
||||||
|
|
||||||
if ($searchContext->orderColumn != 'id')
|
|
||||||
{
|
|
||||||
$sqlQuery->raw(', id');
|
|
||||||
if ($searchContext->orderDir == 1)
|
|
||||||
$sqlQuery->desc();
|
|
||||||
else
|
|
||||||
$sqlQuery->asc();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,87 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class TagSearchService extends AbstractSearchService
|
class TagSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
public static function decorateCustom(Sql\SelectStatement $stmt)
|
||||||
{
|
{
|
||||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
$stmt->addColumn(new Sql\AliasFunctor(new Sql\CountFunctor('post_tag.post_id'), 'post_count'));
|
||||||
$limitQuery = false;
|
|
||||||
$sqlQuery
|
|
||||||
->raw(', 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()->genSlots($allowedSafety);
|
|
||||||
foreach ($allowedSafety as $s)
|
|
||||||
$sqlQuery->put($s);
|
|
||||||
|
|
||||||
$orderToken = null;
|
|
||||||
|
|
||||||
if ($searchQuery !== null)
|
|
||||||
{
|
|
||||||
$tokens = preg_split('/\s+/', $searchQuery);
|
|
||||||
foreach ($tokens as $token)
|
|
||||||
{
|
|
||||||
if (strpos($token, ':') !== false)
|
|
||||||
{
|
|
||||||
list ($key, $value) = explode(':', $token);
|
|
||||||
|
|
||||||
if ($key == 'order')
|
|
||||||
$orderToken = $value;
|
|
||||||
else
|
|
||||||
throw new SimpleException('Unknown key: ' . $key);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$limitQuery = true;
|
|
||||||
if (strlen($token) >= 3)
|
|
||||||
$token = '%' . $token;
|
|
||||||
$token .= '%';
|
|
||||||
$sqlQuery
|
|
||||||
->and('LOWER(tag.name)')
|
|
||||||
->like('LOWER(?)')
|
|
||||||
->put($token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$sqlQuery->groupBy('tag.id');
|
public static function getMostUsedTag()
|
||||||
if ($orderToken)
|
|
||||||
self::order($sqlQuery,$orderToken);
|
|
||||||
|
|
||||||
|
|
||||||
if ($limitQuery)
|
|
||||||
$sqlQuery->limit(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function order(SqlQuery $sqlQuery, $value)
|
|
||||||
{
|
{
|
||||||
if (strpos($value, ',') !== false)
|
$stmt = new Sql\SelectStatement();
|
||||||
{
|
$stmt->setTable('post_tag');
|
||||||
list ($orderColumn, $orderDir) = explode(',', $value);
|
$stmt->addColumn('tag_id');
|
||||||
}
|
$stmt->addColumn(new Sql\AliasFunctor(new Sql\CountFunctor('post_tag.post_id'), 'post_count'));
|
||||||
else
|
$stmt->setGroupBy('post_tag.tag_id');
|
||||||
{
|
$stmt->setOrderBy('post_count', Sql\SelectStatement::ORDER_DESC);
|
||||||
$orderColumn = $value;
|
$stmt->setLimit(1, 0);
|
||||||
$orderDir = 'asc';
|
return Database::fetchOne($stmt);
|
||||||
}
|
|
||||||
|
|
||||||
switch ($orderColumn)
|
|
||||||
{
|
|
||||||
case 'popularity':
|
|
||||||
$sqlQuery->orderBy('post_count');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'alpha':
|
|
||||||
$sqlQuery->orderBy('name');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($orderDir == 'asc')
|
|
||||||
$sqlQuery->asc();
|
|
||||||
else
|
|
||||||
$sqlQuery->desc();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
class UserSearchService extends AbstractSearchService
|
class UserSearchService extends AbstractSearchService
|
||||||
{
|
{
|
||||||
protected static function decorate(SQLQuery $sqlQuery, $searchQuery)
|
|
||||||
{
|
|
||||||
$sqlQuery->from('user');
|
|
||||||
|
|
||||||
$sortStyle = $searchQuery;
|
|
||||||
switch ($sortStyle)
|
|
||||||
{
|
|
||||||
case 'alpha,asc':
|
|
||||||
$sqlQuery->orderBy('name')->asc();
|
|
||||||
break;
|
|
||||||
case 'alpha,desc':
|
|
||||||
$sqlQuery->orderBy('name')->desc();
|
|
||||||
break;
|
|
||||||
case 'date,asc':
|
|
||||||
$sqlQuery->orderBy('join_date')->asc();
|
|
||||||
break;
|
|
||||||
case 'date,desc':
|
|
||||||
$sqlQuery->orderBy('join_date')->desc();
|
|
||||||
break;
|
|
||||||
case 'pending':
|
|
||||||
$sqlQuery->where('staff_confirmed IS NULL');
|
|
||||||
$sqlQuery->or('staff_confirmed = 0');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new SimpleException('Unknown sort style "' . $sortStyle . '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class TagModel extends AbstractCrudModel
|
class TagModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
public static function getTableName()
|
public static function getTableName()
|
||||||
@ -12,27 +15,29 @@ class TagModel extends AbstractCrudModel
|
|||||||
{
|
{
|
||||||
self::forgeId($tag, 'tag');
|
self::forgeId($tag, 'tag');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\UpdateStatement();
|
||||||
->update('tag')
|
$stmt->setTable('tag');
|
||||||
->set('name = ?')->put($tag->name)
|
$stmt->setColumn('name', new Sql\Binding($tag->name));
|
||||||
->where('id = ?')->put($tag->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($tag->id)));
|
||||||
|
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
return $tag->id;
|
return $tag->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function remove($tag)
|
public static function remove($tag)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$binding = new Sql\Binding($tag->id);
|
||||||
->deleteFrom('post_tag')
|
|
||||||
->where('tag_id = ?')->put($tag->id);
|
|
||||||
Database::query($query);
|
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('tag')
|
$stmt->setTable('post_tag');
|
||||||
->where('id = ?')->put($tag->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('tag_id', $binding));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
|
|
||||||
|
$stmt = new Sql\DeleteStatement();
|
||||||
|
$stmt->setTable('tag');
|
||||||
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', $binding));
|
||||||
|
Database::exec($stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function rename($sourceName, $targetName)
|
public static function rename($sourceName, $targetName)
|
||||||
@ -60,38 +65,40 @@ class TagModel extends AbstractCrudModel
|
|||||||
if ($sourceTag->id == $targetTag->id)
|
if ($sourceTag->id == $targetTag->id)
|
||||||
throw new SimpleException('Source and target tag are the same');
|
throw new SimpleException('Source and target tag are the same');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('post.id')
|
$stmt->setColumn('post.id');
|
||||||
->from('post')
|
$stmt->setTable('post');
|
||||||
->where()
|
$stmt->setCriterion(
|
||||||
->exists()
|
(new Sql\ConjunctionFunctor)
|
||||||
->open()
|
->add(
|
||||||
->select('1')
|
new Sql\ExistsFunctor(
|
||||||
->from('post_tag')
|
(new Sql\SelectStatement)
|
||||||
->where('post_tag.post_id = post.id')
|
->setTable('post_tag')
|
||||||
->and('post_tag.tag_id = ?')->put($sourceTag->id)
|
->setCriterion(
|
||||||
->close()
|
(new Sql\ConjunctionFunctor)
|
||||||
->and()
|
->add(new Sql\EqualsFunctor('post_tag.post_id', 'post.id'))
|
||||||
->not()->exists()
|
->add(new Sql\EqualsFunctor('post_tag.tag_id', new Sql\Binding($sourceTag->id))))))
|
||||||
->open()
|
->add(
|
||||||
->select('1')
|
new Sql\NegationFunctor(
|
||||||
->from('post_tag')
|
new Sql\ExistsFunctor(
|
||||||
->where('post_tag.post_id = post.id')
|
(new Sql\SelectStatement)
|
||||||
->and('post_tag.tag_id = ?')->put($targetTag->id)
|
->setTable('post_tag')
|
||||||
->close();
|
->setCriterion(
|
||||||
$rows = Database::fetchAll($query);
|
(new Sql\ConjunctionFunctor)
|
||||||
|
->add(new Sql\EqualsFunctor('post_tag.post_id', 'post.id'))
|
||||||
|
->add(new Sql\EqualsFunctor('post_tag.tag_id', new Sql\Binding($targetTag->id))))))));
|
||||||
|
$rows = Database::fetchAll($stmt);
|
||||||
$postIds = array_map(function($row) { return $row['id']; }, $rows);
|
$postIds = array_map(function($row) { return $row['id']; }, $rows);
|
||||||
|
|
||||||
self::remove($sourceTag);
|
self::remove($sourceTag);
|
||||||
|
|
||||||
foreach ($postIds as $postId)
|
foreach ($postIds as $postId)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\InsertStatement();
|
||||||
->insertInto('post_tag')
|
$stmt->setTable('post_tag');
|
||||||
->surround('post_id, tag_id')
|
$stmt->setColumn('post_id', new Sql\Binding($postId));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('tag_id', new Sql\Binding($targetTag->id));
|
||||||
->put([$postId, $targetTag->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -99,16 +106,13 @@ class TagModel extends AbstractCrudModel
|
|||||||
|
|
||||||
public static function findAllByPostId($key)
|
public static function findAllByPostId($key)
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new Sql\SelectStatement();
|
||||||
$query
|
$stmt->setColumn('tag.*');
|
||||||
->select('tag.*')
|
$stmt->setTable('tag');
|
||||||
->from('tag')
|
$stmt->addInnerJoin('post_tag', new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id'));
|
||||||
->innerJoin('post_tag')
|
$stmt->setCriterion(new Sql\EqualsFunctor('post_tag.post_id', new Sql\Binding($key)));
|
||||||
->on('post_tag.tag_id = tag.id')
|
|
||||||
->where('post_tag.post_id = ?')
|
|
||||||
->put($key);
|
|
||||||
|
|
||||||
$rows = Database::fetchAll($query);
|
$rows = Database::fetchAll($stmt);
|
||||||
if ($rows)
|
if ($rows)
|
||||||
return self::convertRows($rows);
|
return self::convertRows($rows);
|
||||||
return [];
|
return [];
|
||||||
@ -116,17 +120,17 @@ class TagModel extends AbstractCrudModel
|
|||||||
|
|
||||||
public static function findByName($key, $throw = true)
|
public static function findByName($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('tag.*');
|
||||||
->from('tag')
|
$stmt->setTable('tag');
|
||||||
->where('LOWER(name) = LOWER(?)')->put($key);
|
$stmt->setCriterion(new Sql\NoCaseFunctor(new Sql\EqualsFunctor('name', new Sql\Binding($key))));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
if ($throw)
|
if ($throw)
|
||||||
throw new SimpleException('Invalid tag name "' . $key . '"');
|
throw new SimpleNotFoundException('Invalid tag name "' . $key . '"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,16 +138,15 @@ class TagModel extends AbstractCrudModel
|
|||||||
|
|
||||||
public static function removeUnused()
|
public static function removeUnused()
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = (new Sql\DeleteStatement)
|
||||||
->deleteFrom('tag')
|
->setTable('tag')
|
||||||
->where()
|
->setCriterion(
|
||||||
->not()->exists()
|
new Sql\NegationFunctor(
|
||||||
->open()
|
new Sql\ExistsFunctor(
|
||||||
->select('1')
|
(new Sql\SelectStatement)
|
||||||
->from('post_tag')
|
->setTable('post_tag')
|
||||||
->where('post_tag.tag_id = tag.id')
|
->setCriterion(new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id')))));
|
||||||
->close();
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class TokenModel extends AbstractCrudModel
|
class TokenModel extends AbstractCrudModel
|
||||||
implements IModel
|
|
||||||
{
|
{
|
||||||
public static function getTableName()
|
public static function getTableName()
|
||||||
{
|
{
|
||||||
@ -20,39 +22,37 @@ implements IModel
|
|||||||
'expires' => $token->expires,
|
'expires' => $token->expires,
|
||||||
];
|
];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\UpdateStatement();
|
||||||
->update('user_token')
|
$stmt->setTable('user_token');
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($token->id)));
|
||||||
->put(array_values($bindings))
|
|
||||||
->where('id = ?')->put($token->id);
|
foreach ($bindings as $key => $val)
|
||||||
Database::query($query);
|
$stmt->setColumn($key, new Sql\Binding($val));
|
||||||
|
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static function findByToken($key, $throw = true)
|
public static function findByToken($key, $throw = true)
|
||||||
{
|
{
|
||||||
if (empty($key))
|
if (empty($key))
|
||||||
throw new SimpleException('Invalid security token');
|
throw new SimpleNotFoundException('Invalid security token');
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('*')
|
$stmt->setTable('user_token');
|
||||||
->from('user_token')
|
$stmt->setColumn('*');
|
||||||
->where('token = ?')->put($key);
|
$stmt->setCriterion(new Sql\EqualsFunctor('token', new Sql\Binding($key)));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
if ($throw)
|
if ($throw)
|
||||||
throw new SimpleException('No user with such security token');
|
throw new SimpleNotFoundException('No user with such security token');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static function checkValidity($token)
|
public static function checkValidity($token)
|
||||||
{
|
{
|
||||||
if (empty($token))
|
if (empty($token))
|
||||||
@ -65,8 +65,6 @@ implements IModel
|
|||||||
throw new SimpleException('This token has expired');
|
throw new SimpleException('This token has expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static function forgeUnusedToken()
|
public static function forgeUnusedToken()
|
||||||
{
|
{
|
||||||
$tokenText = '';
|
$tokenText = '';
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \Chibi\Sql as Sql;
|
||||||
|
use \Chibi\Database as Database;
|
||||||
|
|
||||||
class UserModel extends AbstractCrudModel
|
class UserModel extends AbstractCrudModel
|
||||||
{
|
{
|
||||||
const SETTING_SAFETY = 1;
|
const SETTING_SAFETY = 1;
|
||||||
@ -34,17 +37,20 @@ class UserModel extends AbstractCrudModel
|
|||||||
'email_unconfirmed' => $user->emailUnconfirmed,
|
'email_unconfirmed' => $user->emailUnconfirmed,
|
||||||
'email_confirmed' => $user->emailConfirmed,
|
'email_confirmed' => $user->emailConfirmed,
|
||||||
'join_date' => $user->joinDate,
|
'join_date' => $user->joinDate,
|
||||||
|
'last_login_date' => $user->lastLoginDate,
|
||||||
'access_rank' => $user->accessRank,
|
'access_rank' => $user->accessRank,
|
||||||
'settings' => $user->settings,
|
'settings' => $user->settings,
|
||||||
'banned' => $user->banned
|
'banned' => $user->banned
|
||||||
];
|
];
|
||||||
|
|
||||||
$query = (new SqlQuery)
|
$stmt = (new Sql\UpdateStatement)
|
||||||
->update('user')
|
->setTable('user')
|
||||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($user->id)));
|
||||||
->put(array_values($bindings))
|
|
||||||
->where('id = ?')->put($user->id);
|
foreach ($bindings as $key => $val)
|
||||||
Database::query($query);
|
$stmt->setColumn($key, new Sql\Binding($val));
|
||||||
|
|
||||||
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,32 +58,31 @@ class UserModel extends AbstractCrudModel
|
|||||||
{
|
{
|
||||||
Database::transaction(function() use ($user)
|
Database::transaction(function() use ($user)
|
||||||
{
|
{
|
||||||
$queries = [];
|
$binding = new Sql\Binding($user->id);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('user_id = ?')->put($user->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('user_id', $binding));
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('favoritee');
|
||||||
->update('comment')
|
Database::exec($stmt);
|
||||||
->set('commenter_id = NULL')
|
|
||||||
->where('commenter_id = ?')->put($user->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt->setTable('user');
|
||||||
->update('post')
|
$stmt->setCriterion(new Sql\EqualsFunctor('id', $binding));
|
||||||
->set('uploader_id = NULL')
|
Database::exec($stmt);
|
||||||
->where('uploader_id = ?')->put($user->id);
|
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new Sql\UpdateStatement();
|
||||||
->deleteFrom('favoritee')
|
$stmt->setTable('comment');
|
||||||
->where('user_id = ?')->put($user->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('commenter_id', $binding));
|
||||||
|
$stmt->setColumn('commenter_id', new Sql\NullFunctor());
|
||||||
|
Database::exec($stmt);
|
||||||
|
|
||||||
$queries []= (new SqlQuery)
|
$stmt = new Sql\UpdateStatement();
|
||||||
->deleteFrom('user')
|
$stmt->setTable('post');
|
||||||
->where('id = ?')->put($user->id);
|
$stmt->setCriterion(new Sql\EqualsFunctor('uploader_id', $binding));
|
||||||
|
$stmt->setColumn('uploader_id', new Sql\NullFunctor());
|
||||||
foreach ($queries as $query)
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,34 +90,35 @@ class UserModel extends AbstractCrudModel
|
|||||||
|
|
||||||
public static function findByName($key, $throw = true)
|
public static function findByName($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\SelectStatement();
|
||||||
->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('user')
|
$stmt->setTable('user');
|
||||||
->where('LOWER(name) = LOWER(?)')->put(trim($key));
|
$stmt->setCriterion(new Sql\NoCaseFunctor(new Sql\EqualsFunctor('name', new Sql\Binding(trim($key)))));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
if ($throw)
|
if ($throw)
|
||||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
throw new SimpleNotFoundException('Invalid user name "' . $key . '"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findByNameOrEmail($key, $throw = true)
|
public static function findByNameOrEmail($key, $throw = true)
|
||||||
{
|
{
|
||||||
$query = new SqlQuery();
|
$stmt = new Sql\SelectStatement();
|
||||||
$query->select('*')
|
$stmt->setColumn('*');
|
||||||
->from('user')
|
$stmt->setTable('user');
|
||||||
->where('LOWER(name) = LOWER(?)')->put(trim($key))
|
$stmt->setCriterion((new Sql\DisjunctionFunctor)
|
||||||
->or('LOWER(email_confirmed) = LOWER(?)')->put(trim($key));
|
->add(new Sql\NoCaseFunctor(new Sql\EqualsFunctor('name', new Sql\Binding(trim($key)))))
|
||||||
|
->add(new Sql\NoCaseFunctor(new Sql\EqualsFunctor('email_confirmed', new Sql\Binding(trim($key))))));
|
||||||
|
|
||||||
$row = Database::fetchOne($query);
|
$row = Database::fetchOne($stmt);
|
||||||
if ($row)
|
if ($row)
|
||||||
return self::convertRow($row);
|
return self::convertRow($row);
|
||||||
|
|
||||||
if ($throw)
|
if ($throw)
|
||||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
throw new SimpleNotFoundException('Invalid user name "' . $key . '"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,20 +128,21 @@ class UserModel extends AbstractCrudModel
|
|||||||
{
|
{
|
||||||
Database::transaction(function() use ($user, $post, $score)
|
Database::transaction(function() use ($user, $post, $score)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('post_score')
|
$stmt->setTable('post_score');
|
||||||
->where('post_id = ?')->put($post->id)
|
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
->and('user_id = ?')->put($user->id);
|
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id)))
|
||||||
Database::query($query);
|
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($user->id))));
|
||||||
|
Database::exec($stmt);
|
||||||
$score = intval($score);
|
$score = intval($score);
|
||||||
if ($score != 0)
|
if ($score != 0)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery);
|
$stmt = new Sql\InsertStatement();
|
||||||
$query->insertInto('post_score')
|
$stmt->setTable('post_score');
|
||||||
->surround('post_id, user_id, score')
|
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||||
->values()->surround('?, ?, ?')
|
$stmt->setColumn('user_id', new Sql\Binding($user->id));
|
||||||
->put([$post->id, $user->id, $score]);
|
$stmt->setColumn('score', new Sql\Binding($score));
|
||||||
Database::query($query);
|
Database::exec($stmt);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -145,12 +152,11 @@ class UserModel extends AbstractCrudModel
|
|||||||
Database::transaction(function() use ($user, $post)
|
Database::transaction(function() use ($user, $post)
|
||||||
{
|
{
|
||||||
self::removeFromUserFavorites($user, $post);
|
self::removeFromUserFavorites($user, $post);
|
||||||
$query = (new SqlQuery);
|
$stmt = new Sql\InsertStatement();
|
||||||
$query->insertInto('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->surround('post_id, user_id')
|
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||||
->values()->surround('?, ?')
|
$stmt->setColumn('user_id', new Sql\Binding($user->id));
|
||||||
->put([$post->id, $user->id]);
|
Database::exec($stmt);
|
||||||
Database::query($query);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,11 +164,12 @@ class UserModel extends AbstractCrudModel
|
|||||||
{
|
{
|
||||||
Database::transaction(function() use ($user, $post)
|
Database::transaction(function() use ($user, $post)
|
||||||
{
|
{
|
||||||
$query = (new SqlQuery)
|
$stmt = new Sql\DeleteStatement();
|
||||||
->deleteFrom('favoritee')
|
$stmt->setTable('favoritee');
|
||||||
->where('post_id = ?')->put($post->id)
|
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||||
->and('user_id = ?')->put($user->id);
|
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id)))
|
||||||
Database::query($query);
|
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($user->id))));
|
||||||
|
Database::exec($stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
src/SimpleNotFoundException.php
Normal file
4
src/SimpleNotFoundException.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
class SimpleNotFoundException extends SimpleException
|
||||||
|
{
|
||||||
|
}
|
@ -1,99 +0,0 @@
|
|||||||
<?php
|
|
||||||
class SqlQuery
|
|
||||||
{
|
|
||||||
protected $sql;
|
|
||||||
protected $bindings;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->sql = '';
|
|
||||||
$this->bindings = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __call($name, array $arguments)
|
|
||||||
{
|
|
||||||
$name = TextHelper::camelCaseToKebabCase($name);
|
|
||||||
$name = str_replace('-', ' ', $name);
|
|
||||||
$this->sql .= $name . ' ';
|
|
||||||
|
|
||||||
if (!empty($arguments))
|
|
||||||
{
|
|
||||||
$arg = array_shift($arguments);
|
|
||||||
assert(empty($arguments));
|
|
||||||
|
|
||||||
if (is_object($arg))
|
|
||||||
{
|
|
||||||
throw new Exception('Not implemented');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->sql .= $arg . ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function put($arg)
|
|
||||||
{
|
|
||||||
if (is_array($arg))
|
|
||||||
{
|
|
||||||
foreach ($arg as $key => $val)
|
|
||||||
{
|
|
||||||
if (is_numeric($key))
|
|
||||||
$this->bindings []= $val;
|
|
||||||
else
|
|
||||||
$this->bindings[$key] = $val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->bindings []= $arg;
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function raw($raw)
|
|
||||||
{
|
|
||||||
$this->sql .= $raw . ' ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function open()
|
|
||||||
{
|
|
||||||
$this->sql .= '(';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close()
|
|
||||||
{
|
|
||||||
$this->sql .= ') ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function surround($raw)
|
|
||||||
{
|
|
||||||
$this->sql .= '(' . $raw . ') ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function genSlots($bindings)
|
|
||||||
{
|
|
||||||
if (empty($bindings))
|
|
||||||
return $this;
|
|
||||||
$this->sql .= '(';
|
|
||||||
$this->sql .= join(',', array_fill(0, count($bindings), '?'));
|
|
||||||
$this->sql .= ') ';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBindings()
|
|
||||||
{
|
|
||||||
return $this->bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSql()
|
|
||||||
{
|
|
||||||
return trim($this->sql);
|
|
||||||
}
|
|
||||||
}
|
|
1
src/Upgrades/mysql/Upgrade10.sql
Normal file
1
src/Upgrades/mysql/Upgrade10.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE user ADD COLUMN last_login_date INTEGER DEFAULT NULL;
|
19
src/Upgrades/mysql/Upgrade11.sql
Normal file
19
src/Upgrades/mysql/Upgrade11.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
INSERT
|
||||||
|
INTO post_score(user_id, post_id, score)
|
||||||
|
SELECT user_id, favoritee.post_id, 1
|
||||||
|
FROM favoritee WHERE NOT EXISTS
|
||||||
|
(
|
||||||
|
SELECT *
|
||||||
|
FROM post_score ps2
|
||||||
|
WHERE favoritee.post_id = ps2.post_id
|
||||||
|
AND favoritee.user_id = ps2.user_id
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE post_score
|
||||||
|
SET score = 1
|
||||||
|
WHERE user_id IN
|
||||||
|
(
|
||||||
|
SELECT user_id
|
||||||
|
FROM favoritee
|
||||||
|
WHERE favoritee.post_id = post_score.post_id
|
||||||
|
);
|
13
src/Upgrades/mysql/Upgrade12.sql
Normal file
13
src/Upgrades/mysql/Upgrade12.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
ALTER TABLE post ADD COLUMN comment_date INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
|
CREATE TRIGGER comment_update_date AFTER UPDATE ON comment FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE post SET comment_date = (SELECT MAX(comment_date) FROM comment WHERE comment.post_id = post.id);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER comment_insert_date AFTER INSERT ON comment FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE post SET comment_date = (SELECT MAX(comment_date) FROM comment WHERE comment.post_id = post.id);
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE comment SET id = id;
|
1
src/Upgrades/sqlite/Upgrade10.sql
Normal file
1
src/Upgrades/sqlite/Upgrade10.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE user ADD COLUMN last_login_date INTEGER DEFAULT NULL;
|
19
src/Upgrades/sqlite/Upgrade11.sql
Normal file
19
src/Upgrades/sqlite/Upgrade11.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
INSERT
|
||||||
|
INTO post_score(user_id, post_id, score)
|
||||||
|
SELECT user_id, favoritee.post_id, 1
|
||||||
|
FROM favoritee WHERE NOT EXISTS
|
||||||
|
(
|
||||||
|
SELECT *
|
||||||
|
FROM post_score ps2
|
||||||
|
WHERE favoritee.post_id = ps2.post_id
|
||||||
|
AND favoritee.user_id = ps2.user_id
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE post_score
|
||||||
|
SET score = 1
|
||||||
|
WHERE user_id IN
|
||||||
|
(
|
||||||
|
SELECT user_id
|
||||||
|
FROM favoritee
|
||||||
|
WHERE favoritee.post_id = post_score.post_id
|
||||||
|
);
|
13
src/Upgrades/sqlite/Upgrade12.sql
Normal file
13
src/Upgrades/sqlite/Upgrade12.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
ALTER TABLE post ADD COLUMN comment_date INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
|
CREATE TRIGGER comment_update_date AFTER UPDATE ON comment FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE post SET comment_date = (SELECT MAX(comment_date) FROM comment WHERE comment.post_id = post.id);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER comment_insert_date AFTER INSERT ON comment FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE post SET comment_date = (SELECT MAX(comment_date) FROM comment WHERE comment.post_id = post.id);
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE comment SET id = id;
|
@ -1,20 +1,23 @@
|
|||||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth aligned" method="post">
|
<?php
|
||||||
<div>
|
CustomAssetViewDecorator::setSubTitle('authentication form');
|
||||||
<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>
|
CustomAssetViewDecorator::addStylesheet('auth.css');
|
||||||
</div>
|
?>
|
||||||
|
|
||||||
<div>
|
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth" method="post">
|
||||||
<label class="left" for="name">User name:</label>
|
<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 class="form-row">
|
||||||
|
<label for="name">User name:</label>
|
||||||
<div class="input-wrapper"><input type="text" id="name" name="name"/></div>
|
<div class="input-wrapper"><input type="text" id="name" name="name"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left" for="password">Password:</label>
|
<label for="password">Password:</label>
|
||||||
<div class="input-wrapper"><input type="password" id="password" name="password"/></div>
|
<div class="input-wrapper"><input type="password" id="password" name="password"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left"> </label>
|
<label></label>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<button class="submit" type="submit">Log in</button>
|
<button class="submit" type="submit">Log in</button>
|
||||||
|
|
||||||
@ -30,8 +33,8 @@
|
|||||||
|
|
||||||
<input type="hidden" name="submit" value="1"/>
|
<input type="hidden" name="submit" value="1"/>
|
||||||
|
|
||||||
<div class="help">
|
<div class="form-row help">
|
||||||
<label class="left"> </label>
|
<label></label>
|
||||||
<div>
|
<div>
|
||||||
<p>Problems logging in?</p>
|
<p>Problems logging in?</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
21
src/Views/comment-add.phtml
Normal file
21
src/Views/comment-add.phtml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::addStylesheet('comment-edit.css');
|
||||||
|
CustomAssetViewDecorator::addScript('comment-edit.js');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form action="<?php echo \Chibi\UrlHelper::route('comment', 'add', ['postId' => $this->context->transport->post->id]) ?>" method="post" class="add-comment">
|
||||||
|
<h1>add comment</h1>
|
||||||
|
|
||||||
|
<div class="preview"></div>
|
||||||
|
|
||||||
|
<div class="form-row text">
|
||||||
|
<div class="input-wrapper"><textarea name="text" cols="50" rows="3"></textarea></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="submit" value="1"/>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<button name="sender" class="submit" type="submit" value="preview">Preview</button>
|
||||||
|
<button name="sender" class="submit" type="submit" value="submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
21
src/Views/comment-edit.phtml
Normal file
21
src/Views/comment-edit.phtml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::addStylesheet('comment-edit.css');
|
||||||
|
CustomAssetViewDecorator::addScript('comment-edit.js');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form action="<?php echo \Chibi\UrlHelper::route('comment', 'edit', ['id' => $this->context->transport->comment->id]) ?>" method="post" class="edit-comment">
|
||||||
|
<h1>edit comment</h1>
|
||||||
|
|
||||||
|
<div class="preview"></div>
|
||||||
|
|
||||||
|
<div class="form-row text">
|
||||||
|
<div class="input-wrapper"><textarea name="text" cols="50" rows="3"><?php echo TextHelper::secureWhitespace($this->context->transport->comment->text) ?></textarea></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="submit" value="1"/>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<button name="sender" class="submit" type="submit" value="preview">Preview</button>
|
||||||
|
<button name="sender" class="submit" type="submit" value="submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -1,36 +1,37 @@
|
|||||||
<?php if (empty($this->context->transport->comments)): ?>
|
<?php
|
||||||
|
CustomAssetViewDecorator::setSubTitle('comments');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if (empty($this->context->transport->posts)): ?>
|
||||||
<p class="alert alert-warning">No comments to show.</p>
|
<p class="alert alert-warning">No comments to show.</p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="comments paginator-content">
|
|
||||||
<?php
|
<?php
|
||||||
$groups = [];
|
CustomAssetViewDecorator::addStylesheet('comment-list.css');
|
||||||
$posts = [];
|
CustomAssetViewDecorator::addStylesheet('comment-small.css');
|
||||||
$currentGroupPostId = null;
|
CustomAssetViewDecorator::addStylesheet('comment-edit.css');
|
||||||
$currentGroup = null;
|
CustomAssetViewDecorator::addScript('comment-edit.js');
|
||||||
foreach ($this->context->transport->comments as $comment)
|
|
||||||
{
|
|
||||||
if ($comment->postId != $currentGroupPostId)
|
|
||||||
{
|
|
||||||
unset($currentGroup);
|
|
||||||
$currentGroup = [];
|
|
||||||
$currentGroupPostId = $comment->postId;
|
|
||||||
$posts[$comment->postId] = $comment->getPost();
|
|
||||||
$groups[] = &$currentGroup;
|
|
||||||
}
|
|
||||||
$currentGroup []= $comment;
|
|
||||||
}
|
|
||||||
?>
|
?>
|
||||||
<?php foreach ($groups as $group): ?>
|
|
||||||
|
<div class="comments-wrapper">
|
||||||
|
<div class="comments paginator-content">
|
||||||
|
<?php foreach ($this->context->transport->posts as $post): ?>
|
||||||
<div class="comment-group">
|
<div class="comment-group">
|
||||||
<div class="post-wrapper">
|
<div class="post-wrapper">
|
||||||
<?php $this->context->post = $posts[reset($group)->postId] ?>
|
<?php $this->context->post = $post ?>
|
||||||
<?php echo $this->renderFile('post-small') ?>
|
<?php echo $this->renderFile('post-small') ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="comments">
|
<div class="comments">
|
||||||
<?php foreach ($group as $comment): ?>
|
<?php $comments = array_reverse($post->getComments()) ?>
|
||||||
|
<?php foreach (array_slice($comments, 0, $this->config->comments->maxCommentsInList) as $comment): ?>
|
||||||
<?php $this->context->comment = $comment ?>
|
<?php $this->context->comment = $comment ?>
|
||||||
<?php echo $this->renderFile('comment-small') ?>
|
<?php echo $this->renderFile('comment-small') ?>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
|
|
||||||
|
<?php if (count($comments) > $this->config->comments->maxCommentsInList): ?>
|
||||||
|
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||||
|
<span class="hellip">(more…)</span>
|
||||||
|
</a>
|
||||||
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -38,4 +39,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php $this->renderFile('paginator') ?>
|
<?php $this->renderFile('paginator') ?>
|
||||||
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::addStylesheet('comment-small.css');
|
||||||
|
CustomAssetViewDecorator::addStylesheet('comment-edit.css');
|
||||||
|
CustomAssetViewDecorator::addScript('comment-edit.js');
|
||||||
|
?>
|
||||||
|
|
||||||
<div class="comment">
|
<div class="comment">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<?php $commenter = $this->context->comment->getCommenter() ?>
|
<?php $commenter = $this->context->comment->getCommenter() ?>
|
||||||
@ -22,10 +28,18 @@
|
|||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="date">
|
<span class="date" title="<?php echo TextHelper::formatDate($this->context->comment->commentDate, true) ?>">
|
||||||
<?php echo date('Y-m-d H:i', $this->context->comment->commentDate) ?>
|
<?php echo TextHelper::formatDate($this->context->comment->commentDate, false) ?>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<?php if (PrivilegesHelper::confirm(Privilege::EditComment, PrivilegesHelper::getIdentitySubPrivilege($commenter))): ?>
|
||||||
|
<span class="edit">
|
||||||
|
<a href="<?php echo \Chibi\UrlHelper::route('comment', 'edit', ['id' => $this->context->comment->id]) ?>">
|
||||||
|
edit
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($commenter))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($commenter))): ?>
|
||||||
<span class="delete">
|
<span class="delete">
|
||||||
<a class="simple-action confirmable" href="<?php echo \Chibi\UrlHelper::route('comment', 'delete', ['id' => $this->context->comment->id]) ?>" data-confirm-text="Are you sure you want to delete this comment?">
|
<a class="simple-action confirmable" href="<?php echo \Chibi\UrlHelper::route('comment', 'delete', ['id' => $this->context->comment->id]) ?>" data-confirm-text="Are you sure you want to delete this comment?">
|
||||||
|
24
src/Views/debug.phtml
Normal file
24
src/Views/debug.phtml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php CustomAssetViewDecorator::addStylesheet('debug.css') ?>
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<?php foreach (\Chibi\Database::getLogs() as $log): ?>
|
||||||
|
<div class="debug">
|
||||||
|
<?php
|
||||||
|
$query = $log->getStatement()->getAsString();
|
||||||
|
$query = str_replace('(', '<span>(', $query);
|
||||||
|
$query = str_replace(')', ')</span>', $query);
|
||||||
|
?>
|
||||||
|
<pre class="query"><?php echo $query ?></pre>
|
||||||
|
|
||||||
|
<pre class="bindings"><?php echo join(', ', array_map(function($key) use ($log)
|
||||||
|
{
|
||||||
|
return $key . '=<span class="value">' . $log->getStatement()->getBindings()[$key] . '</span>';
|
||||||
|
},
|
||||||
|
array_keys($log->getStatement()->getBindings()))) ?></pre>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><td>Execution:</td><td><?php echo sprintf('%.05fs', $log->getExecutionTime()) ?></td></tr>
|
||||||
|
<tr><td>Retrieval:</td><td><?php echo sprintf('%.05fs', $log->getRetrievalTime()) ?></td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
@ -1,9 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
CustomAssetViewDecorator::setSubtitle('help');
|
||||||
|
CustomAssetViewDecorator::addStylesheet('index-help.css');
|
||||||
|
|
||||||
$tabs = $this->config->help->subTitles;
|
$tabs = $this->config->help->subTitles;
|
||||||
$firstTab = !empty($tabs) ? array_keys($tabs)[0] : null;
|
$firstTab = !empty($tabs) ? array_keys($tabs)[0] : null;
|
||||||
|
$showTabs = count($tabs) > 1;
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<?php if (count($tabs) > 1): ?>
|
<?php if ($showTabs): ?>
|
||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($tabs as $tab => $text): ?>
|
<?php foreach ($tabs as $tab => $text): ?>
|
||||||
@ -19,6 +23,12 @@ $firstTab = !empty($tabs) ? array_keys($tabs)[0] : null;
|
|||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php echo TextHelper::parseMarkdown(file_get_contents($this->context->path)) ?>
|
<?php echo TextHelper::parseMarkdown(file_get_contents($this->context->path)) ?>
|
||||||
|
|
||||||
|
<?php if ($showTabs): ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::setSubtitle('home');
|
||||||
|
CustomAssetViewDecorator::addStylesheet('index-index.css');
|
||||||
|
?>
|
||||||
|
|
||||||
<div id="welcome">
|
<div id="welcome">
|
||||||
<h1><?php echo $this->config->main->title ?></h1>
|
<h1><?php echo $this->config->main->title ?></h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
<?php \Chibi\HeadersHelper::set('Content-Type', 'application/json') ?>
|
<?php
|
||||||
<?php echo TextHelper::jsonEncode($this->context->transport, '/.*(email|confirm|pass|salt)/i') ?>
|
\Chibi\HeadersHelper::set('Content-Type', 'application/json');
|
||||||
|
echo TextHelper::jsonEncode($this->context->transport, '/.*(email|confirm|pass|salt)/i');
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::addStylesheet('../lib/jquery-ui/jquery-ui.css');
|
||||||
|
CustomAssetViewDecorator::addStylesheet('core.css');
|
||||||
|
CustomAssetViewDecorator::addScript('../lib/jquery/jquery.min.js');
|
||||||
|
CustomAssetViewDecorator::addScript('../lib/jquery-ui/jquery-ui.min.js');
|
||||||
|
CustomAssetViewDecorator::addScript('../lib/mousetrap/mousetrap.min.js');
|
||||||
|
CustomAssetViewDecorator::addScript('core.js');
|
||||||
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<?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 ?>
|
|
||||||
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -41,37 +35,21 @@
|
|||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="main-wrapper">
|
<div class="main-wrapper">
|
||||||
|
<hr>
|
||||||
<span>Load: <?php echo sprintf('%.05f', microtime(true) - $this->context->startTime) ?>s</span>
|
<span>Load: <?php echo sprintf('%.05f', microtime(true) - $this->context->startTime) ?>s</span>
|
||||||
<span>Queries: <?php echo count(Database::getLogs()) ?></span>
|
<span>Queries: <?php echo count(\Chibi\Database::getLogs()) ?></span>
|
||||||
<span><a href="<?php echo SZURU_LINK ?>">szurubooru v<?php echo SZURU_VERSION ?></a></span>
|
<span><a href="<?php echo SZURU_LINK ?>">szurubooru v<?php echo SZURU_VERSION ?></a></span>
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::ListLogs)): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::ListLogs)): ?>
|
||||||
<span><a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Logs</a></span>
|
<span><a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Logs</a></span>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
|
||||||
<?php if ($this->config->misc->debugQueries): ?>
|
|
||||||
<hr>
|
<hr>
|
||||||
<div class="main-wrapper">
|
|
||||||
<pre class="debug">
|
|
||||||
<?php foreach (Database::getLogs() as $query)
|
|
||||||
{
|
|
||||||
$bindings = [];
|
|
||||||
foreach ($query->getBindings() as $k => $v)
|
|
||||||
$bindings []= $k . '=' . $v;
|
|
||||||
printf('<p>%s [%s]</p>', htmlspecialchars($query->getSql()), join(', ', $bindings));
|
|
||||||
} ?>
|
|
||||||
</pre>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<?php foreach (array_unique($this->context->scripts) as $name): ?>
|
<?php if ($this->config->misc->debugQueries): ?>
|
||||||
<script type="text/javascript" src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/js/' . $name) ?>"></script>
|
<?php echo $this->renderFile('debug') ?>
|
||||||
<?php endforeach ?>
|
<?php endif ?>
|
||||||
<script type="text/javascript">
|
|
||||||
$(function()
|
<div id="small-screen"></div>
|
||||||
{
|
|
||||||
$('body').trigger('dom-update');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
$this->context->subTitle = 'latest logs';
|
||||||
|
?>
|
||||||
|
|
||||||
<?php if (empty($this->context->transport->logs)): ?>
|
<?php if (empty($this->context->transport->logs)): ?>
|
||||||
<p class="alert alert-warning">No logs to show.</p>
|
<p class="alert alert-warning">No logs to show.</p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
<?php if (empty($this->context->transport->log)): ?>
|
<?php
|
||||||
|
CustomAssetViewDecorator::setSubTitle('logs (' . $name . ')');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if (empty($this->context->transport->lines)): ?>
|
||||||
<p class="alert alert-warning">This log is empty. <a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Go back</a></p>
|
<p class="alert alert-warning">This log is empty. <a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Go back</a></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::addStylesheet('logs.css');
|
||||||
|
CustomAssetViewDecorator::addScript('logs.js');
|
||||||
|
?>
|
||||||
|
|
||||||
<form action="<?php echo \Chibi\UrlHelper::route('log', 'view', ['name' => $this->context->transport->name]) ?>" method="get">
|
<form action="<?php echo \Chibi\UrlHelper::route('log', 'view', ['name' => $this->context->transport->name]) ?>" method="get">
|
||||||
Keep only lines that contain:
|
Keep only lines that contain:
|
||||||
|
|
||||||
<input type="text" name="filter" value="<?php echo $this->context->transport->filter ?>" placeholder="any text…"/>
|
<input type="text" name="query" value="<?php echo htmlspecialchars($this->context->transport->filter) ?>" placeholder="any text…"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<pre><?php echo $this->context->transport->log ?></pre>
|
<div class="paginator-content">
|
||||||
|
<pre><?php echo $this->context->transport->lines ?></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php $this->renderFile('paginator') ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
if (!isset($this->context->transport->paginator))
|
||||||
|
return;
|
||||||
|
|
||||||
$page = $this->context->transport->paginator->page;
|
$page = $this->context->transport->paginator->page;
|
||||||
$pageCount = $this->context->transport->paginator->pageCount;
|
$pageCount = $this->context->transport->paginator->pageCount;
|
||||||
|
|
||||||
@ -38,6 +41,12 @@ if (!function_exists('pageUrl'))
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<?php if (!empty($pagesVisible)): ?>
|
<?php if (!empty($pagesVisible)): ?>
|
||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::addStylesheet('paginator.css');
|
||||||
|
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||||
|
CustomAssetViewDecorator::addScript('paginator-endless.js');
|
||||||
|
?>
|
||||||
|
|
||||||
<nav class="paginator-wrapper">
|
<nav class="paginator-wrapper">
|
||||||
<ul class="paginator">
|
<ul class="paginator">
|
||||||
<?php if ($page > 1): ?>
|
<?php if ($page > 1): ?>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<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">
|
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit-post">
|
||||||
<h1>edit post</h1>
|
<h1>edit post</h1>
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
<div class="safety">
|
<div class="form-row safety">
|
||||||
<label class="left">Safety:</label>
|
<label>Safety:</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="safety" value="<?php echo $safety ?>" <?php if ($this->context->transport->post->safety == $safety) echo 'checked="checked"' ?>/>
|
<input type="radio" name="safety" value="<?php echo $safety ?>" <?php if ($this->context->transport->post->safety == $safety) echo 'checked="checked"' ?>/>
|
||||||
@ -10,45 +11,46 @@
|
|||||||
</label>
|
</label>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
<div class="tags">
|
<div class="form-row tags">
|
||||||
<label class="left" for="tags">Tags:</label>
|
<label 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->getTags())) ?>"/></div>
|
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags…" value="<?php echo join(',', array_map(function($tag) { return htmlspecialchars($tag->name); }, $this->context->transport->post->getTags())) ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="edit-token" id="edit-token" value="<?php echo $this->context->transport->editToken ?>"/>
|
<input type="hidden" name="edit-token" id="edit-token" value="<?php echo htmlspecialchars($this->context->transport->post->getEditToken()) ?>"/>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
<div class="source">
|
<div class="form-row source">
|
||||||
<label class="left" for="source">Source:</label>
|
<label 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 class="input-wrapper"><input type="text" name="source" id="source" value="<?php echo htmlspecialchars($this->context->transport->post->source) ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
<div class="thumb">
|
<div class="form-row thumb">
|
||||||
<label class="left" for="relations">Relations:</label>
|
<label 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->getRelations())) ?>"/></div>
|
<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->getRelations())) ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
<div class="url">
|
<div class="form-row url">
|
||||||
<label class="left" for="url">File:</label>
|
<label for="url">File:</label>
|
||||||
<div class="input-wrapper"><input type="text" name="url" id="url" placeholder="Some url…"/></div>
|
<div class="input-wrapper"><input type="text" name="url" id="url" placeholder="Some url…"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="file">
|
<div class="form-row file">
|
||||||
<label class="left" for="file"></label>
|
<label for="file"></label>
|
||||||
<div class="input-wrapper"><input type="file" name="file" id="file"/></div>
|
<div class="input-wrapper"><input type="file" name="file" id="file"/></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
<div class="thumb">
|
<div class="form-row thumb">
|
||||||
<label class="left" for="thumb">Thumb:</label>
|
<label for="thumb">Thumb:</label>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<input type="file" name="thumb" id="thumb"/>
|
<input type="file" name="thumb" id="thumb"/>
|
||||||
<?php if ($this->context->transport->post->hasCustomThumb()): ?>
|
<?php if ($this->context->transport->post->hasCustomThumb()): ?>
|
||||||
@ -60,8 +62,8 @@
|
|||||||
|
|
||||||
<input type="hidden" name="submit" value="1"/>
|
<input type="hidden" name="submit" value="1"/>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left"> </label>
|
<label></label>
|
||||||
<button class="submit" type="submit">Submit</button>
|
<button class="submit" type="submit">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<?php CustomAssetViewDecorator::setPageThumb(\Chibi\UrlHelper::route('post', 'thumb', ['name' => $this->context->transport->post->name])) ?>
|
||||||
<?php $post = $this->context->transport->post ?>
|
<?php $post = $this->context->transport->post ?>
|
||||||
|
|
||||||
<?php if ($post->type == PostType::Image): ?>
|
<?php if ($post->type == PostType::Image): ?>
|
||||||
@ -14,10 +15,13 @@
|
|||||||
|
|
||||||
<?php elseif ($post->type == PostType::Flash): ?>
|
<?php elseif ($post->type == PostType::Flash): ?>
|
||||||
|
|
||||||
<iframe width="<?php echo $post->imageWidth ?>" height="<?php echo $post->imageHeight ?>" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $post->name]) ?>"> </iframe>
|
<object type="<?php echo $post->mimeType ?>" width="<?php echo $post->imageWidth ?>" height="<?php echo $post->imageHeight ?>" data="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $post->name]) ?>">
|
||||||
|
<param name="movie" value="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $post->name]) ?>"/>
|
||||||
|
<param name="wmode" value="opaque"/>
|
||||||
|
</object>
|
||||||
|
|
||||||
<?php elseif ($post->type == PostType::Youtube): ?>
|
<?php elseif ($post->type == PostType::Youtube): ?>
|
||||||
|
|
||||||
<iframe style="width: 800px; height: 600px; border: 0;" src="//www.youtube.com/embed/<?php echo $post->origName ?>" allowfullscreen></iframe>
|
<iframe style="width: 800px; height: 600px; border: 0;" src="//www.youtube.com/embed/<?php echo $post->fileHash ?>?wmode=opaque" allowfullscreen></iframe>
|
||||||
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
CustomAssetViewDecorator::setSubTitle('posts');
|
||||||
|
|
||||||
$tabs = [];
|
$tabs = [];
|
||||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['All posts', \Chibi\UrlHelper::route('post', 'list')];
|
if (PrivilegesHelper::confirm(Privilege::ListPosts))
|
||||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['Random', \Chibi\UrlHelper::route('post', 'random')];
|
$tabs []= ['All posts', \Chibi\UrlHelper::route('post', 'list')];
|
||||||
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])];
|
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', [
|
||||||
|
'source' => 'mass-tag',
|
||||||
|
'query' => isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '',
|
||||||
|
'page' => isset($this->context->transport->paginator) ? $this->context->transport->paginator->page : 1])];
|
||||||
|
|
||||||
$activeTab = 0;
|
$activeTab = 0;
|
||||||
if ($this->context->route->simpleActionName == 'random') $activeTab = 1;
|
if ($this->context->route->simpleActionName == 'random') $activeTab = 1;
|
||||||
@ -28,4 +39,6 @@ if ($this->context->source == 'mass-tag') $activeTab = 3;
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<?php $this->renderFile('post-list') ?>
|
<div class="tab-content">
|
||||||
|
<?php $this->renderFile('post-list') ?>
|
||||||
|
</div>
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::addStylesheet('post-list.css');
|
||||||
|
CustomAssetViewDecorator::addScript('post-list.js');
|
||||||
|
?>
|
||||||
|
|
||||||
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag' and PrivilegesHelper::confirm(Privilege::MassTag)): ?>
|
<?php if (isset($this->context->source) and $this->context->source == 'mass-tag' and PrivilegesHelper::confirm(Privilege::MassTag)): ?>
|
||||||
<?php $this->renderFile('tag-mass-tag') ?>
|
<?php $this->renderFile('tag-mass-tag') ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (empty($this->context->transport->posts)): ?>
|
|
||||||
|
<?php if (!empty($this->context->transport->message)): ?>
|
||||||
|
<?php $this->renderFile('message') ?>
|
||||||
|
<?php elseif (empty($this->context->transport->posts)): ?>
|
||||||
<p class="alert alert-warning">No posts to show.</p>
|
<p class="alert alert-warning">No posts to show.</p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="posts-wrapper">
|
<div class="posts-wrapper">
|
||||||
@ -15,7 +23,5 @@
|
|||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php $this->renderFile('paginator') ?>
|
||||||
$this->renderFile('paginator');
|
|
||||||
?>
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
@ -1,11 +1,25 @@
|
|||||||
<?php $classNames = ['post', 'post-type-' . TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post->type))] ?>
|
<?php
|
||||||
<?php $masstag = (isset($this->context->source) and $this->context->source == 'mass-tag' and !empty($this->context->additionalInfo)) ?>
|
CustomAssetViewDecorator::addStylesheet('post-small.css');
|
||||||
<?php if ($masstag): ?>
|
|
||||||
<?php $classNames []= 'taggable' ?>
|
$classNames =
|
||||||
<?php if ($this->context->post->isTaggedWith($this->context->additionalInfo)): ?>
|
[
|
||||||
<?php $classNames []= 'tagged' ?>
|
'post',
|
||||||
<?php endif ?>
|
'post-type-' . TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post->type))
|
||||||
<?php endif ?>
|
];
|
||||||
|
|
||||||
|
$masstag = (isset($this->context->source)
|
||||||
|
and $this->context->source == 'mass-tag'
|
||||||
|
and !empty($this->context->additionalInfo));
|
||||||
|
|
||||||
|
if ($masstag)
|
||||||
|
{
|
||||||
|
$classNames []= 'taggable';
|
||||||
|
if ($this->context->post->isTaggedWith($this->context->additionalInfo))
|
||||||
|
{
|
||||||
|
$classNames []= 'tagged';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<div class="<?php echo implode(' ', $classNames) ?>">
|
<div class="<?php echo implode(' ', $classNames) ?>">
|
||||||
<?php if ($masstag): ?>
|
<?php if ($masstag): ?>
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::setSubTitle('upload');
|
||||||
|
CustomAssetViewDecorator::addStylesheet('post-upload.css');
|
||||||
|
CustomAssetViewDecorator::addScript('post-upload.js');
|
||||||
|
CustomAssetViewDecorator::addStylesheet('../lib/tagit/jquery.tagit.css');
|
||||||
|
CustomAssetViewDecorator::addScript('../lib/tagit/jquery.tagit.js');
|
||||||
|
?>
|
||||||
|
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<div class="unit">
|
<div class="unit">
|
||||||
<h1>file upload</h1>
|
<h1>file upload</h1>
|
||||||
@ -10,38 +18,18 @@
|
|||||||
|
|
||||||
<div id="inner-content">
|
<div id="inner-content">
|
||||||
<div id="upload-step1">
|
<div id="upload-step1">
|
||||||
<nav class="tabs">
|
|
||||||
<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 class="tab file">
|
|
||||||
<input type=file multiple style="display: none"/>
|
|
||||||
<div id="file-handler-wrapper">
|
<div id="file-handler-wrapper">
|
||||||
|
<input type=file multiple style="display: none"/>
|
||||||
<div id="file-handler">
|
<div id="file-handler">
|
||||||
Drop files here!<br>
|
Drop files here!<br>
|
||||||
Or just click on this box.
|
Or just click on this box.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab url">
|
|
||||||
<div id="url-handler-wrapper">
|
<div id="url-handler-wrapper">
|
||||||
<div id="url-handler">
|
<div id="url-handler">
|
||||||
<div class="input-wrapper"><textarea placeholder="Paste some URLs here, one per line." name="urls"></textarea></div>
|
<div class="input-wrapper"><input placeholder="Alternatively, paste an URL here." name="url"/></div>
|
||||||
</div>
|
<button class="submit" type="submit">Add URL</button>
|
||||||
<button class="submit" type="submit">Add</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -79,14 +67,15 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>" method="post" class="aligned">
|
<form action="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>" method="post">
|
||||||
<div class="file-name">
|
<div class="form-row file-name">
|
||||||
<label class="left">File:</label>
|
<label>File:</label>
|
||||||
<strong>filename.jpg</strong>
|
<strong>filename.jpg</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="safety">
|
<div class="form-row safety">
|
||||||
<label class="left">Safety:</label>
|
<label>Safety:</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
<?php $checked = false ?>
|
<?php $checked = false ?>
|
||||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||||
<label>
|
<label>
|
||||||
@ -100,16 +89,16 @@
|
|||||||
<input type="checkbox" name="anonymous" value="1"/>
|
<input type="checkbox" name="anonymous" value="1"/>
|
||||||
Upload anonymously
|
Upload anonymously
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tags">
|
<div class="form-row tags">
|
||||||
<label class="left">Tags:</label>
|
<label>Tags:</label>
|
||||||
<div class="input-wrapper"><input type="text" name="tags" placeholder="enter some tags…"/></div>
|
<div class="input-wrapper"><input type="text" name="tags" placeholder="enter some tags…"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="source">
|
<div class="form-row source">
|
||||||
<label class="left">Source:</label>
|
<label>Source:</label>
|
||||||
<div class="input-wrapper"><input type="text" name="source" placeholder="where did you get this from? (optional)"/></div>
|
<div class="input-wrapper"><input type="text" name="source" placeholder="where did you get this from? (optional)"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -118,3 +107,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<img id="lightbox" src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="Preview"/>
|
||||||
|
@ -1,3 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
CustomAssetViewDecorator::setSubTitle('showing ' . TextHelper::reprPost($this->context->transport->post) . ' – ' . TextHelper::reprTags($this->context->transport->post->getTags()));
|
||||||
|
CustomAssetViewDecorator::addStylesheet('post-view.css');
|
||||||
|
CustomAssetViewDecorator::addScript('post-view.js');
|
||||||
|
CustomAssetViewDecorator::addStylesheet('../lib/tagit/jquery.tagit.css');
|
||||||
|
CustomAssetViewDecorator::addScript('../lib/tagit/jquery.tagit.js');
|
||||||
|
|
||||||
|
$editPostPrivileges = [
|
||||||
|
Privilege::EditPostSafety,
|
||||||
|
Privilege::EditPostTags,
|
||||||
|
Privilege::EditPostThumb,
|
||||||
|
Privilege::EditPostSource,
|
||||||
|
];
|
||||||
|
$editPostPrivileges = array_fill_keys($editPostPrivileges, false);
|
||||||
|
foreach (array_keys($editPostPrivileges) as $privilege)
|
||||||
|
{
|
||||||
|
if (PrivilegesHelper::confirm($privilege, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||||
|
$editPostPrivileges[$privilege] = true;
|
||||||
|
}
|
||||||
|
$canEditAnything = count(array_filter($editPostPrivileges)) > 0;
|
||||||
|
?>
|
||||||
|
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<nav id="around">
|
<nav id="around">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
@ -33,9 +55,9 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="unit tags">
|
<div class="unit tags">
|
||||||
<h1>tags (<?php echo count($this->context->transport->post->getTags()) ?>)</h1>
|
|
||||||
<ul>
|
|
||||||
<?php $tags = $this->context->transport->post->getTags() ?>
|
<?php $tags = $this->context->transport->post->getTags() ?>
|
||||||
|
<h1>tags (<?php echo count($tags) ?>)</h1>
|
||||||
|
<ul>
|
||||||
<?php uasort($tags, function($a, $b) { return strnatcasecmp($a->name, $b->name); }) ?>
|
<?php uasort($tags, function($a, $b) { return strnatcasecmp($a->name, $b->name); }) ?>
|
||||||
<?php foreach ($tags as $tag): ?>
|
<?php foreach ($tags as $tag): ?>
|
||||||
<li title="<?php echo $tag->name ?>">
|
<li title="<?php echo $tag->name ?>">
|
||||||
@ -53,13 +75,12 @@
|
|||||||
<div class="unit details">
|
<div class="unit details">
|
||||||
<h1>details</h1>
|
<h1>details</h1>
|
||||||
|
|
||||||
<div class="key-value uploader">
|
<div class="uploader">
|
||||||
<span class="key">Uploader:</span>
|
|
||||||
<?php $uploader = $this->context->transport->post->getUploader() ?>
|
<?php $uploader = $this->context->transport->post->getUploader() ?>
|
||||||
<?php if ($uploader): ?>
|
<?php if ($uploader): ?>
|
||||||
<span class="value" title="<?php echo $val = $uploader->name ?>">
|
<span class="value" title="<?php echo $val = $uploader->name ?>">
|
||||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $uploader->name]) ?>">
|
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $uploader->name]) ?>">
|
||||||
<img src="<?php echo htmlentities($uploader->getAvatarUrl(16)) ?>" alt="<?php echo $uploader->name ?>"/>
|
<img src="<?php echo htmlentities($uploader->getAvatarUrl(24)) ?>" alt="<?php echo $uploader->name ?>"/>
|
||||||
<?php echo $val ?>
|
<?php echo $val ?>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
@ -69,6 +90,10 @@
|
|||||||
<?php echo UserModel::getAnonymousName() ?>
|
<?php echo UserModel::getAnonymousName() ?>
|
||||||
</span>
|
</span>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
<br>
|
||||||
|
<span class="date" title="<?php echo TextHelper::formatDate($this->context->transport->post->uploadDate, true) ?>">
|
||||||
|
<?php echo TextHelper::formatDate($this->context->transport->post->uploadDate, false) ?>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="key-value safety">
|
<div class="key-value safety">
|
||||||
@ -78,12 +103,34 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="key-value source">
|
||||||
|
<span class="key">Source:</span>
|
||||||
|
<span class="value" title="<?php echo $val = htmlspecialchars($this->context->transport->post->source ?: 'unknown') ?>">
|
||||||
|
<?php if (preg_match('/^((https?|ftp):|)\/\//', $this->context->transport->post->source)): ?>
|
||||||
|
<a href="<?php echo $val ?>"><?php echo $val ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php echo $val ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($this->context->transport->post->imageWidth > 0): ?>
|
||||||
|
<div class="key-value dim">
|
||||||
|
<span class="key">Dimensions:</span>
|
||||||
|
<span class="value" title="<?php echo $val = sprintf('%dx%d',
|
||||||
|
$this->context->transport->post->imageWidth,
|
||||||
|
$this->context->transport->post->imageHeight) ?>">
|
||||||
|
<?php echo $val ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
<div class="key-value score">
|
<div class="key-value score">
|
||||||
<span class="key">Score:</span>
|
<span class="key">Score:</span>
|
||||||
<span class="value">
|
<span class="value">
|
||||||
<?php echo $this->context->transport->post->score ?>
|
<?php echo $this->context->transport->post->score ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::ScorePost)): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::ScorePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
[
|
[
|
||||||
<?php $scoreLink = function($score) { return \Chibi\UrlHelper::route('post', 'score', ['id' => $this->context->transport->post->id, 'score' => $score]); } ?>
|
<?php $scoreLink = function($score) { return \Chibi\UrlHelper::route('post', 'score', ['id' => $this->context->transport->post->id, 'score' => $score]); } ?>
|
||||||
|
|
||||||
@ -107,51 +154,49 @@
|
|||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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->uploadDate) ?>">
|
|
||||||
<?php echo $val ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($this->context->transport->post->imageWidth > 0): ?>
|
|
||||||
<div class="key-value dim">
|
|
||||||
<span class="key">Dimensions:</span>
|
|
||||||
<span class="value" title="<?php echo $val = sprintf('%dx%d',
|
|
||||||
$this->context->transport->post->imageWidth,
|
|
||||||
$this->context->transport->post->imageHeight) ?>">
|
|
||||||
<?php echo $val ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<?php endif ?>
|
|
||||||
|
|
||||||
<div class="key-value source">
|
|
||||||
<span class="key">Source:</span>
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
|
<div class="unit hl-options">
|
||||||
<?php if ($this->context->transport->post->type != PostType::Youtube): ?>
|
<?php if ($this->context->transport->post->type != PostType::Youtube): ?>
|
||||||
<div class="permalink">
|
<div class="hl-option">
|
||||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" title="Download">
|
<a href="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $this->context->transport->post->name]) ?>" title="Download">
|
||||||
<i class="icon-dl"></i>
|
<i class="icon-dl"></i>
|
||||||
<span class="ext">
|
<span>
|
||||||
<?php $mimes = ['image/jpeg' => 'JPG', 'image/gif' => 'GIF', 'image/png' => 'PNG', 'application/x-shockwave-flash' => 'SWF'] ?>
|
<?php
|
||||||
<?php $mime = $this->context->transport->post->mimeType ?>
|
printf(
|
||||||
<?php echo isset($mimes[$mime]) ? $mimes[$mime] : 'unknown' ?>
|
'Download %s (%s)',
|
||||||
</span>
|
strtoupper(TextHelper::resolveMimeType($this->context->transport->post->mimeType)) ?: 'Unknown',
|
||||||
<span class="size">
|
TextHelper::useBytesUnits($this->context->transport->post->fileSize));
|
||||||
<?php echo TextHelper::useBytesUnits($this->context->transport->post->fileSize) ?>
|
?>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (PrivilegesHelper::confirm(Privilege::FavoritePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||||
|
<div class="hl-option">
|
||||||
|
<?php if (!$this->context->favorite): ?>
|
||||||
|
<a class="add-fav icon simple-action" href="<?php echo \Chibi\UrlHelper::route('post', 'add-favorite', ['id' => $this->context->transport->post->id]) ?>">
|
||||||
|
<i class="icon-fav"></i>
|
||||||
|
<span>Add to favorites</span>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a class="rem-fav icon simple-action" href="<?php echo \Chibi\UrlHelper::route('post', 'rem-favorite', ['id' => $this->context->transport->post->id]) ?>">
|
||||||
|
<i class="icon-fav"></i>
|
||||||
|
<span>Remove from favorites</span>
|
||||||
|
</a>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ($canEditAnything): ?>
|
||||||
|
<div class="hl-option">
|
||||||
|
<a class="edit-post icon" href="#">
|
||||||
|
<i class="icon-edit"></i>
|
||||||
|
<span>Edit</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (count($this->context->transport->post->getFavorites()) > 0): ?>
|
<?php if (count($this->context->transport->post->getFavorites()) > 0): ?>
|
||||||
@ -170,7 +215,7 @@
|
|||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (count($this->context->transport->post->getRelations())): ?>
|
<?php if (count($this->context->transport->post->getRelations())): ?>
|
||||||
<div class="relations unit">
|
<div class="unit relations">
|
||||||
<h1>related</h1>
|
<h1>related</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($this->context->transport->post->getRelations() as $relatedPost): ?>
|
<?php foreach ($this->context->transport->post->getRelations() as $relatedPost): ?>
|
||||||
@ -185,53 +230,43 @@
|
|||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$editPostPrivileges = [
|
|
||||||
Privilege::EditPostSafety,
|
|
||||||
Privilege::EditPostTags,
|
|
||||||
Privilege::EditPostThumb,
|
|
||||||
Privilege::EditPostSource,
|
|
||||||
];
|
|
||||||
$editPostPrivileges = array_fill_keys($editPostPrivileges, false);
|
|
||||||
foreach (array_keys($editPostPrivileges) as $privilege)
|
|
||||||
{
|
|
||||||
if (PrivilegesHelper::confirm($privilege, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
|
||||||
$editPostPrivileges[$privilege] = true;
|
|
||||||
}
|
|
||||||
$canEditAnything = count(array_filter($editPostPrivileges)) > 0;
|
|
||||||
|
|
||||||
$options = [];
|
$options = [];
|
||||||
|
|
||||||
if (PrivilegesHelper::confirm(Privilege::FavoritePost))
|
if (PrivilegesHelper::confirm(Privilege::FeaturePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||||
{
|
|
||||||
if (!$this->context->favorite)
|
|
||||||
{
|
{
|
||||||
$options []=
|
$options []=
|
||||||
[
|
[
|
||||||
'class' => 'add-fav',
|
'class' => 'feature',
|
||||||
'text' => 'Add to favorites',
|
'text' => 'Feature on main page',
|
||||||
'simple-action' => \Chibi\UrlHelper::route('post', 'add-favorite', ['id' => $this->context->transport->post->id]),
|
'simple-action' => \Chibi\UrlHelper::route('post', 'feature', ['id' => $this->context->transport->post->id]),
|
||||||
|
'data-confirm-text' => 'Are you sure you want to feature this post on the main page?',
|
||||||
|
'data-redirect-url' => \Chibi\UrlHelper::route('index', 'index'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PrivilegesHelper::confirm(Privilege::FlagPost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||||
|
{
|
||||||
|
if ($this->context->flagged)
|
||||||
|
{
|
||||||
|
$options []=
|
||||||
|
[
|
||||||
|
'class' => 'flag',
|
||||||
|
'text' => 'Flagged',
|
||||||
|
'inactive' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$options []=
|
$options []=
|
||||||
[
|
[
|
||||||
'class' => 'rem-fav',
|
'class' => 'flag',
|
||||||
'text' => 'Remove from favorites',
|
'text' => 'Flag for moderator attention',
|
||||||
'simple-action' => \Chibi\UrlHelper::route('post', 'rem-favorite', ['id' => $this->context->transport->post->id]),
|
'simple-action' => \Chibi\UrlHelper::route('post', 'flag', ['id' => $this->context->transport->post->id]),
|
||||||
|
'data-confirm-text' => 'Are you sure you want to flag this post?',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($canEditAnything)
|
|
||||||
{
|
|
||||||
$options []=
|
|
||||||
[
|
|
||||||
'class' => 'edit',
|
|
||||||
'text' => 'Edit',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PrivilegesHelper::confirm(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
if (PrivilegesHelper::confirm(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||||
{
|
{
|
||||||
if ($this->context->transport->post->hidden)
|
if ($this->context->transport->post->hidden)
|
||||||
@ -254,41 +289,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PrivilegesHelper::confirm(Privilege::FeaturePost))
|
|
||||||
{
|
|
||||||
$options []=
|
|
||||||
[
|
|
||||||
'class' => 'feature',
|
|
||||||
'text' => 'Feature on main page',
|
|
||||||
'simple-action' => \Chibi\UrlHelper::route('post', 'feature', ['id' => $this->context->transport->post->id]),
|
|
||||||
'data-confirm-text' => 'Are you sure you want to feature this post on the main page?',
|
|
||||||
'data-redirect-url' => \Chibi\UrlHelper::route('index', 'index'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PrivilegesHelper::confirm(Privilege::FlagPost))
|
|
||||||
{
|
|
||||||
if ($this->context->flagged)
|
|
||||||
{
|
|
||||||
$options []=
|
|
||||||
[
|
|
||||||
'class' => 'flag',
|
|
||||||
'text' => 'Flagged',
|
|
||||||
'inactive' => true,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$options []=
|
|
||||||
[
|
|
||||||
'class' => 'flag',
|
|
||||||
'text' => 'Flag for moderator attention',
|
|
||||||
'simple-action' => \Chibi\UrlHelper::route('post', 'flag', ['id' => $this->context->transport->post->id]),
|
|
||||||
'data-confirm-text' => 'Are you sure you want to flag this post?',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PrivilegesHelper::confirm(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
if (PrivilegesHelper::confirm(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||||
{
|
{
|
||||||
$options []=
|
$options []=
|
||||||
@ -307,17 +307,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="inner-content">
|
<div id="inner-content">
|
||||||
|
<?php if ($canEditAnything): ?>
|
||||||
|
<div class="unit edit-post">
|
||||||
|
<?php $this->renderFile('post-edit') ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
<div class="post-wrapper post-type-<?php echo strtolower(PostType::toString($this->context->transport->post->type)) ?>">
|
<div class="post-wrapper post-type-<?php echo strtolower(PostType::toString($this->context->transport->post->type)) ?>">
|
||||||
<?php echo $this->renderFile('post-file-render') ?>
|
<?php echo $this->renderFile('post-file-render') ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($canEditAnything): ?>
|
<?php
|
||||||
<?php $this->renderFile('post-edit') ?>
|
CustomAssetViewDecorator::addStylesheet('comment-list.css');
|
||||||
<?php endif ?>
|
CustomAssetViewDecorator::addStylesheet('comment-small.css');
|
||||||
|
?>
|
||||||
<div class="comments-wrapper">
|
<div class="comments-wrapper">
|
||||||
<?php if (!empty($this->context->transport->post->getComments())): ?>
|
<?php if (!empty($this->context->transport->post->getComments())): ?>
|
||||||
<div class="comments unit">
|
<div class="unit comments">
|
||||||
<h1>comments (<?php echo count($this->context->transport->post->getComments()) ?>)</h1>
|
<h1>comments (<?php echo count($this->context->transport->post->getComments()) ?>)</h1>
|
||||||
<div class="comments">
|
<div class="comments">
|
||||||
<?php foreach ($this->context->transport->post->getComments() as $comment): ?>
|
<?php foreach ($this->context->transport->post->getComments() as $comment): ?>
|
||||||
@ -330,21 +336,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::AddComment)): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::AddComment)): ?>
|
||||||
<form action="<?php echo \Chibi\UrlHelper::route('comment', 'add', ['postId' => $this->context->transport->post->id]) ?>" method="post" class="add-comment aligned unit">
|
<div class="unit comment-add">
|
||||||
<h1>add comment</h1>
|
<?php $this->renderFile('comment-add') ?>
|
||||||
|
|
||||||
<div class="preview"></div>
|
|
||||||
|
|
||||||
<div class="text">
|
|
||||||
<div class="input-wrapper"><textarea name="text" cols="50" rows="3"></textarea></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="submit" value="1"/>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button name="sender" class="submit" type="submit" value="preview">Preview</button>
|
|
||||||
<button name="sender" class="submit" type="submit" value="submit">Submit</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,4 +42,3 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
<?php $tabs = [] ?>
|
<?php
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::ListTags)) $tabs['list'] = 'List'; ?>
|
CustomAssetViewDecorator::setSubTitle('tags');
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::RenameTags)) $tabs['rename'] = 'Rename'; ?>
|
CustomAssetViewDecorator::addStylesheet('tag-list.css');
|
||||||
<?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): ?>
|
$tabs = [];
|
||||||
|
if (PrivilegesHelper::confirm(Privilege::ListTags)) $tabs['list'] = 'List';
|
||||||
|
if (PrivilegesHelper::confirm(Privilege::RenameTags)) $tabs['rename'] = 'Rename';
|
||||||
|
if (PrivilegesHelper::confirm(Privilege::MergeTags)) $tabs['merge'] = 'Merge';
|
||||||
|
if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs['mass-tag-redirect'] = 'Mass tag';
|
||||||
|
$showTabs = count($tabs) > 1;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($showTabs): ?>
|
||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($tabs as $tab => $name): ?>
|
<?php foreach ($tabs as $tab => $name): ?>
|
||||||
@ -20,6 +26,8 @@
|
|||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if ($this->context->route->simpleActionName == 'merge'): ?>
|
<?php if ($this->context->route->simpleActionName == 'merge'): ?>
|
||||||
@ -37,3 +45,7 @@
|
|||||||
<?php if ($this->context->route->simpleActionName == 'mass-tag-redirect'): ?>
|
<?php if ($this->context->route->simpleActionName == 'mass-tag-redirect'): ?>
|
||||||
<?php $this->renderFile('tag-mass-tag') ?>
|
<?php $this->renderFile('tag-mass-tag') ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ($showTabs): ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
<nav class="sort-styles">
|
<nav class="sort-styles">
|
||||||
<ul>
|
<ul>
|
||||||
<?php
|
<?php
|
||||||
$sortStyles =
|
$filters =
|
||||||
[
|
[
|
||||||
'order:alpha,asc' => 'Sort A→Z',
|
'order:alpha,asc' => 'Sort A→Z',
|
||||||
'order:alpha,desc' => 'Sort Z→A',
|
'order:alpha,desc' => 'Sort Z→A',
|
||||||
'order:popularity,desc' => 'Often used first',
|
'order:popularity,desc' => 'Often used first',
|
||||||
'order:popularity,asc' => 'Rarely used first',
|
'order:popularity,asc' => 'Rarely used first',
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->config->registration->staffActivation)
|
|
||||||
$sortStyles['pending'] = 'Pending staff review';
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<?php foreach ($sortStyles as $key => $text): ?>
|
<?php foreach ($filters as $key => $text): ?>
|
||||||
<?php if ($this->context->filter == $key): ?>
|
<?php if ($this->context->filter == $key): ?>
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
@ -28,11 +25,11 @@
|
|||||||
<?php if (empty($this->context->transport->tags)): ?>
|
<?php if (empty($this->context->transport->tags)): ?>
|
||||||
<p class="alert alert-warning">No tags to show.</p>
|
<p class="alert alert-warning">No tags to show.</p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php $max = max([0]+array_map(function($x) { return $x['post_count']; }, $this->context->transport->tags)); ?>
|
<?php $max = $this->context->highestUsage ?>
|
||||||
<?php $add = 0. ?>
|
<?php $add = 0. ?>
|
||||||
<?php $mul = 10. / max(1, log(max(1, $max))) ?>
|
<?php $mul = 10. / max(1, log(max(1, $max))) ?>
|
||||||
<?php $url = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']) ?>
|
<?php $url = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']) ?>
|
||||||
<div class="tags">
|
<div class="tags paginator-content">
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($this->context->transport->tags as $tag): ?>
|
<?php foreach ($this->context->transport->tags as $tag): ?>
|
||||||
<?php $name = $tag['name'] ?>
|
<?php $name = $tag['name'] ?>
|
||||||
@ -45,4 +42,6 @@
|
|||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php $this->renderFile('paginator') ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
<div class="form-wrapper">
|
<div class="form-wrapper">
|
||||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'mass-tag-redirect') ?>">
|
<form method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'mass-tag-redirect') ?>">
|
||||||
<h1>mass tag</h1>
|
<h1>mass tag</h1>
|
||||||
<div>
|
|
||||||
<label class="left" for="mass-tag-query">Search query:</label>
|
<div class="form-row">
|
||||||
<div class="input-wrapper"><input class="autocomplete" 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>
|
<label for="mass-tag-query">Search query:</label>
|
||||||
|
<div class="input-wrapper"><input class="autocomplete" type="text" name="query" id="mass-tag-query" value="<?php echo isset($this->context->massTagQuery) ? htmlspecialchars($this->context->massTagQuery) : '' ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left" for="mass-tag-tag">Tag:</label>
|
<label for="mass-tag-tag">Tag:</label>
|
||||||
<div class="input-wrapper"><input class="autocomplete" type="text" name="tag" id="mass-tag-tag" value="<?php echo isset($this->context->massTagTag) ? $this->context->massTagTag : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
<div class="input-wrapper"><input class="autocomplete" type="text" name="tag" id="mass-tag-tag" value="<?php echo isset($this->context->massTagTag) ? htmlspecialchars($this->context->massTagTag) : '' ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="old-page" value="<?php echo isset($this->context->transport->paginator) ? htmlspecialchars($this->context->transport->paginator->page) : '' ?>"/>
|
||||||
|
<input type="hidden" name="old-query" value="<?php echo isset($this->context->massTagQuery) ? htmlspecialchars($this->context->massTagQuery) : '' ?>"/>
|
||||||
<input type="hidden" name="submit" value="1"/>
|
<input type="hidden" name="submit" value="1"/>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left"> </label>
|
<label></label>
|
||||||
<button class="submit" type="submit">Tag!</button>
|
<button class="submit" type="submit">Tag!</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
<div class="form-wrapper">
|
<div class="form-wrapper">
|
||||||
<form class="aligned" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'merge') ?>">
|
<form method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'merge') ?>">
|
||||||
<h1>merge tags</h1>
|
<h1>merge tags</h1>
|
||||||
<div>
|
|
||||||
<label class="left" for="merge-source-tag">Source tag:</label>
|
<div class="form-row">
|
||||||
<div class="input-wrapper"><input class="autocomplete" type="text" name="source-tag" id="merge-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
<label for="merge-source-tag">Source tag:</label>
|
||||||
|
<div class="input-wrapper"><input class="autocomplete" type="text" name="source-tag" id="merge-source-tag"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left" for="merge-target-tag">Target tag:</label>
|
<label for="merge-target-tag">Target tag:</label>
|
||||||
<div class="input-wrapper"><input class="autocomplete" type="text" name="target-tag" id="merge-target-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
<div class="input-wrapper"><input class="autocomplete" type="text" name="target-tag" id="merge-target-tag"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="submit" value="1"/>
|
<input type="hidden" name="submit" value="1"/>
|
||||||
|
|
||||||
<div>
|
<?php $this->renderFile('message') ?>
|
||||||
<label class="left"> </label>
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label></label>
|
||||||
<button class="submit" type="submit">Merge!</button>
|
<button class="submit" type="submit">Merge!</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
<div class="form-wrapper">
|
<div class="form-wrapper">
|
||||||
<form class="aligned simple-action" method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'rename') ?>">
|
<form method="post" action="<?php echo \Chibi\UrlHelper::route('tag', 'rename') ?>">
|
||||||
<h1>rename tags</h1>
|
<h1>rename tags</h1>
|
||||||
<div>
|
|
||||||
<label class="left" for="rename-source-tag">Source tag:</label>
|
<div class="form-row">
|
||||||
<div class="input-wrapper"><input class="autocomplete" type="text" name="source-tag" id="rename-source-tag" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
<label for="rename-source-tag">Source tag:</label>
|
||||||
|
<div class="input-wrapper"><input class="autocomplete" type="text" name="source-tag" id="rename-source-tag"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left" for="rename-target-tag">Target tag:</label>
|
<label for="rename-target-tag">Target tag:</label>
|
||||||
<div class="input-wrapper"><input type="text" name="target-tag" id="rename-target-tag"/></div>
|
<div class="input-wrapper"><input type="text" name="target-tag" id="rename-target-tag"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="submit" value="1"/>
|
<input type="hidden" name="submit" value="1"/>
|
||||||
|
|
||||||
<div>
|
<?php $this->renderFile('message') ?>
|
||||||
<label class="left"> </label>
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label></label>
|
||||||
<button class="submit" type="submit">Rename!</button>
|
<button class="submit" type="submit">Rename!</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
|
|
||||||
<li class="search">
|
<li class="search">
|
||||||
<form name="search" action="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>" method="get">
|
<form name="search" action="<?php echo \Chibi\UrlHelper::route('post', 'list') ?>" method="get">
|
||||||
<input class="autocomplete" type="search" name="query" placeholder="Search…" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/>
|
<input class="autocomplete" type="search" name="query" placeholder="Search…" value="<?php echo isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '' ?>"/>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name]) ?>" method="post" class="delete aligned confirmable" autocomplete="off" data-confirm-text="Are you sure you want to delete your account?">
|
<form action="<?php echo \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name]) ?>" method="post" class="delete confirmable" 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): ?>
|
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
|
||||||
<div class="current-password">
|
<div class="form-row current-password">
|
||||||
<label class="left" for="current-password">Current password:</label>
|
<label for="current-password">Current password:</label>
|
||||||
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
<?php $this->renderFile('message') ?>
|
<?php $this->renderFile('message') ?>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left"> </label>
|
<label></label>
|
||||||
<button class="submit" type="submit">Delete account</button>
|
<button class="submit" type="submit">Delete account</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'edit', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit aligned" autocomplete="off">
|
<form action="<?php echo \Chibi\UrlHelper::route('user', 'edit', ['name' => $this->context->transport->user->name]) ?>" method="post" class="edit" autocomplete="off">
|
||||||
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
|
<?php if ($this->context->user->id == $this->context->transport->user->id): ?>
|
||||||
<div class="current-password">
|
<div class="form-row current-password">
|
||||||
<label class="left" for="current-password">Current password:</label>
|
<label for="current-password">Current password:</label>
|
||||||
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
<div class="input-wrapper"><input type="password" name="current-password" id="current-password" placeholder="Current password"/></div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||||
<div class="nickname">
|
<div class="form-row nickname">
|
||||||
<label class="left" for="name">Name:</label>
|
<label 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 class="input-wrapper"><input type="text" name="name" id="name" placeholder="New name…" value="<?php echo htmlspecialchars($this->context->suppliedName) ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||||
<div class="email">
|
<div class="form-row email">
|
||||||
<label class="left" for="name">E-mail:</label>
|
<label 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 class="input-wrapper"><input type="text" name="email" id="email" placeholder="New e-mail…" value="<?php echo htmlspecialchars($this->context->suppliedEmail) ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||||
<div class="password1">
|
<div class="form-row password1">
|
||||||
<label class="left" for="password1">New password:</label>
|
<label 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 class="input-wrapper"><input type="password" name="password1" id="password1" placeholder="New password…" value="<?php echo htmlspecialchars($this->context->suppliedPassword1) ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="password2">
|
<div class="form-row password2">
|
||||||
<label class="left" for="password2"></label>
|
<label 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 class="input-wrapper"><input type="password" name="password2" id="password2" placeholder="New password… (repeat)" value="<?php echo htmlspecialchars($this->context->suppliedPassword2) ?>"/></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||||
<div class="access-rank">
|
<div class="form-row access-rank">
|
||||||
<label class="left" for="access-rank">Access rank:</label>
|
<label for="access-rank">Access rank:</label>
|
||||||
<div class="input-wrapper"><select name="access-rank" id="access-rank">
|
<div class="input-wrapper"><select name="access-rank" id="access-rank">
|
||||||
<?php foreach (AccessRank::getAll() as $rank): ?>
|
<?php foreach (AccessRank::getAll() as $rank): ?>
|
||||||
<?php if ($rank == AccessRank::Nobody) continue ?>
|
<?php if ($rank == AccessRank::Nobody) continue ?>
|
||||||
@ -54,8 +54,8 @@
|
|||||||
|
|
||||||
<?php $this->renderFile('message') ?>
|
<?php $this->renderFile('message') ?>
|
||||||
|
|
||||||
<div>
|
<div class="form-row">
|
||||||
<label class="left"> </label>
|
<label></label>
|
||||||
<button class="submit" type="submit">Submit</button>
|
<button class="submit" type="submit">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user