mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
70 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 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -4,3 +4,6 @@
|
||||
[submodule "php-markdown"]
|
||||
path = lib/php-markdown
|
||||
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]
|
||||
prettyPrint=1
|
||||
enableCache=1
|
||||
|
||||
[main]
|
||||
dbDriver = "sqlite"
|
||||
@ -31,6 +31,7 @@ paths[privacy]=./data/privacy.md
|
||||
usersPerPage=8
|
||||
postsPerPage=20
|
||||
logsPerPage=250
|
||||
tagsPerPage=100
|
||||
thumbWidth=150
|
||||
thumbHeight=150
|
||||
thumbStyle=outside
|
||||
@ -43,7 +44,8 @@ maxRelatedPosts=50
|
||||
[comments]
|
||||
minLength = 5
|
||||
maxLength = 2000
|
||||
commentsPerPage = 20
|
||||
commentsPerPage = 10
|
||||
maxCommentsInList = 5
|
||||
|
||||
[registration]
|
||||
staffActivation = 0
|
||||
@ -85,12 +87,11 @@ editPostThumb=moderator
|
||||
editPostSource=moderator
|
||||
editPostRelations.own=registered
|
||||
editPostRelations.all=moderator
|
||||
editPostFile.all=moderator
|
||||
editPostFile.own=moderator
|
||||
hidePost.own=moderator
|
||||
hidePost.all=moderator
|
||||
deletePost.own=moderator
|
||||
deletePost.all=moderator
|
||||
editPostFile=moderator
|
||||
massTag.own=registered
|
||||
massTag.all=power-user
|
||||
hidePost=moderator
|
||||
deletePost=moderator
|
||||
featurePost=moderator
|
||||
scorePost=registered
|
||||
flagPost=registered
|
||||
@ -124,7 +125,6 @@ editComment.all=admin
|
||||
listTags=anonymous
|
||||
mergeTags=moderator
|
||||
renameTags=moderator
|
||||
massTag=moderator
|
||||
|
||||
listLogs=moderator
|
||||
viewLog=moderator
|
||||
|
Submodule lib/chibi-core updated: 9653960e23...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;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
form.auth label.left {
|
||||
#content form label {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
form.auth p {
|
||||
#content form p {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
form.auth .help {
|
||||
#content form .help {
|
||||
opacity: .5;
|
||||
margin-top: 1em;
|
||||
font-size: small;
|
||||
}
|
||||
form.auth .help p {
|
||||
#content form .help p {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
form.auth .help label+div {
|
||||
#content form .help label+div {
|
||||
float: left;
|
||||
}
|
||||
form.auth .help ul {
|
||||
#content form .help ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -26,3 +26,8 @@
|
||||
.small-screen .comment-group .comments {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.hellip {
|
||||
margin-bottom: 2em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -22,11 +22,8 @@
|
||||
.comment {
|
||||
clear: left;
|
||||
}
|
||||
.comment .date:before {
|
||||
content: ' on ';
|
||||
margin: 0 0.2em;
|
||||
}
|
||||
.comment .date {
|
||||
margin: 0 0.2em 0 0.75em;
|
||||
color: silver;
|
||||
}
|
||||
|
||||
@ -50,3 +47,10 @@
|
||||
.comment .delete a {
|
||||
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;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
font-family: 'Droid Sans', sans-serif;
|
||||
font-size: 12pt;
|
||||
}
|
||||
@ -33,7 +35,8 @@ body {
|
||||
}
|
||||
|
||||
.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:hover {
|
||||
color: firebrick;
|
||||
border-bottom: 3px solid firebrick;
|
||||
color: hsl(0,70%,45%);
|
||||
border-bottom: 3px solid hsl(0,70%,45%);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -85,11 +88,10 @@ body {
|
||||
}
|
||||
#top-nav li.search input {
|
||||
border: 0;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
padding: 4px 10px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
padding: 0 10px;
|
||||
margin: 0;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
#top-nav li.safety {
|
||||
@ -131,11 +133,9 @@ body {
|
||||
|
||||
|
||||
|
||||
footer {
|
||||
footer .main-wrapper {
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
padding-top: 0.5em;
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 1em;
|
||||
font-size: small;
|
||||
color: silver;
|
||||
}
|
||||
@ -151,11 +151,16 @@ footer a {
|
||||
|
||||
#sidebar {
|
||||
float: left;
|
||||
width: 256px;
|
||||
margin-right: 1em;
|
||||
width: 240px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
#sidebar h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#sidebar+#inner-content {
|
||||
margin-left: 255px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@ -169,23 +174,11 @@ footer a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#inner-content {
|
||||
overflow: hidden;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.unit {
|
||||
padding: 1em;
|
||||
border: 1px solid #eee;
|
||||
margin: 1em 0;
|
||||
margin: 2.5em 0;
|
||||
}
|
||||
#inner-content .unit {
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
#sidebar .unit {
|
||||
border-left: 0;
|
||||
padding-left: 0;
|
||||
#sidebar .unit:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
#small-screen { display: none; }
|
||||
|
||||
@ -195,14 +188,10 @@ footer a {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
body #sidebar .unit {
|
||||
border: 1px solid #eee;
|
||||
border-bottom: 0;
|
||||
padding: 1em 1em 0 1em;
|
||||
}
|
||||
#inner-content {
|
||||
float: none;
|
||||
width: auto;
|
||||
margin-left: 0;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +213,7 @@ hr {
|
||||
}
|
||||
|
||||
a {
|
||||
color: firebrick;
|
||||
color: hsl(0,70%,45%);
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
}
|
||||
@ -239,7 +228,7 @@ i[class*='icon-'] {
|
||||
display: inline-block;
|
||||
}
|
||||
a i[class*='icon-'] {
|
||||
background-color: firebrick;
|
||||
background-color: hsl(0,70%,45%);
|
||||
}
|
||||
a:focus i[class*='icon-'],
|
||||
a:hover i[class*='icon-'] {
|
||||
@ -248,42 +237,38 @@ a:hover i[class*='icon-'] {
|
||||
|
||||
|
||||
|
||||
form.aligned input,
|
||||
form.aligned button {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
form.aligned label {
|
||||
.form-row>label {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
}
|
||||
form.aligned label.left {
|
||||
display: inline-block;
|
||||
padding-right: 1em;
|
||||
width: 5em;
|
||||
width: 7em;
|
||||
min-height: 1em;
|
||||
float: left;
|
||||
}
|
||||
form.aligned>div {
|
||||
margin-bottom: 0.5em;
|
||||
clear: left;
|
||||
}
|
||||
form.aligned label,
|
||||
form.aligned input,
|
||||
form.aligned select,
|
||||
form.aligned button {
|
||||
label,
|
||||
input:not([type=radio]):not([type=checkbox]):not([type=file]),
|
||||
select,
|
||||
button {
|
||||
-webkit-box-sizing: border-box !important;
|
||||
-moz-box-sizing: border-box !important;
|
||||
box-sizing: border-box !important;
|
||||
vertical-align: middle;
|
||||
line-height: 20px;
|
||||
line-height: 24px;
|
||||
height: 34px;
|
||||
}
|
||||
form.aligned label,
|
||||
form.aligned input,
|
||||
form.aligned select {
|
||||
label,
|
||||
input,
|
||||
select {
|
||||
padding: 5px;
|
||||
font-family: inherit;
|
||||
font-size: 11pt;
|
||||
}
|
||||
form.aligned input[type=file] {
|
||||
input[type=file] {
|
||||
padding: 5px 0;
|
||||
}
|
||||
form.aligned input[type=radio],
|
||||
form.aligned input[type=checkbox] {
|
||||
input[type=radio],
|
||||
input[type=checkbox] {
|
||||
width: auto;
|
||||
max-width: auto;
|
||||
margin: 0 10px 0 0;
|
||||
@ -291,60 +276,58 @@ form.aligned input[type=checkbox] {
|
||||
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 {
|
||||
overflow: hidden;
|
||||
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,
|
||||
select,
|
||||
textarea,
|
||||
input:not([type=radio]):not([type=checkbox]):not([type=file]) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
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 {
|
||||
border: 0 !important;
|
||||
}
|
||||
button {
|
||||
font-size: 115%;
|
||||
padding: 0.2em 0.7em;
|
||||
color: white;
|
||||
background: cornflowerblue;
|
||||
border: 0;
|
||||
}
|
||||
button:hover {
|
||||
background-color: royalblue;
|
||||
cursor: pointer;
|
||||
line-height: auto !important;
|
||||
height: auto !important;
|
||||
margin: -4px 0 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tabs ul {
|
||||
list-style-type: none;
|
||||
margin: -4px 0 1em 0;
|
||||
margin: 0 0 1em 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-bottom: 3px solid #eee;
|
||||
}
|
||||
.tabs li {
|
||||
display: inline-block;
|
||||
@ -353,22 +336,22 @@ button:hover {
|
||||
.tabs li a {
|
||||
display: inline-block;
|
||||
padding: 0.5em 1em;
|
||||
margin: 5px 0 -1px 0;
|
||||
vertical-align: middle;
|
||||
border: 1px none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border: 3px solid rgba(238, 238, 238, 0);
|
||||
border-bottom: 3px solid #eee;
|
||||
color: silver;
|
||||
margin: 0 0 -3px 0;
|
||||
}
|
||||
.tabs li.selected a {
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
border: 3px solid #eee;
|
||||
border-bottom-color: rgba(238, 238, 238, 0);
|
||||
color: inherit;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.tabs li a:hover,
|
||||
.tabs li a:focus {
|
||||
color: firebrick;
|
||||
color: hsl(0,70%,45%);
|
||||
}
|
||||
|
||||
|
||||
@ -379,7 +362,7 @@ button:hover {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
max-width: 500px;
|
||||
margin: 2em auto !important;
|
||||
margin: 2em auto;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
@ -405,15 +388,7 @@ button:hover {
|
||||
clear: both;
|
||||
height: 1px; /* ghost top margin in firefox */
|
||||
width: 100%;
|
||||
margin: 0 0 -1px 0;
|
||||
}
|
||||
|
||||
pre.debug {
|
||||
margin-left: 1em;
|
||||
text-align: left;
|
||||
color: black;
|
||||
white-space: normal;
|
||||
text-indent: -1em;
|
||||
margin: -1px 0 0 0;
|
||||
}
|
||||
|
||||
.spoiler:before,
|
||||
@ -450,3 +425,9 @@ blockquote>*:first-child {
|
||||
blockquote>*:last-child {
|
||||
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 {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
@ -16,16 +16,15 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#content {
|
||||
#content .main-wrapper>* {
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
min-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
.small-screen #content {
|
||||
@media only screen and (max-width:700px) {
|
||||
#content .main-wrapper>* {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
#content .body {
|
||||
@ -45,7 +44,7 @@
|
||||
#content .footer {
|
||||
font-size: small;
|
||||
color: dimgray;
|
||||
margin: 0.5em 0 3em 0;
|
||||
margin: 0.5em auto 3em auto;
|
||||
}
|
||||
#content .footer .left {
|
||||
float: left;
|
||||
|
@ -4,8 +4,7 @@
|
||||
|
||||
#content input {
|
||||
margin: 0 1em;
|
||||
height: 25px;
|
||||
vertical-align: middle;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
pre {
|
||||
|
@ -34,6 +34,6 @@
|
||||
|
||||
.paginator li a:focus,
|
||||
.paginator li a:hover {
|
||||
border: 1px solid firebrick;
|
||||
border: 1px solid hsl(0,70%,50%);
|
||||
background: pink;
|
||||
}
|
||||
|
@ -1,28 +1,29 @@
|
||||
.post {
|
||||
margin: 0.5em;
|
||||
margin: 8px;
|
||||
}
|
||||
.posts-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
.posts {
|
||||
margin: 0 auto;
|
||||
margin: -8px auto 0 auto;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.small-screen .form-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
form.aligned {
|
||||
#content form {
|
||||
margin: 0 auto;
|
||||
width: 24em;
|
||||
text-align: left;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
#content form label {
|
||||
width: 9em;
|
||||
}
|
||||
form h1 {
|
||||
#content form h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@
|
||||
|
||||
.post .link:focus,
|
||||
.post .link:hover {
|
||||
border: 1px solid firebrick;
|
||||
border: 1px solid hsl(0,70%,50%);
|
||||
box-shadow: 0.25em 0.25em pink;
|
||||
}
|
||||
.post .link:focus img.thumb,
|
||||
@ -83,7 +83,7 @@
|
||||
}
|
||||
|
||||
.post .info-bar:before {
|
||||
border-top: 1px solid firebrick;
|
||||
border-top: 1px solid hsl(0,70%,50%);
|
||||
margin-bottom: -1px;
|
||||
content: '';
|
||||
display: block;
|
||||
|
@ -8,13 +8,10 @@
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tab {
|
||||
margin-bottom: 1em;
|
||||
#upload-step1 {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
.tab.url {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-handler-wrapper {
|
||||
display: table;
|
||||
width: 100%;
|
||||
@ -30,13 +27,21 @@
|
||||
}
|
||||
#file-handler.active {
|
||||
background: #eee;
|
||||
border-color: firebrick;
|
||||
border-color: hsl(0,70%,50%);
|
||||
}
|
||||
|
||||
#url-handler textarea {
|
||||
width: 100%;
|
||||
height: 10em;
|
||||
margin-bottom: 0.5em;
|
||||
#url-handler {
|
||||
margin-top: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
#url-handler .input-wrapper {
|
||||
margin-right: 8.5em;
|
||||
}
|
||||
#url-handler button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.post .thumbnail {
|
||||
@ -109,19 +114,6 @@
|
||||
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 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -129,7 +121,7 @@
|
||||
white-space: pre;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 33px;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.safety-safe {
|
||||
@ -149,10 +141,30 @@ ul.tagit {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.submit-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
#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 {
|
||||
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;
|
||||
}
|
||||
|
||||
#sidebar .uploader img {
|
||||
vertical-align: middle;
|
||||
margin: 0 0.5em 0 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=16');
|
||||
#sidebar .uploader .date {
|
||||
font-size: 9pt !important;
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
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 {
|
||||
color: #43aa43;
|
||||
@ -80,17 +91,19 @@ embed {
|
||||
|
||||
i.icon-prev {
|
||||
background-position: -12px -1px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
i.icon-next {
|
||||
background-position: -1px -1px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
i.icon-prev,
|
||||
i.icon-next {
|
||||
margin: 0 8px;
|
||||
vertical-align: middle;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
i.icon-dl {
|
||||
margin: 0;
|
||||
width: 20px;
|
||||
@ -98,14 +111,33 @@ i.icon-dl {
|
||||
background-position: -22px -1px;
|
||||
}
|
||||
|
||||
.permalink {
|
||||
margin: 1em 0;
|
||||
i.icon-edit {
|
||||
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;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.permalink span {
|
||||
.hl-option span {
|
||||
padding-left: 0.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.permalink .ext:after {
|
||||
@ -133,11 +165,27 @@ i.icon-dl {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
form.edit-post .safety label:not(.left) {
|
||||
margin-right: 0.75em;
|
||||
.unit.edit-post ul.tagit,
|
||||
.unit.edit-post input:not([type=file]) {
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
.unit.edit-post ul.tagit input {
|
||||
background: transparent;
|
||||
}
|
||||
ul.tagit {
|
||||
display: block;
|
||||
|
@ -17,22 +17,15 @@
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
width: 50%;
|
||||
max-width: 24em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
.small-screen .form-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
form.aligned {
|
||||
text-align: left;
|
||||
margin: 0 auto;
|
||||
#content form label {
|
||||
width: 9em;
|
||||
}
|
||||
form.aligned label.left {
|
||||
width: 7em;
|
||||
}
|
||||
form h1 {
|
||||
#content form h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -61,5 +54,5 @@ nav.sort-styles li {
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
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 {
|
||||
list-style-type: none;
|
||||
margin: 0 0 2.5em 0;
|
||||
@ -35,5 +11,41 @@ nav.sort-styles li {
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
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 {
|
||||
width: 220px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
@ -13,22 +12,12 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form.settings label.left,
|
||||
form.delete label.left,
|
||||
form.edit label.left {
|
||||
width: 9em;
|
||||
#content form {
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
form.settings .alert,
|
||||
form.delete .alert,
|
||||
form.edit .alert {
|
||||
#content form label {
|
||||
width: 10em;
|
||||
}
|
||||
#content form .alert {
|
||||
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 |
@ -3,12 +3,13 @@ $(function()
|
||||
function onDomUpdate()
|
||||
{
|
||||
$('form.edit-comment textarea, form.add-comment textarea')
|
||||
.bind('change keyup', function(e)
|
||||
.bindOnce('exit-confirmation', 'change keyp', function(e)
|
||||
{
|
||||
enableExitConfirmation();
|
||||
});
|
||||
|
||||
$('form.edit-comment, form.add-comment').submit(function(e)
|
||||
$('form.edit-comment, form.add-comment')
|
||||
.bindOnce('comment-submit', 'submit', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
rememberLastSearchQuery();
|
||||
@ -93,19 +94,30 @@ $(function()
|
||||
$.ajax(ajaxData);
|
||||
});
|
||||
|
||||
$('.comment .edit a').click(function(e)
|
||||
$('.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)
|
||||
{
|
||||
commentDom.find('form.edit-comment').remove();
|
||||
var otherForm = $(data).find('form.edit-comment');
|
||||
otherForm.hide();
|
||||
commentDom.find('.body').append(otherForm);
|
||||
otherForm.slideDown();
|
||||
$('body').trigger('dom-update');
|
||||
formDom = commentDom.find('form.edit-comment');
|
||||
cb(formDom);
|
||||
});
|
||||
}
|
||||
else
|
||||
cb(formDom);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ function setCookie(name, value, exdays)
|
||||
|
||||
function getCookie(name)
|
||||
{
|
||||
console.log(document.cookie);
|
||||
var value = document.cookie;
|
||||
var start = value.indexOf(' ' + name + '=');
|
||||
|
||||
@ -38,6 +37,17 @@ $.fn.hasAttr = function(name)
|
||||
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
|
||||
@ -83,12 +93,12 @@ $(function()
|
||||
}
|
||||
}
|
||||
|
||||
$('form.confirmable').submit(confirmEvent);
|
||||
$('a.confirmable').click(confirmEvent);
|
||||
$('form.confirmable').bindOnce('confirmation', 'submit', confirmEvent);
|
||||
$('a.confirmable').bindOnce('confirmation', 'click', confirmEvent);
|
||||
|
||||
|
||||
//simple action buttons
|
||||
$('a.simple-action').click(function(e)
|
||||
$('a.simple-action').bindOnce('simple-action', 'click', function(e)
|
||||
{
|
||||
if (e.isPropagationStopped())
|
||||
return;
|
||||
@ -125,7 +135,7 @@ $(function()
|
||||
//attach data from submit buttons to forms before .submit() gets called
|
||||
$('.submit').each(function()
|
||||
{
|
||||
$(this).click(function()
|
||||
$(this).bindOnce('submit-faux-input', 'click', function()
|
||||
{
|
||||
var form = $(this).closest('form');
|
||||
form.find('.faux-submit').remove();
|
||||
@ -157,14 +167,30 @@ $(function()
|
||||
{
|
||||
$(window).resize(function()
|
||||
{
|
||||
fixSize();
|
||||
if ($('body').width() == $('body').data('last-width'))
|
||||
return;
|
||||
$('body').data('last-width', $('body').width());
|
||||
$('body').trigger('dom-update');
|
||||
});
|
||||
$('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
|
||||
@ -178,6 +204,25 @@ function extractLast(term)
|
||||
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()
|
||||
{
|
||||
$('.autocomplete').each(function()
|
||||
@ -189,10 +234,7 @@ $(function()
|
||||
{
|
||||
var term = extractLast(request.term);
|
||||
if (term != '')
|
||||
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term + ' order:popularity,desc'}, function(data)
|
||||
{
|
||||
response($.map(data.tags, function(tag) { return { label: tag.name + ' (' + tag.count + ')', value: tag.name }; }));
|
||||
});
|
||||
retrieveTags(term, response);
|
||||
},
|
||||
focus: function(e)
|
||||
{
|
||||
@ -230,34 +272,34 @@ $(function()
|
||||
});
|
||||
});
|
||||
|
||||
function getTagItOptions()
|
||||
function attachTagIt(element)
|
||||
{
|
||||
var tagItOptions =
|
||||
{
|
||||
return {
|
||||
caseSensitive: false,
|
||||
autocomplete:
|
||||
{
|
||||
source:
|
||||
function(request, response)
|
||||
{
|
||||
var term = request.term.toLowerCase();
|
||||
var tags = $.map(this.options.availableTags, function(a)
|
||||
var tagit = this;
|
||||
retrieveTags(request.term.toLowerCase(), function(tags)
|
||||
{
|
||||
return a.name;
|
||||
});
|
||||
var results = $.grep(tags, function(a)
|
||||
if (!tagit.options.allowDuplicates)
|
||||
{
|
||||
if (term.length < 3)
|
||||
return a.toLowerCase().indexOf(term) == 0;
|
||||
else
|
||||
return a.toLowerCase().indexOf(term) != -1;
|
||||
tags = $.grep(tags, function(tag)
|
||||
{
|
||||
return tagit.assignedTags().indexOf(tag.value) == -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);
|
||||
}
|
||||
|
||||
|
||||
@ -279,7 +321,7 @@ function enableExitConfirmation()
|
||||
{
|
||||
$(window).bind('beforeunload', function(e)
|
||||
{
|
||||
return true;
|
||||
return 'There are unsaved changes.';
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,9 @@ $(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.stopPropagation();
|
||||
|
||||
var aDom = $(this);
|
||||
if (aDom.hasClass('inactive'))
|
||||
|
@ -6,14 +6,8 @@ $(function()
|
||||
var className = $(this).parents('li').attr('class').replace('selected', '').replace(/^\s+|\s+$/, '');
|
||||
$('.tabs li').removeClass('selected');
|
||||
$(this).parents('li').addClass('selected');
|
||||
$('.tab').hide();
|
||||
$('.tab.' + className).show();
|
||||
});
|
||||
|
||||
var tags = [];
|
||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
||||
{
|
||||
tags = data['tags'];
|
||||
$('.tab-content').hide();
|
||||
$('.tab-content.' + className).show();
|
||||
});
|
||||
|
||||
$('#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)
|
||||
{
|
||||
var urls = [];
|
||||
$.each($('#url-handler-wrapper textarea').val().split(/\s+/), function(i, url)
|
||||
{
|
||||
var url = $('#url-handler-wrapper input').val();
|
||||
url = url.replace(/^\s+|\s+$/, '');
|
||||
if (url == '')
|
||||
return;
|
||||
urls.push(url);
|
||||
});
|
||||
$('#url-handler-wrapper textarea').val('');
|
||||
handleURLs(urls);
|
||||
$('#url-handler-wrapper input').val('');
|
||||
handleURLs([url]);
|
||||
});
|
||||
|
||||
|
||||
@ -93,7 +91,6 @@ $(function()
|
||||
|
||||
var postDom = posts.first();
|
||||
var url = postDom.find('form').attr('action') + '?json';
|
||||
console.log(postDom.find('form').get(0));
|
||||
var fd = new FormData(postDom.find('form').get(0));
|
||||
|
||||
fd.append('file', postDom.data('file'));
|
||||
@ -183,8 +180,7 @@ $(function()
|
||||
{
|
||||
return function(e)
|
||||
{
|
||||
img.css('background-image', 'none');
|
||||
img.attr('src', e.target.result);
|
||||
changeThumb(img, e.target.result);
|
||||
};
|
||||
})(file, img);
|
||||
reader.readAsDataURL(file);
|
||||
@ -192,6 +188,14 @@ $(function()
|
||||
});
|
||||
}
|
||||
|
||||
function changeThumb(img, url)
|
||||
{
|
||||
$(img)
|
||||
.css('background-image', 'none')
|
||||
.attr('src', url)
|
||||
.data('custom-thumb', true);
|
||||
}
|
||||
|
||||
function handleURLs(urls)
|
||||
{
|
||||
handleInputs(urls, function(postDom, url)
|
||||
@ -205,18 +209,13 @@ $(function()
|
||||
{
|
||||
postDom.find('.file-name strong')
|
||||
.text(data.data.title);
|
||||
postDom.find('img')
|
||||
.css('background-image', 'none')
|
||||
.attr('src', data.data.thumbnail.hqDefault);
|
||||
changeThumb(postDom.find('img'), data.data.thumbnail.hqDefault);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
postDom.find('.file-name strong')
|
||||
.text(url);
|
||||
postDom.find('img')
|
||||
.css('background-image', 'none')
|
||||
.attr('src', url);
|
||||
postDom.find('.file-name strong').text(url);
|
||||
changeThumb(postDom.find('img'), url);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -225,7 +224,6 @@ $(function()
|
||||
{
|
||||
for (var i = 0; i < inputs.length; i ++)
|
||||
{
|
||||
enableExitConfirmation();
|
||||
var input = inputs[i];
|
||||
var postDom = $('#post-template').clone(true);
|
||||
postDom.find('form').submit(false);
|
||||
@ -234,16 +232,39 @@ $(function()
|
||||
$('.posts').append(postDom);
|
||||
|
||||
postDom.show();
|
||||
var tagItOptions = getTagItOptions();
|
||||
tagItOptions.availableTags = tags;
|
||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
||||
$('.tags input', postDom).tagit(tagItOptions);
|
||||
attachTagIt($('.tags input', postDom));
|
||||
|
||||
callback(postDom, input);
|
||||
}
|
||||
if ($('.posts .post').length == 0)
|
||||
{
|
||||
disableExitConfirmation();
|
||||
$('#upload-step2').fadeOut();
|
||||
}
|
||||
else
|
||||
{
|
||||
enableExitConfirmation();
|
||||
$('#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();
|
||||
});
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ $(function()
|
||||
{
|
||||
function onDomUpdate()
|
||||
{
|
||||
$('#sidebar .edit a').click(function(e)
|
||||
$('#sidebar a.edit-post').bindOnce('edit-post', 'click', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
@ -12,18 +12,10 @@ $(function()
|
||||
aDom.addClass('inactive');
|
||||
|
||||
var formDom = $('form.edit-post');
|
||||
formDom.data('original-data', formDom.serialize());
|
||||
if (formDom.find('.tagit').length == 0)
|
||||
{
|
||||
$.getJSON('/tags?json', {filter: 'order:popularity,desc'}, function(data)
|
||||
{
|
||||
attachTagIt($('.tags input'));
|
||||
aDom.removeClass('inactive');
|
||||
var tags = data['tags'];
|
||||
|
||||
var tagItOptions = getTagItOptions();
|
||||
tagItOptions.availableTags = tags;
|
||||
tagItOptions.placeholderText = $('.tags input').attr('placeholder');
|
||||
$('.tags input').tagit(tagItOptions);
|
||||
|
||||
formDom.find('input[type=text]:visible:eq(0)').focus();
|
||||
formDom.find('textarea, input').bind('change keyup', function()
|
||||
@ -31,21 +23,45 @@ $(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'))
|
||||
{
|
||||
formDom.parents('.unit')
|
||||
.show().css('height', formDom.height()).hide()
|
||||
.slideDown(function()
|
||||
formDom.data('original-data', formDom.serialize());
|
||||
|
||||
editUnit.show();
|
||||
var editUnitHeight = formDom.height();
|
||||
editUnit.css('height', editUnitHeight);
|
||||
editUnit.hide();
|
||||
|
||||
if (postUnit.height() < editUnitHeight)
|
||||
postUnit.animate({height: editUnitHeight + 'px'}, 'fast');
|
||||
|
||||
editUnit.slideDown('fast', function()
|
||||
{
|
||||
$(this).css('height', 'auto');
|
||||
});
|
||||
}
|
||||
$('html, body').animate({ scrollTop: $(formDom).offset().top + 'px' }, 'fast');
|
||||
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();
|
||||
});
|
||||
|
||||
@ -126,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('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,26 +1,14 @@
|
||||
<?php
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class Bootstrap
|
||||
{
|
||||
public function render($callback = null)
|
||||
{
|
||||
if ($callback === null)
|
||||
{
|
||||
$callback = function()
|
||||
{
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
};
|
||||
}
|
||||
|
||||
if ($this->context->layoutName == 'layout-normal')
|
||||
{
|
||||
ob_start(['LayoutHelper', 'transformHtml']);
|
||||
if ($callback !== null)
|
||||
$callback();
|
||||
ob_end_flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
$callback();
|
||||
}
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
|
||||
public function workWrapper($workCallback)
|
||||
@ -29,7 +17,7 @@ class Bootstrap
|
||||
session_start();
|
||||
|
||||
$this->context->handleExceptions = false;
|
||||
LayoutHelper::setTitle($this->config->main->title);
|
||||
CustomAssetViewDecorator::setTitle($this->config->main->title);
|
||||
|
||||
$this->context->json = isset($_GET['json']);
|
||||
$this->context->layoutName = $this->context->json
|
||||
@ -48,6 +36,8 @@ class Bootstrap
|
||||
return;
|
||||
}
|
||||
|
||||
$this->context->viewDecorators []= new CustomAssetViewDecorator();
|
||||
$this->context->viewDecorators []= new \Chibi\PrettyPrintViewDecorator();
|
||||
try
|
||||
{
|
||||
$this->render($workCallback);
|
||||
@ -62,14 +52,14 @@ class Bootstrap
|
||||
{
|
||||
if ($e instanceof SimpleNotFoundException)
|
||||
http_response_code(404);
|
||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
||||
StatusHelper::failure($e->getMessage());
|
||||
if (!$this->context->handleExceptions)
|
||||
$this->context->viewName = 'message';
|
||||
$this->render();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
||||
StatusHelper::failure($e->getMessage());
|
||||
$this->context->transport->exception = $e;
|
||||
$this->context->transport->queries = Database::getLogs();
|
||||
$this->context->viewName = 'error-exception';
|
||||
|
@ -8,27 +8,30 @@ class CommentController
|
||||
*/
|
||||
public function listAction($page)
|
||||
{
|
||||
$page = intval($page);
|
||||
$commentsPerPage = intval($this->config->comments->commentsPerPage);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListComments);
|
||||
|
||||
$page = max(1, $page);
|
||||
$comments = CommentSearchService::getEntities(null, $commentsPerPage, $page);
|
||||
$commentCount = CommentSearchService::getEntityCount(null, $commentsPerPage, $page);
|
||||
$pageCount = ceil($commentCount / $commentsPerPage);
|
||||
CommentModel::preloadCommenters($comments);
|
||||
CommentModel::preloadPosts($comments);
|
||||
$posts = array_map(function($comment) { return $comment->getPost(); }, $comments);
|
||||
$page = max(1, intval($page));
|
||||
$commentsPerPage = intval($this->config->comments->commentsPerPage);
|
||||
$searchQuery = 'comment_min:1 order:comment_date,desc';
|
||||
|
||||
$posts = PostSearchService::getEntities($searchQuery, $commentsPerPage, $page);
|
||||
$postCount = PostSearchService::getEntityCount($searchQuery);
|
||||
$pageCount = ceil($postCount / $commentsPerPage);
|
||||
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->transport->posts = $posts;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
$this->context->transport->paginator->pageCount = $pageCount;
|
||||
$this->context->transport->paginator->entityCount = $commentCount;
|
||||
$this->context->transport->paginator->entities = $comments;
|
||||
$this->context->transport->paginator->entityCount = $postCount;
|
||||
$this->context->transport->paginator->entities = $posts;
|
||||
$this->context->transport->paginator->params = func_get_args();
|
||||
$this->context->transport->comments = $comments;
|
||||
}
|
||||
|
||||
|
||||
|
@ -47,27 +47,8 @@ class IndexController
|
||||
//check if post was deleted
|
||||
$featuredPost = PostModel::findById($featuredPostId, false);
|
||||
if (!$featuredPost)
|
||||
return $this->featureNewPost();
|
||||
return PropertyModel::featureNewPost();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ class PostController
|
||||
$this->context->viewName = 'post-list-wrapper';
|
||||
$this->context->source = $source;
|
||||
$this->context->additionalInfo = $additionalInfo;
|
||||
$this->context->handleExceptions = true;
|
||||
|
||||
//redirect requests in form of /posts/?query=... to canonical address
|
||||
$formQuery = InputHelper::get('query');
|
||||
@ -72,10 +73,13 @@ class PostController
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
$this->context->massTagTag = $additionalInfo;
|
||||
$this->context->massTagQuery = $query;
|
||||
|
||||
if (!PrivilegesHelper::confirm(Privilege::MassTag, 'all'))
|
||||
$query = trim($query . ' submit:' . $this->context->user->name);
|
||||
}
|
||||
|
||||
$posts = PostSearchService::getEntities($query, $postsPerPage, $page);
|
||||
$postCount = PostSearchService::getEntityCount($query, $postsPerPage, $page);
|
||||
$postCount = PostSearchService::getEntityCount($query);
|
||||
$pageCount = ceil($postCount / $postsPerPage);
|
||||
$page = min($pageCount, $page);
|
||||
PostModel::preloadTags($posts);
|
||||
@ -103,7 +107,7 @@ class PostController
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$tags = $post->getTags();
|
||||
|
||||
@ -172,7 +176,7 @@ class PostController
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
Database::transaction(function()
|
||||
\Chibi\Database::transaction(function()
|
||||
{
|
||||
$post = PostModel::spawn();
|
||||
LogHelper::bufferChanges();
|
||||
@ -250,7 +254,7 @@ class PostController
|
||||
public function flagAction($id)
|
||||
{
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagPost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagPost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
@ -335,13 +339,14 @@ class PostController
|
||||
public function addFavoriteAction($id)
|
||||
{
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
|
||||
UserModel::updateUserScore($this->context->user, $post, 1);
|
||||
UserModel::addToUserFavorites($this->context->user, $post);
|
||||
StatusHelper::success();
|
||||
}
|
||||
@ -354,7 +359,7 @@ class PostController
|
||||
public function remFavoriteAction($id)
|
||||
{
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
@ -375,7 +380,7 @@ class PostController
|
||||
public function scoreAction($id, $score)
|
||||
{
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ScorePost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ScorePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
@ -395,7 +400,7 @@ class PostController
|
||||
public function featureAction($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::FeaturedPostDate, time());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, $this->context->user->name);
|
||||
@ -419,23 +424,21 @@ class PostController
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
|
||||
|
||||
PostSearchService::enableTokenLimit(false);
|
||||
try
|
||||
{
|
||||
$this->context->transport->lastSearchQuery = InputHelper::get('last-search-query');
|
||||
$prevPostQuery = $this->context->transport->lastSearchQuery . ' prev:' . $id;
|
||||
$nextPostQuery = $this->context->transport->lastSearchQuery . ' next:' . $id;
|
||||
$prevPost = current(PostSearchService::getEntities($prevPostQuery, 1, 1));
|
||||
$nextPost = current(PostSearchService::getEntities($nextPostQuery, 1, 1));
|
||||
list ($prevPostId, $nextPostId) =
|
||||
PostSearchService::getPostIdsAround(
|
||||
$this->context->transport->lastSearchQuery, $id);
|
||||
}
|
||||
#search for some reason was invalid, e.g. tag was deleted in the meantime
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->context->transport->lastSearchQuery = '';
|
||||
$prevPost = current(PostSearchService::getEntities('prev:' . $id, 1, 1));
|
||||
$nextPost = current(PostSearchService::getEntities('next:' . $id, 1, 1));
|
||||
list ($prevPostId, $nextPostId) =
|
||||
PostSearchService::getPostIdsAround(
|
||||
$this->context->transport->lastSearchQuery, $id);
|
||||
}
|
||||
PostSearchService::enableTokenLimit(true);
|
||||
|
||||
$favorite = $this->context->user->hasFavorited($post);
|
||||
$score = $this->context->user->getScore($post);
|
||||
@ -445,8 +448,8 @@ class PostController
|
||||
$this->context->score = $score;
|
||||
$this->context->flagged = $flagged;
|
||||
$this->context->transport->post = $post;
|
||||
$this->context->transport->prevPostId = $prevPost ? $prevPost->id : null;
|
||||
$this->context->transport->nextPostId = $nextPost ? $nextPost->id : null;
|
||||
$this->context->transport->prevPostId = $prevPostId ? $prevPostId : null;
|
||||
$this->context->transport->nextPostId = $nextPostId ? $nextPostId : null;
|
||||
}
|
||||
|
||||
|
||||
@ -501,14 +504,11 @@ class PostController
|
||||
if (!is_readable($path))
|
||||
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',
|
||||
$this->config->main->title,
|
||||
$post->id,
|
||||
join(',', array_map(function($tag) { return $tag->name; }, $post->getTags())),
|
||||
$ext);
|
||||
TextHelper::resolveMimeType($post->mimeType) ?: 'dat');
|
||||
$fn = preg_replace('/[[:^print:]]/', '', $fn);
|
||||
|
||||
$ttl = 60 * 60 * 24 * 14;
|
||||
@ -536,6 +536,7 @@ class PostController
|
||||
|
||||
$srcPath = $suppliedFile['tmp_name'];
|
||||
$post->setContentFromPath($srcPath);
|
||||
$post->origName = $suppliedFile['name'];
|
||||
|
||||
if (!$isNew)
|
||||
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
@ -547,6 +548,7 @@ class PostController
|
||||
|
||||
$url = InputHelper::get('url');
|
||||
$post->setContentFromUrl($url);
|
||||
$post->origName = $url;
|
||||
|
||||
if (!$isNew)
|
||||
LogHelper::log('{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
|
@ -3,17 +3,26 @@ class TagController
|
||||
{
|
||||
/**
|
||||
* @route /tags
|
||||
* @route /tags/{page}
|
||||
* @route /tags/{filter}
|
||||
* @route /tags/{filter}/{page}
|
||||
* @validate filter [a-zA-Z\32:,_-]+
|
||||
* @validate page \d*
|
||||
*/
|
||||
public function listAction($filter = null)
|
||||
public function listAction($filter = null, $page = 1)
|
||||
{
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
|
||||
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->transport->tags = $tags;
|
||||
|
||||
@ -23,6 +32,15 @@ class TagController
|
||||
return ['name' => $tag['name'], 'count' => $tag['post_count']];
|
||||
}, $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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,6 +49,7 @@ class TagController
|
||||
public function mergeAction()
|
||||
{
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
$this->context->handleExceptions = true;
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
@ -45,9 +64,8 @@ class TagController
|
||||
|
||||
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)]);
|
||||
StatusHelper::success();
|
||||
StatusHelper::success('Tags merged successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +75,7 @@ class TagController
|
||||
public function renameAction()
|
||||
{
|
||||
$this->context->viewName = 'tag-list-wrapper';
|
||||
$this->context->handleExceptions = true;
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
@ -71,9 +90,8 @@ class TagController
|
||||
|
||||
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)]);
|
||||
StatusHelper::success();
|
||||
StatusHelper::success('Tag renamed successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,13 +105,19 @@ class TagController
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedOldPage = intval(InputHelper::get('old-page'));
|
||||
$suppliedOldQuery = InputHelper::get('old-query');
|
||||
$suppliedQuery = InputHelper::get('query');
|
||||
if (!$suppliedQuery)
|
||||
$suppliedQuery = ' ';
|
||||
$suppliedTag = InputHelper::get('tag');
|
||||
if (!empty($suppliedTag))
|
||||
$suppliedTag = TagModel::validateTag($suppliedTag);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', ['source' => 'mass-tag', 'query' => $suppliedQuery, 'additionalInfo' => $suppliedTag]));
|
||||
|
||||
$params = [
|
||||
'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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,35 +100,32 @@ class UserController
|
||||
/**
|
||||
* @route /users
|
||||
* @route /users/{page}
|
||||
* @route /users/{sortStyle}
|
||||
* @route /users/{sortStyle}/{page}
|
||||
* @validate sortStyle alpha|alpha,asc|alpha,desc|date,asc|date,desc|pending
|
||||
* @route /users/{filter}
|
||||
* @route /users/{filter}/{page}
|
||||
* @validate filter [a-zA-Z\32:,_-]+
|
||||
* @validate page [0-9]+
|
||||
*/
|
||||
public function listAction($sortStyle, $page)
|
||||
public function listAction($filter, $page)
|
||||
{
|
||||
if ($sortStyle == '' or $sortStyle == 'alpha')
|
||||
$sortStyle = 'alpha,asc';
|
||||
if ($sortStyle == 'date')
|
||||
$sortStyle = 'date,asc';
|
||||
|
||||
$page = intval($page);
|
||||
$usersPerPage = intval($this->config->browsing->usersPerPage);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
||||
|
||||
$page = max(1, $page);
|
||||
$users = UserSearchService::getEntities($sortStyle, $usersPerPage, $page);
|
||||
$userCount = UserSearchService::getEntityCount($sortStyle, $usersPerPage, $page);
|
||||
$pageCount = ceil($userCount / $usersPerPage);
|
||||
$suppliedFilter = $filter ?: InputHelper::get('filter') ?: 'order:alpha,asc';
|
||||
$page = max(1, intval($page));
|
||||
$usersPerPage = intval($this->config->browsing->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->page = $page;
|
||||
$this->context->transport->paginator->pageCount = $pageCount;
|
||||
$this->context->transport->paginator->entityCount = $userCount;
|
||||
$this->context->transport->paginator->entities = $users;
|
||||
$this->context->transport->paginator->params = func_get_args();
|
||||
$this->context->transport->users = $users;
|
||||
}
|
||||
|
||||
|
||||
@ -140,7 +137,7 @@ class UserController
|
||||
public function flagAction($name)
|
||||
{
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagUser);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
|
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;
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
class LayoutHelper
|
||||
{
|
||||
private static $stylesheets = [];
|
||||
private static $scripts = [];
|
||||
private static $title = null;
|
||||
private static $pageThumb = null;
|
||||
private static $subTitle = null;
|
||||
|
||||
public static function setTitle($text)
|
||||
{
|
||||
self::$title = $text;
|
||||
}
|
||||
|
||||
public static function setSubTitle($text)
|
||||
{
|
||||
self::$subTitle = $text;
|
||||
}
|
||||
|
||||
public static function setPageThumb($path)
|
||||
{
|
||||
self::$pageThumb = $path;
|
||||
}
|
||||
|
||||
public static function addStylesheet($css)
|
||||
{
|
||||
self::$stylesheets []= $css;
|
||||
}
|
||||
|
||||
public static function addScript($js)
|
||||
{
|
||||
self::$scripts []= $js;
|
||||
}
|
||||
|
||||
public static function transformHtml($html)
|
||||
{
|
||||
$bodySnippet = '';
|
||||
$headSnippet = '';
|
||||
|
||||
$title = isset(self::$subTitle)
|
||||
? sprintf('%s – %s', self::$title, self::$subTitle)
|
||||
: self::$title;
|
||||
$headSnippet .= '<title>' . $title . '</title>';
|
||||
|
||||
$headSnippet .= '<meta property="og:title" content="' . $title . '"/>';
|
||||
$headSnippet .= '<meta property="og:url" content="' . \Chibi\UrlHelper::currentUrl() . '"/>';
|
||||
if (!empty(self::$pageThumb))
|
||||
$headSnippet .= '<meta property="og:image" content="' . self::$pageThumb . '"/>';
|
||||
|
||||
foreach (array_unique(self::$stylesheets) as $name)
|
||||
$headSnippet .= '<link rel="stylesheet" type="text/css" href="' . \Chibi\UrlHelper::absoluteUrl('/media/css/' . $name) . '"/>';
|
||||
|
||||
foreach (array_unique(self::$scripts) as $name)
|
||||
$bodySnippet .= '<script type="text/javascript" src="' . \Chibi\UrlHelper::absoluteUrl('/media/js/' . $name) . '"></script>';
|
||||
|
||||
$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');
|
||||
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)
|
||||
{
|
||||
if (!self::confirm($privilege, $subPrivilege))
|
||||
{
|
||||
throw new SimpleException('Insufficient privileges');
|
||||
}
|
||||
}
|
||||
|
||||
public static function getIdentitySubPrivilege($user)
|
||||
{
|
||||
|
@ -6,6 +6,9 @@ class StatusHelper
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if (!empty($message))
|
||||
{
|
||||
if (!preg_match('/[.?!]$/', $message))
|
||||
$message .= '.';
|
||||
|
||||
$context->transport->message = $message;
|
||||
$context->transport->messageHtml = TextHelper::parseMarkdown($message, true);
|
||||
}
|
||||
|
@ -87,33 +87,44 @@ class TextHelper
|
||||
{
|
||||
$suffix = substr($string, -1, 1);
|
||||
$index = array_search($suffix, $suffixes);
|
||||
if ($index === false)
|
||||
return $string;
|
||||
$number = intval($string);
|
||||
for ($i = 0; $i < $index; $i ++)
|
||||
$number *= $base;
|
||||
return $number;
|
||||
return floatval($string) * pow($base, $index !== false ? $index : 0);
|
||||
}
|
||||
|
||||
private static function useUnits($number, $base, $suffixes)
|
||||
private static function useUnits($number, $base, $suffixes, $fmtCallback = null)
|
||||
{
|
||||
$suffix = array_shift($suffixes);
|
||||
if ($number < $base)
|
||||
{
|
||||
return sprintf('%d%s', $number, $suffix);
|
||||
}
|
||||
do
|
||||
|
||||
while ($number >= $base and !empty($suffixes))
|
||||
{
|
||||
$suffix = array_shift($suffixes);
|
||||
$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 $fmtCallback($number, $suffix);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -284,4 +295,60 @@ class TextHelper
|
||||
|
||||
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
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
abstract class AbstractCrudModel implements IModel
|
||||
{
|
||||
public static function spawn()
|
||||
@ -21,12 +24,12 @@ abstract class AbstractCrudModel implements IModel
|
||||
|
||||
public static function findById($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from(static::getTableName())
|
||||
->where('id = ?')->put($key);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('*');
|
||||
$stmt->setTable(static::getTableName());
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($key)));
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
$row = Database::fetchOne($stmt);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
@ -37,12 +40,12 @@ abstract class AbstractCrudModel implements IModel
|
||||
|
||||
public static function findByIds(array $ids)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from(static::getTableName())
|
||||
->where('id')->in()->genSlots($ids)->put($ids);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('*');
|
||||
$stmt->setTable(static::getTableName());
|
||||
$stmt->setCriterion(Sql\InFunctor::fromArray('id', Sql\Binding::fromArray(array_unique($ids))));
|
||||
|
||||
$rows = Database::fetchAll($query);
|
||||
$rows = Database::fetchAll($stmt);
|
||||
if ($rows)
|
||||
return self::convertRows($rows);
|
||||
|
||||
@ -51,9 +54,10 @@ abstract class AbstractCrudModel implements IModel
|
||||
|
||||
public static function getCount()
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query->select('count(1)')->as('count')->from(static::getTableName());
|
||||
return Database::fetchOne($query)['count'];
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||
$stmt->setTable(static::getTableName());
|
||||
return Database::fetchOne($stmt)['count'];
|
||||
}
|
||||
|
||||
|
||||
@ -106,13 +110,9 @@ abstract class AbstractCrudModel implements IModel
|
||||
throw new Exception('Can be run only within transaction');
|
||||
if (!$entity->id)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$query = (new SqlQuery);
|
||||
if ($config->main->dbDriver == 'sqlite')
|
||||
$query->insertInto($table)->defaultValues();
|
||||
else
|
||||
$query->insertInto($table)->values()->open()->close();
|
||||
Database::query($query);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setTable($table);
|
||||
Database::exec($stmt);
|
||||
$entity->id = Database::lastInsertId();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class CommentModel extends AbstractCrudModel
|
||||
{
|
||||
public static function getTableName()
|
||||
@ -25,13 +28,14 @@ class CommentModel extends AbstractCrudModel
|
||||
'comment_date' => $comment->commentDate,
|
||||
'commenter_id' => $comment->commenterId];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('comment')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($comment->id);
|
||||
$stmt = new Sql\UpdateStatement();
|
||||
$stmt->setTable('comment');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($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)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('comment')
|
||||
->where('id = ?')->put($comment->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('comment');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($comment->id)));
|
||||
Database::exec($stmt);
|
||||
});
|
||||
}
|
||||
|
||||
@ -50,14 +54,12 @@ class CommentModel extends AbstractCrudModel
|
||||
|
||||
public static function findAllByPostId($key)
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query
|
||||
->select('comment.*')
|
||||
->from('comment')
|
||||
->where('post_id = ?')
|
||||
->put($key);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('comment.*');
|
||||
$stmt->setTable('comment');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', new Sql\Binding($key)));
|
||||
|
||||
$rows = Database::fetchAll($query);
|
||||
$rows = Database::fetchAll($stmt);
|
||||
if ($rows)
|
||||
return self::convertRows($rows);
|
||||
return [];
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class PostEntity extends AbstractEntity
|
||||
{
|
||||
public $type;
|
||||
@ -51,12 +54,12 @@ class PostEntity extends AbstractEntity
|
||||
{
|
||||
if ($this->hasCache('favoritee'))
|
||||
return $this->getCache('favoritee');
|
||||
$query = (new SqlQuery)
|
||||
->select('user.*')
|
||||
->from('user')
|
||||
->innerJoin('favoritee')->on('favoritee.user_id = user.id')
|
||||
->where('favoritee.post_id = ?')->put($this->id);
|
||||
$rows = Database::fetchAll($query);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('user.*');
|
||||
$stmt->setTable('user');
|
||||
$stmt->addInnerJoin('favoritee', new Sql\EqualsFunctor('favoritee.user_id', 'user.id'));
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('favoritee.post_id', new Sql\Binding($this->id)));
|
||||
$rows = Database::fetchAll($stmt);
|
||||
$favorites = UserModel::convertRows($rows);
|
||||
$this->setCache('favoritee', $favorites);
|
||||
return $favorites;
|
||||
@ -69,13 +72,20 @@ class PostEntity extends AbstractEntity
|
||||
if ($this->hasCache('relations'))
|
||||
return $this->getCache('relations');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->select('post.*')
|
||||
->from('post')
|
||||
->innerJoin('crossref')
|
||||
->on()->open()->raw('post.id = crossref.post2_id')->and('crossref.post_id = ?')->close()->put($this->id)
|
||||
->or()->open()->raw('post.id = crossref.post_id')->and('crossref.post2_id = ?')->close()->put($this->id);
|
||||
$rows = Database::fetchAll($query);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('post.*');
|
||||
$stmt->setTable('post');
|
||||
$binding = new Sql\Binding($this->id);
|
||||
$stmt->addInnerJoin('crossref', (new Sql\DisjunctionFunctor)
|
||||
->add(
|
||||
(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);
|
||||
$this->setCache('relations', $posts);
|
||||
return $posts;
|
||||
@ -234,7 +244,7 @@ class PostEntity extends AbstractEntity
|
||||
if ($this->type == PostType::Youtube)
|
||||
{
|
||||
$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);
|
||||
if (file_exists($tmpPath))
|
||||
$srcImage = imagecreatefromjpeg($tmpPath);
|
||||
@ -333,7 +343,6 @@ class PostEntity extends AbstractEntity
|
||||
throw new SimpleException('Invalid file type "' . $this->mimeType . '"');
|
||||
}
|
||||
|
||||
$this->origName = basename($srcPath);
|
||||
$duplicatedPost = PostModel::findByHash($this->fileHash, false);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||
@ -352,19 +361,16 @@ class PostEntity extends AbstractEntity
|
||||
|
||||
public function setContentFromUrl($srcUrl)
|
||||
{
|
||||
$this->origName = $srcUrl;
|
||||
|
||||
if (!preg_match('/^https?:\/\//', $srcUrl))
|
||||
throw new SimpleException('Invalid URL "' . $srcUrl . '"');
|
||||
|
||||
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $srcUrl, $matches))
|
||||
{
|
||||
$origName = $matches[1];
|
||||
$this->origName = $origName;
|
||||
$youtubeId = $matches[1];
|
||||
$this->type = PostType::Youtube;
|
||||
$this->mimeType = null;
|
||||
$this->fileSize = null;
|
||||
$this->fileHash = $origName;
|
||||
$this->fileHash = $youtubeId;
|
||||
$this->imageWidth = null;
|
||||
$this->imageHeight = null;
|
||||
|
||||
@ -372,7 +378,7 @@ class PostEntity extends AbstractEntity
|
||||
if (file_exists($thumbPath))
|
||||
unlink($thumbPath);
|
||||
|
||||
$duplicatedPost = PostModel::findByHash($origName, false);
|
||||
$duplicatedPost = PostModel::findByHash($youtubeId, false);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||
return;
|
||||
|
@ -1,14 +1,17 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class TagEntity extends AbstractEntity
|
||||
{
|
||||
public $name;
|
||||
|
||||
public function getPostCount()
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('count(*)')->as('count')
|
||||
->from('post_tag')
|
||||
->where('tag_id = ?')->put($this->id);
|
||||
return Database::fetchOne($query)['count'];
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||
$stmt->setTable('post_tag');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('tag_id', new Sql\Binding($this->id)));
|
||||
return Database::fetchOne($stmt)['count'];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class UserEntity extends AbstractEntity
|
||||
{
|
||||
public $name;
|
||||
@ -111,22 +114,24 @@ class UserEntity extends AbstractEntity
|
||||
|
||||
public function hasFavorited($post)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('count(1)')->as('count')
|
||||
->from('favoritee')
|
||||
->where('user_id = ?')->put($this->id)
|
||||
->and('post_id = ?')->put($post->id);
|
||||
return Database::fetchOne($query)['count'] == 1;
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||
$stmt->setTable('favoritee');
|
||||
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($this->id)))
|
||||
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id))));
|
||||
return Database::fetchOne($stmt)['count'] == 1;
|
||||
}
|
||||
|
||||
public function getScore($post)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('score')
|
||||
->from('post_score')
|
||||
->where('user_id = ?')->put($this->id)
|
||||
->and('post_id = ?')->put($post->id);
|
||||
$row = Database::fetchOne($query);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('score');
|
||||
$stmt->setTable('post_score');
|
||||
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($this->id)))
|
||||
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id))));
|
||||
$row = Database::fetchOne($stmt);
|
||||
if ($row)
|
||||
return intval($row['score']);
|
||||
return null;
|
||||
@ -134,28 +139,28 @@ class UserEntity extends AbstractEntity
|
||||
|
||||
public function getFavoriteCount()
|
||||
{
|
||||
$sqlQuery = (new SqlQuery)
|
||||
->select('count(1)')->as('count')
|
||||
->from('favoritee')
|
||||
->where('user_id = ?')->put($this->id);
|
||||
return Database::fetchOne($sqlQuery)['count'];
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||
$stmt->setTable('favoritee');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('user_id', new Sql\Binding($this->id)));
|
||||
return Database::fetchOne($stmt)['count'];
|
||||
}
|
||||
|
||||
public function getCommentCount()
|
||||
{
|
||||
$sqlQuery = (new SqlQuery)
|
||||
->select('count(1)')->as('count')
|
||||
->from('comment')
|
||||
->where('commenter_id = ?')->put($this->id);
|
||||
return Database::fetchOne($sqlQuery)['count'];
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||
$stmt->setTable('comment');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('commenter_id', new Sql\Binding($this->id)));
|
||||
return Database::fetchOne($stmt)['count'];
|
||||
}
|
||||
|
||||
public function getPostCount()
|
||||
{
|
||||
$sqlQuery = (new SqlQuery)
|
||||
->select('count(1)')->as('count')
|
||||
->from('post')
|
||||
->where('uploader_id = ?')->put($this->id);
|
||||
return Database::fetchOne($sqlQuery)['count'];
|
||||
$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'];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class PostModel extends AbstractCrudModel
|
||||
{
|
||||
protected static $config;
|
||||
@ -48,48 +51,50 @@ class PostModel extends AbstractCrudModel
|
||||
'source' => $post->source,
|
||||
];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('post')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($post->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\UpdateStatement();
|
||||
$stmt->setTable('post');
|
||||
|
||||
foreach ($bindings as $key => $value)
|
||||
$stmt->setColumn($key, new Sql\Binding($value));
|
||||
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($post->id)));
|
||||
Database::exec($stmt);
|
||||
|
||||
//tags
|
||||
$tags = $post->getTags();
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('post_tag')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('post_tag');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id)));
|
||||
Database::exec($stmt);
|
||||
|
||||
foreach ($tags as $postTag)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->insertInto('post_tag')
|
||||
->surround('post_id, tag_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$post->id, $postTag->id]);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setTable('post_tag');
|
||||
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||
$stmt->setColumn('tag_id', new Sql\Binding($postTag->id));
|
||||
Database::exec($stmt);
|
||||
}
|
||||
|
||||
//relations
|
||||
$relations = $post->getRelations();
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('crossref')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->or('post2_id = ?')->put($post->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('crossref');
|
||||
$binding = new Sql\Binding($post->id);
|
||||
$stmt->setCriterion((new Sql\DisjunctionFunctor)
|
||||
->add(new Sql\EqualsFunctor('post_id', $binding))
|
||||
->add(new Sql\EqualsFunctor('post2_id', $binding)));
|
||||
Database::exec($stmt);
|
||||
|
||||
foreach ($relations as $relatedPost)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->insertInto('crossref')
|
||||
->surround('post_id, post2_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$post->id, $relatedPost->id]);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setTable('crossref');
|
||||
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||
$stmt->setColumn('post2_id', new Sql\Binding($relatedPost->id));
|
||||
Database::exec($stmt);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -98,36 +103,31 @@ class PostModel extends AbstractCrudModel
|
||||
{
|
||||
Database::transaction(function() use ($post)
|
||||
{
|
||||
$queries = [];
|
||||
$binding = new Sql\Binding($post->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post_score')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('post_score');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', $binding));
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post_tag')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
$stmt->setTable('post_tag');
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('crossref')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->or('post2_id = ?')->put($post->id);
|
||||
$stmt->setTable('favoritee');
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('favoritee')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
$stmt->setTable('comment');
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->update('comment')
|
||||
->set('post_id = NULL')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
$stmt->setTable('crossref');
|
||||
$stmt->setCriterion((new Sql\DisjunctionFunctor)
|
||||
->add(new Sql\EqualsFunctor('post_id', $binding))
|
||||
->add(new Sql\EqualsFunctor('post_id', $binding)));
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post')
|
||||
->where('id = ?')->put($post->id);
|
||||
|
||||
foreach ($queries as $query)
|
||||
Database::query($query);
|
||||
$stmt->setTable('post');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', $binding));
|
||||
Database::exec($stmt);
|
||||
});
|
||||
}
|
||||
|
||||
@ -136,12 +136,12 @@ class PostModel extends AbstractCrudModel
|
||||
|
||||
public static function findByName($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('post')
|
||||
->where('name = ?')->put($key);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('*');
|
||||
$stmt->setTable('post');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('name', new Sql\Binding($key)));
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
$row = Database::fetchOne($stmt);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
@ -161,12 +161,12 @@ class PostModel extends AbstractCrudModel
|
||||
|
||||
public static function findByHash($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('post')
|
||||
->where('file_hash = ?')->put($key);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('*');
|
||||
$stmt->setTable('post');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('file_hash', new Sql\Binding($key)));
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
$row = Database::fetchOne($stmt);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
@ -177,6 +177,47 @@ class PostModel extends AbstractCrudModel
|
||||
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
if (empty($posts))
|
||||
@ -190,14 +231,15 @@ class PostModel extends AbstractCrudModel
|
||||
$postMap[$postId] = $post;
|
||||
$tagsMap[$postId] = [];
|
||||
}
|
||||
$postIds = array_keys($postMap);
|
||||
$postIds = array_unique(array_keys($postMap));
|
||||
|
||||
$sqlQuery = (new SqlQuery)
|
||||
->select('tag.*, post_id')
|
||||
->from('tag')
|
||||
->innerJoin('post_tag')->on('post_tag.tag_id = tag.id')
|
||||
->where('post_id')->in()->genSlots($postIds)->put($postIds);
|
||||
$rows = Database::fetchAll($sqlQuery);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setTable('tag');
|
||||
$stmt->addColumn('tag.*');
|
||||
$stmt->addColumn('post_id');
|
||||
$stmt->addInnerJoin('post_tag', new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id'));
|
||||
$stmt->setCriterion(Sql\InFunctor::fromArray('post_id', Sql\Binding::fromArray($postIds)));
|
||||
$rows = Database::fetchAll($stmt);
|
||||
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
@ -215,10 +257,8 @@ class PostModel extends AbstractCrudModel
|
||||
}
|
||||
|
||||
foreach ($tagsMap as $postId => $tags)
|
||||
{
|
||||
$postMap[$postId]->setCache('tags', $tags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class PropertyModel implements IModel
|
||||
{
|
||||
const FeaturedPostId = 0;
|
||||
@ -20,8 +23,10 @@ class PropertyModel implements IModel
|
||||
{
|
||||
self::$loaded = true;
|
||||
self::$allProperties = [];
|
||||
$query = (new SqlQuery())->select('*')->from('property');
|
||||
foreach (Database::fetchAll($query) as $row)
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt ->setColumn('*');
|
||||
$stmt ->setTable('property');
|
||||
foreach (Database::fetchAll($stmt) as $row)
|
||||
self::$allProperties[$row['prop_id']] = $row['value'];
|
||||
}
|
||||
}
|
||||
@ -39,35 +44,47 @@ class PropertyModel implements IModel
|
||||
self::loadIfNecessary();
|
||||
Database::transaction(function() use ($propertyId, $value)
|
||||
{
|
||||
$row = Database::query((new SqlQuery)
|
||||
->select('id')
|
||||
->from('property')
|
||||
->where('prop_id = ?')
|
||||
->put($propertyId));
|
||||
|
||||
$query = (new SqlQuery);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('id');
|
||||
$stmt->setTable('property');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('prop_id', new Sql\Binding($propertyId)));
|
||||
$row = Database::fetchOne($stmt);
|
||||
|
||||
if ($row)
|
||||
{
|
||||
$query
|
||||
->update('property')
|
||||
->set('value = ?')
|
||||
->put($value)
|
||||
->where('prop_id = ?')
|
||||
->put($propertyId);
|
||||
$stmt = new Sql\UpdateStatement();
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('prop_id', new Sql\Binding($propertyId)));
|
||||
}
|
||||
else
|
||||
{
|
||||
$query
|
||||
->insertInto('property')
|
||||
->open()->raw('prop_id, value_id')->close()
|
||||
->open()->raw('?, ?')->close()
|
||||
->put([$propertyId, $value]);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setColumn('prop_id', new Sql\Binding($propertyId));
|
||||
}
|
||||
$stmt->setTable('property');
|
||||
$stmt->setColumn('value', new Sql\Binding($value));
|
||||
|
||||
Database::query($query);
|
||||
Database::exec($stmt);
|
||||
|
||||
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,4 +1,7 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
abstract class AbstractSearchService
|
||||
{
|
||||
protected static function getModelClassName()
|
||||
@ -8,49 +11,69 @@ abstract class AbstractSearchService
|
||||
return $modelClassName;
|
||||
}
|
||||
|
||||
protected static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
||||
protected static function getParserClassName()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
$searchServiceClassName = get_called_class();
|
||||
$parserClassName = str_replace('SearchService', 'SearchParser', $searchServiceClassName);
|
||||
return $parserClassName;
|
||||
}
|
||||
|
||||
protected static function decoratePager(SqlQuery $sqlQuery, $perPage, $page)
|
||||
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;
|
||||
$sqlQuery->limit('?')->put($perPage);
|
||||
$sqlQuery->offset('?')->put(($page - 1) * $perPage);
|
||||
$stmt->setLimit(
|
||||
new Sql\Binding($perPage),
|
||||
new Sql\Binding(($page - 1) * $perPage));
|
||||
}
|
||||
|
||||
static function getEntitiesRows($searchQuery, $perPage = null, $page = 1)
|
||||
public 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);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn($table . '.*');
|
||||
$stmt->setTable($table);
|
||||
static::decorateParser($stmt, $searchQuery);
|
||||
static::decorateCustom($stmt);
|
||||
static::decoratePager($stmt, $perPage, $page);
|
||||
|
||||
$rows = Database::fetchAll($sqlQuery);
|
||||
return $rows;
|
||||
return Database::fetchAll($stmt);
|
||||
}
|
||||
|
||||
static function getEntities($searchQuery, $perPage = null, $page = 1)
|
||||
public 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)
|
||||
public static function getEntityCount($searchQuery)
|
||||
{
|
||||
$modelClassName = self::getModelClassName();
|
||||
$table = $modelClassName::getTableName();
|
||||
|
||||
$sqlQuery = new SqlQuery();
|
||||
$sqlQuery->select('count(1)')->as('count');
|
||||
static::decorate($sqlQuery, $searchQuery);
|
||||
$innerStmt = new Sql\SelectStatement();
|
||||
$innerStmt->setTable($table);
|
||||
static::decorateParser($innerStmt, $searchQuery);
|
||||
static::decorateCustom($innerStmt);
|
||||
$innerStmt->resetOrderBy();
|
||||
|
||||
return Database::fetchOne($sqlQuery)['count'];
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn(new Sql\AliasFunctor(new Sql\CountFunctor('1'), 'count'));
|
||||
$stmt->setSource($innerStmt);
|
||||
|
||||
return Database::fetchOne($stmt)['count'];
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,4 @@
|
||||
<?php
|
||||
class CommentSearchService extends AbstractSearchService
|
||||
{
|
||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
||||
{
|
||||
$sqlQuery
|
||||
->from('comment')
|
||||
->innerJoin('post')
|
||||
->on('post_id = post.id');
|
||||
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
if (empty($allowedSafety))
|
||||
$sqlQuery->where('0');
|
||||
else
|
||||
$sqlQuery->where('post.safety')->in()->genSlots($allowedSafety)->put($allowedSafety);
|
||||
|
||||
$sqlQuery
|
||||
->orderBy('comment.id')
|
||||
->desc();
|
||||
}
|
||||
}
|
||||
|
@ -1,487 +1,50 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class PostSearchService extends AbstractSearchService
|
||||
{
|
||||
private static $enableTokenLimit = true;
|
||||
|
||||
public static function enableTokenLimit($enable)
|
||||
public static function getPostIdsAround($searchQuery, $postId)
|
||||
{
|
||||
self::$enableTokenLimit = $enable;
|
||||
}
|
||||
|
||||
protected static function filterUserSafety(SqlQuery $sqlQuery)
|
||||
return Database::transaction(function() use ($searchQuery, $postId)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
if (empty($allowedSafety))
|
||||
$sqlQuery->raw('0');
|
||||
else
|
||||
$sqlQuery->raw('safety')->in()->genSlots($allowedSafety)->put($allowedSafety);
|
||||
}
|
||||
$stmt = new Sql\RawStatement('CREATE TEMPORARY TABLE IF NOT EXISTS post_search(id INTEGER PRIMARY KEY, post_id INTEGER)');
|
||||
Database::exec($stmt);
|
||||
|
||||
protected static function filterChain(SqlQuery $sqlQuery)
|
||||
{
|
||||
if (isset($sqlQuery->__chained))
|
||||
$sqlQuery->and();
|
||||
else
|
||||
$sqlQuery->where();
|
||||
$sqlQuery->__chained = true;
|
||||
}
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('post_search');
|
||||
Database::exec($stmt);
|
||||
|
||||
protected static function filterNegate(SqlQuery $sqlQuery)
|
||||
{
|
||||
$sqlQuery->not();
|
||||
}
|
||||
$innerStmt = new Sql\SelectStatement($searchQuery);
|
||||
$innerStmt->setColumn('post.id');
|
||||
$innerStmt->setTable('post');
|
||||
self::decorateParser($innerStmt, $searchQuery);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setTable('post_search');
|
||||
$stmt->setSource(['post_id'], $innerStmt);
|
||||
Database::exec($stmt);
|
||||
|
||||
protected static function filterTag($sqlQuery, $val)
|
||||
{
|
||||
$tag = TagModel::findByName($val);
|
||||
$sqlQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_id = post.id')
|
||||
->and('post_tag.tag_id = ?')->put($tag->id)
|
||||
->close();
|
||||
}
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setTable('post_search');
|
||||
$stmt->setColumn('id');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('post_id', new Sql\Binding($postId)));
|
||||
$rowId = Database::fetchOne($stmt)['id'];
|
||||
|
||||
protected static function filterTokenId($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$ids = preg_split('/[;,]/', $val);
|
||||
$ids = array_map('intval', $ids);
|
||||
if (empty($ids))
|
||||
$sqlQuery->raw('0');
|
||||
else
|
||||
$sqlQuery->raw('id')->in()->genSlots($ids)->put($ids);
|
||||
}
|
||||
//it's possible that given post won't show in search results:
|
||||
//it can be hidden, it can have prohibited safety etc.
|
||||
if (!$rowId)
|
||||
return [null, null];
|
||||
|
||||
protected static function filterTokenIdMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$sqlQuery->raw('id >= ?')->put(intval($val));
|
||||
}
|
||||
$rowId = intval($rowId);
|
||||
$stmt->setColumn('post_id');
|
||||
|
||||
protected static function filterTokenIdMax($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$sqlQuery->raw('id <= ?')->put(intval($val));
|
||||
}
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($rowId - 1)));
|
||||
$nextPostId = Database::fetchOne($stmt)['post_id'];
|
||||
|
||||
protected static function filterTokenScoreMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$sqlQuery->raw('score >= ?')->put(intval($val));
|
||||
}
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($rowId + 1)));
|
||||
$prevPostId = Database::fetchOne($stmt)['post_id'];
|
||||
|
||||
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 ($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;
|
||||
|
||||
case 'hidden':
|
||||
$sqlQuery->raw('hidden');
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new SimpleException('Unknown special "' . $val . '"');
|
||||
}
|
||||
}
|
||||
|
||||
protected static function filterTokenType($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
switch ($val)
|
||||
{
|
||||
case 'swf':
|
||||
$type = PostType::Flash;
|
||||
break;
|
||||
case 'img':
|
||||
$type = PostType::Image;
|
||||
break;
|
||||
case 'yt':
|
||||
case 'youtube':
|
||||
$type = PostType::Youtube;
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown type "' . $val . '"');
|
||||
}
|
||||
$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 = 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);
|
||||
|
||||
/* query tokens */
|
||||
$tokens = array_filter(array_unique(explode(' ', strtolower($searchQuery))));
|
||||
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';
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden') or !in_array('special:hidden', $tokens))
|
||||
$tokens []= '-special:hidden';
|
||||
|
||||
$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;
|
||||
return [$prevPostId, $nextPostId];
|
||||
});
|
||||
|
||||
|
||||
$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,90 +1,23 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class TagSearchService extends AbstractSearchService
|
||||
{
|
||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
||||
public static function decorateCustom(Sql\SelectStatement $stmt)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
$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');
|
||||
if (empty($allowedSafety))
|
||||
$sqlQuery->where('0');
|
||||
else
|
||||
$sqlQuery->where('safety')->in()->genSlots($allowedSafety);
|
||||
foreach ($allowedSafety as $s)
|
||||
$sqlQuery->put($s);
|
||||
$stmt->addColumn(new Sql\AliasFunctor(new Sql\CountFunctor('post_tag.post_id'), 'post_count'));
|
||||
}
|
||||
|
||||
$orderToken = null;
|
||||
|
||||
if ($searchQuery !== null)
|
||||
public static function getMostUsedTag()
|
||||
{
|
||||
$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');
|
||||
if ($orderToken)
|
||||
self::order($sqlQuery,$orderToken);
|
||||
|
||||
|
||||
if ($limitQuery)
|
||||
$sqlQuery->limit(15);
|
||||
}
|
||||
|
||||
private static function order(SqlQuery $sqlQuery, $value)
|
||||
{
|
||||
if (strpos($value, ',') !== false)
|
||||
{
|
||||
list ($orderColumn, $orderDir) = explode(',', $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$orderColumn = $value;
|
||||
$orderDir = 'asc';
|
||||
}
|
||||
|
||||
switch ($orderColumn)
|
||||
{
|
||||
case 'popularity':
|
||||
$sqlQuery->orderBy('post_count');
|
||||
break;
|
||||
|
||||
case 'alpha':
|
||||
$sqlQuery->orderBy('name');
|
||||
break;
|
||||
}
|
||||
|
||||
if ($orderDir == 'asc')
|
||||
$sqlQuery->asc();
|
||||
else
|
||||
$sqlQuery->desc();
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setTable('post_tag');
|
||||
$stmt->addColumn('tag_id');
|
||||
$stmt->addColumn(new Sql\AliasFunctor(new Sql\CountFunctor('post_tag.post_id'), 'post_count'));
|
||||
$stmt->setGroupBy('post_tag.tag_id');
|
||||
$stmt->setOrderBy('post_count', Sql\SelectStatement::ORDER_DESC);
|
||||
$stmt->setLimit(1, 0);
|
||||
return Database::fetchOne($stmt);
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,4 @@
|
||||
<?php
|
||||
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
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class TagModel extends AbstractCrudModel
|
||||
{
|
||||
public static function getTableName()
|
||||
@ -12,27 +15,29 @@ class TagModel extends AbstractCrudModel
|
||||
{
|
||||
self::forgeId($tag, 'tag');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('tag')
|
||||
->set('name = ?')->put($tag->name)
|
||||
->where('id = ?')->put($tag->id);
|
||||
$stmt = new Sql\UpdateStatement();
|
||||
$stmt->setTable('tag');
|
||||
$stmt->setColumn('name', new Sql\Binding($tag->name));
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($tag->id)));
|
||||
|
||||
Database::query($query);
|
||||
Database::exec($stmt);
|
||||
});
|
||||
return $tag->id;
|
||||
}
|
||||
|
||||
public static function remove($tag)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('post_tag')
|
||||
->where('tag_id = ?')->put($tag->id);
|
||||
Database::query($query);
|
||||
$binding = new Sql\Binding($tag->id);
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('tag')
|
||||
->where('id = ?')->put($tag->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('post_tag');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('tag_id', $binding));
|
||||
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)
|
||||
@ -60,38 +65,40 @@ class TagModel extends AbstractCrudModel
|
||||
if ($sourceTag->id == $targetTag->id)
|
||||
throw new SimpleException('Source and target tag are the same');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->select('post.id')
|
||||
->from('post')
|
||||
->where()
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.post_id = post.id')
|
||||
->and('post_tag.tag_id = ?')->put($sourceTag->id)
|
||||
->close()
|
||||
->and()
|
||||
->not()->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.post_id = post.id')
|
||||
->and('post_tag.tag_id = ?')->put($targetTag->id)
|
||||
->close();
|
||||
$rows = Database::fetchAll($query);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('post.id');
|
||||
$stmt->setTable('post');
|
||||
$stmt->setCriterion(
|
||||
(new Sql\ConjunctionFunctor)
|
||||
->add(
|
||||
new Sql\ExistsFunctor(
|
||||
(new Sql\SelectStatement)
|
||||
->setTable('post_tag')
|
||||
->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($sourceTag->id))))))
|
||||
->add(
|
||||
new Sql\NegationFunctor(
|
||||
new Sql\ExistsFunctor(
|
||||
(new Sql\SelectStatement)
|
||||
->setTable('post_tag')
|
||||
->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($targetTag->id))))))));
|
||||
$rows = Database::fetchAll($stmt);
|
||||
$postIds = array_map(function($row) { return $row['id']; }, $rows);
|
||||
|
||||
self::remove($sourceTag);
|
||||
|
||||
foreach ($postIds as $postId)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->insertInto('post_tag')
|
||||
->surround('post_id, tag_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$postId, $targetTag->id]);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setTable('post_tag');
|
||||
$stmt->setColumn('post_id', new Sql\Binding($postId));
|
||||
$stmt->setColumn('tag_id', new Sql\Binding($targetTag->id));
|
||||
Database::exec($stmt);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -99,16 +106,13 @@ class TagModel extends AbstractCrudModel
|
||||
|
||||
public static function findAllByPostId($key)
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query
|
||||
->select('tag.*')
|
||||
->from('tag')
|
||||
->innerJoin('post_tag')
|
||||
->on('post_tag.tag_id = tag.id')
|
||||
->where('post_tag.post_id = ?')
|
||||
->put($key);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('tag.*');
|
||||
$stmt->setTable('tag');
|
||||
$stmt->addInnerJoin('post_tag', new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id'));
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('post_tag.post_id', new Sql\Binding($key)));
|
||||
|
||||
$rows = Database::fetchAll($query);
|
||||
$rows = Database::fetchAll($stmt);
|
||||
if ($rows)
|
||||
return self::convertRows($rows);
|
||||
return [];
|
||||
@ -116,12 +120,12 @@ class TagModel extends AbstractCrudModel
|
||||
|
||||
public static function findByName($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('tag')
|
||||
->where('LOWER(name) = LOWER(?)')->put($key);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('tag.*');
|
||||
$stmt->setTable('tag');
|
||||
$stmt->setCriterion(new Sql\NoCaseFunctor(new Sql\EqualsFunctor('name', new Sql\Binding($key))));
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
$row = Database::fetchOne($stmt);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
@ -134,16 +138,15 @@ class TagModel extends AbstractCrudModel
|
||||
|
||||
public static function removeUnused()
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('tag')
|
||||
->where()
|
||||
->not()->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.tag_id = tag.id')
|
||||
->close();
|
||||
Database::query($query);
|
||||
$stmt = (new Sql\DeleteStatement)
|
||||
->setTable('tag')
|
||||
->setCriterion(
|
||||
new Sql\NegationFunctor(
|
||||
new Sql\ExistsFunctor(
|
||||
(new Sql\SelectStatement)
|
||||
->setTable('post_tag')
|
||||
->setCriterion(new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id')))));
|
||||
Database::exec($stmt);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class TokenModel extends AbstractCrudModel
|
||||
implements IModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
@ -20,29 +22,29 @@ implements IModel
|
||||
'expires' => $token->expires,
|
||||
];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('user_token')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($token->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\UpdateStatement();
|
||||
$stmt->setTable('user_token');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($token->id)));
|
||||
|
||||
foreach ($bindings as $key => $val)
|
||||
$stmt->setColumn($key, new Sql\Binding($val));
|
||||
|
||||
Database::exec($stmt);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function findByToken($key, $throw = true)
|
||||
{
|
||||
if (empty($key))
|
||||
throw new SimpleNotFoundException('Invalid security token');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('user_token')
|
||||
->where('token = ?')->put($key);
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setTable('user_token');
|
||||
$stmt->setColumn('*');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('token', new Sql\Binding($key)));
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
$row = Database::fetchOne($stmt);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
@ -51,8 +53,6 @@ implements IModel
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function checkValidity($token)
|
||||
{
|
||||
if (empty($token))
|
||||
@ -65,8 +65,6 @@ implements IModel
|
||||
throw new SimpleException('This token has expired');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function forgeUnusedToken()
|
||||
{
|
||||
$tokenText = '';
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
use \Chibi\Sql as Sql;
|
||||
use \Chibi\Database as Database;
|
||||
|
||||
class UserModel extends AbstractCrudModel
|
||||
{
|
||||
const SETTING_SAFETY = 1;
|
||||
@ -40,12 +43,14 @@ class UserModel extends AbstractCrudModel
|
||||
'banned' => $user->banned
|
||||
];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('user')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($user->id);
|
||||
Database::query($query);
|
||||
$stmt = (new Sql\UpdateStatement)
|
||||
->setTable('user')
|
||||
->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($user->id)));
|
||||
|
||||
foreach ($bindings as $key => $val)
|
||||
$stmt->setColumn($key, new Sql\Binding($val));
|
||||
|
||||
Database::exec($stmt);
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,32 +58,31 @@ class UserModel extends AbstractCrudModel
|
||||
{
|
||||
Database::transaction(function() use ($user)
|
||||
{
|
||||
$queries = [];
|
||||
$binding = new Sql\Binding($user->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post_score')
|
||||
->where('user_id = ?')->put($user->id);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('post_score');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('user_id', $binding));
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->update('comment')
|
||||
->set('commenter_id = NULL')
|
||||
->where('commenter_id = ?')->put($user->id);
|
||||
$stmt->setTable('favoritee');
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->update('post')
|
||||
->set('uploader_id = NULL')
|
||||
->where('uploader_id = ?')->put($user->id);
|
||||
$stmt->setTable('user');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('id', $binding));
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('favoritee')
|
||||
->where('user_id = ?')->put($user->id);
|
||||
$stmt = new Sql\UpdateStatement();
|
||||
$stmt->setTable('comment');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('commenter_id', $binding));
|
||||
$stmt->setColumn('commenter_id', new Sql\NullFunctor());
|
||||
Database::exec($stmt);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('user')
|
||||
->where('id = ?')->put($user->id);
|
||||
|
||||
foreach ($queries as $query)
|
||||
Database::query($query);
|
||||
$stmt = new Sql\UpdateStatement();
|
||||
$stmt->setTable('post');
|
||||
$stmt->setCriterion(new Sql\EqualsFunctor('uploader_id', $binding));
|
||||
$stmt->setColumn('uploader_id', new Sql\NullFunctor());
|
||||
Database::exec($stmt);
|
||||
});
|
||||
}
|
||||
|
||||
@ -86,12 +90,12 @@ class UserModel extends AbstractCrudModel
|
||||
|
||||
public static function findByName($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('user')
|
||||
->where('LOWER(name) = LOWER(?)')->put(trim($key));
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('*');
|
||||
$stmt->setTable('user');
|
||||
$stmt->setCriterion(new Sql\NoCaseFunctor(new Sql\EqualsFunctor('name', new Sql\Binding(trim($key)))));
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
$row = Database::fetchOne($stmt);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
@ -102,13 +106,14 @@ class UserModel extends AbstractCrudModel
|
||||
|
||||
public static function findByNameOrEmail($key, $throw = true)
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query->select('*')
|
||||
->from('user')
|
||||
->where('LOWER(name) = LOWER(?)')->put(trim($key))
|
||||
->or('LOWER(email_confirmed) = LOWER(?)')->put(trim($key));
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt->setColumn('*');
|
||||
$stmt->setTable('user');
|
||||
$stmt->setCriterion((new Sql\DisjunctionFunctor)
|
||||
->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)
|
||||
return self::convertRow($row);
|
||||
|
||||
@ -123,20 +128,21 @@ class UserModel extends AbstractCrudModel
|
||||
{
|
||||
Database::transaction(function() use ($user, $post, $score)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('post_score')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->and('user_id = ?')->put($user->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('post_score');
|
||||
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id)))
|
||||
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($user->id))));
|
||||
Database::exec($stmt);
|
||||
$score = intval($score);
|
||||
if ($score != 0)
|
||||
{
|
||||
$query = (new SqlQuery);
|
||||
$query->insertInto('post_score')
|
||||
->surround('post_id, user_id, score')
|
||||
->values()->surround('?, ?, ?')
|
||||
->put([$post->id, $user->id, $score]);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setTable('post_score');
|
||||
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||
$stmt->setColumn('user_id', new Sql\Binding($user->id));
|
||||
$stmt->setColumn('score', new Sql\Binding($score));
|
||||
Database::exec($stmt);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -146,12 +152,11 @@ class UserModel extends AbstractCrudModel
|
||||
Database::transaction(function() use ($user, $post)
|
||||
{
|
||||
self::removeFromUserFavorites($user, $post);
|
||||
$query = (new SqlQuery);
|
||||
$query->insertInto('favoritee')
|
||||
->surround('post_id, user_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$post->id, $user->id]);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\InsertStatement();
|
||||
$stmt->setTable('favoritee');
|
||||
$stmt->setColumn('post_id', new Sql\Binding($post->id));
|
||||
$stmt->setColumn('user_id', new Sql\Binding($user->id));
|
||||
Database::exec($stmt);
|
||||
});
|
||||
}
|
||||
|
||||
@ -159,11 +164,12 @@ class UserModel extends AbstractCrudModel
|
||||
{
|
||||
Database::transaction(function() use ($user, $post)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('favoritee')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->and('user_id = ?')->put($user->id);
|
||||
Database::query($query);
|
||||
$stmt = new Sql\DeleteStatement();
|
||||
$stmt->setTable('favoritee');
|
||||
$stmt->setCriterion((new Sql\ConjunctionFunctor)
|
||||
->add(new Sql\EqualsFunctor('post_id', new Sql\Binding($post->id)))
|
||||
->add(new Sql\EqualsFunctor('user_id', new Sql\Binding($user->id))));
|
||||
Database::exec($stmt);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
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;
|
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,25 +1,23 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('authentication form');
|
||||
LayoutHelper::addStylesheet('auth.css');
|
||||
CustomAssetViewDecorator::setSubTitle('authentication form');
|
||||
CustomAssetViewDecorator::addStylesheet('auth.css');
|
||||
?>
|
||||
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth aligned" method="post">
|
||||
<div>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth" method="post">
|
||||
<p>If you don't have an account yet,<br/><a href="<?php echo \Chibi\UrlHelper::route('user', 'registration'); ?>">click here</a> to create a new one.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="name">User name:</label>
|
||||
<div class="form-row">
|
||||
<label for="name">User name:</label>
|
||||
<div class="input-wrapper"><input type="text" id="name" name="name"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="password">Password:</label>
|
||||
<div class="form-row">
|
||||
<label for="password">Password:</label>
|
||||
<div class="input-wrapper"><input type="password" id="password" name="password"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<div class="input-wrapper">
|
||||
<button class="submit" type="submit">Log in</button>
|
||||
|
||||
@ -35,8 +33,8 @@ LayoutHelper::addStylesheet('auth.css');
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div class="help">
|
||||
<label class="left"> </label>
|
||||
<div class="form-row help">
|
||||
<label></label>
|
||||
<div>
|
||||
<p>Problems logging in?</p>
|
||||
<ul>
|
||||
|
@ -1,20 +1,20 @@
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('comment-edit.css');
|
||||
LayoutHelper::addScript('comment-edit.js');
|
||||
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 aligned">
|
||||
<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="text">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,20 +1,20 @@
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('comment-edit.css');
|
||||
LayoutHelper::addScript('comment-edit.js');
|
||||
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 aligned">
|
||||
<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="text">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,48 +1,37 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('comments');
|
||||
CustomAssetViewDecorator::setSubTitle('comments');
|
||||
?>
|
||||
|
||||
<?php if (empty($this->context->transport->comments)): ?>
|
||||
<?php if (empty($this->context->transport->posts)): ?>
|
||||
<p class="alert alert-warning">No comments to show.</p>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('comment-list.css');
|
||||
LayoutHelper::addStylesheet('comment-small.css');
|
||||
LayoutHelper::addStylesheet('comment-edit.css');
|
||||
LayoutHelper::addScript('comment-edit.js');
|
||||
CustomAssetViewDecorator::addStylesheet('comment-list.css');
|
||||
CustomAssetViewDecorator::addStylesheet('comment-small.css');
|
||||
CustomAssetViewDecorator::addStylesheet('comment-edit.css');
|
||||
CustomAssetViewDecorator::addScript('comment-edit.js');
|
||||
?>
|
||||
|
||||
<div class="comments-wrapper">
|
||||
<div class="comments paginator-content">
|
||||
<?php
|
||||
$groups = [];
|
||||
$posts = [];
|
||||
$currentGroupPostId = null;
|
||||
$currentGroup = null;
|
||||
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): ?>
|
||||
<?php foreach ($this->context->transport->posts as $post): ?>
|
||||
<div class="comment-group">
|
||||
<div class="post-wrapper">
|
||||
<?php $this->context->post = $posts[reset($group)->postId] ?>
|
||||
<?php $this->context->post = $post ?>
|
||||
<?php echo $this->renderFile('post-small') ?>
|
||||
</div>
|
||||
<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 echo $this->renderFile('comment-small') ?>
|
||||
<?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 class="clear"></div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('comment-small.css');
|
||||
LayoutHelper::addStylesheet('comment-edit.css');
|
||||
LayoutHelper::addScript('comment-edit.js');
|
||||
CustomAssetViewDecorator::addStylesheet('comment-small.css');
|
||||
CustomAssetViewDecorator::addStylesheet('comment-edit.css');
|
||||
CustomAssetViewDecorator::addScript('comment-edit.js');
|
||||
?>
|
||||
|
||||
<div class="comment">
|
||||
@ -28,8 +28,8 @@ LayoutHelper::addScript('comment-edit.js');
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<span class="date">
|
||||
<?php echo date('Y-m-d H:i', $this->context->comment->commentDate) ?>
|
||||
<span class="date" title="<?php echo TextHelper::formatDate($this->context->comment->commentDate, true) ?>">
|
||||
<?php echo TextHelper::formatDate($this->context->comment->commentDate, false) ?>
|
||||
</span>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditComment, PrivilegesHelper::getIdentitySubPrivilege($commenter))): ?>
|
||||
|
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,12 +1,13 @@
|
||||
<?php
|
||||
LayoutHelper::setSubtitle('help');
|
||||
LayoutHelper::addStylesheet('index-help.css');
|
||||
CustomAssetViewDecorator::setSubtitle('help');
|
||||
CustomAssetViewDecorator::addStylesheet('index-help.css');
|
||||
|
||||
$tabs = $this->config->help->subTitles;
|
||||
$firstTab = !empty($tabs) ? array_keys($tabs)[0] : null;
|
||||
$showTabs = count($tabs) > 1;
|
||||
?>
|
||||
|
||||
<?php if (count($tabs) > 1): ?>
|
||||
<?php if ($showTabs): ?>
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
<?php foreach ($tabs as $tab => $text): ?>
|
||||
@ -22,6 +23,12 @@ $firstTab = !empty($tabs) ? array_keys($tabs)[0] : null;
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content">
|
||||
<?php endif ?>
|
||||
|
||||
<?php echo TextHelper::parseMarkdown(file_get_contents($this->context->path)) ?>
|
||||
|
||||
<?php if ($showTabs): ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
LayoutHelper::setSubtitle('home');
|
||||
LayoutHelper::addStylesheet('index-index.css');
|
||||
CustomAssetViewDecorator::setSubtitle('home');
|
||||
CustomAssetViewDecorator::addStylesheet('index-index.css');
|
||||
?>
|
||||
|
||||
<div id="welcome">
|
||||
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('../lib/jquery-ui/jquery-ui.css');
|
||||
LayoutHelper::addStylesheet('core.css');
|
||||
LayoutHelper::addScript('../lib/jquery/jquery.min.js');
|
||||
LayoutHelper::addScript('../lib/jquery-ui/jquery-ui.min.js');
|
||||
LayoutHelper::addScript('../lib/mousetrap/mousetrap.min.js');
|
||||
LayoutHelper::addScript('core.js');
|
||||
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>
|
||||
@ -35,29 +35,21 @@ LayoutHelper::addScript('core.js');
|
||||
|
||||
<footer>
|
||||
<div class="main-wrapper">
|
||||
<hr>
|
||||
<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>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListLogs)): ?>
|
||||
<span><a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Logs</a></span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php if ($this->config->misc->debugQueries): ?>
|
||||
<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>
|
||||
<?php endif ?>
|
||||
</footer>
|
||||
|
||||
<?php if ($this->config->misc->debugQueries): ?>
|
||||
<?php echo $this->renderFile('debug') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<div id="small-screen"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,19 +1,19 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('logs (' . $name . ')');
|
||||
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>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('logs.css');
|
||||
LayoutHelper::addScript('logs.js');
|
||||
CustomAssetViewDecorator::addStylesheet('logs.css');
|
||||
CustomAssetViewDecorator::addScript('logs.js');
|
||||
?>
|
||||
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('log', 'view', ['name' => $this->context->transport->name]) ?>" method="get">
|
||||
Keep only lines that contain:
|
||||
|
||||
<input type="text" name="query" 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>
|
||||
|
||||
<div class="paginator-content">
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
if (!isset($this->context->transport->paginator))
|
||||
return;
|
||||
|
||||
$page = $this->context->transport->paginator->page;
|
||||
$pageCount = $this->context->transport->paginator->pageCount;
|
||||
|
||||
@ -39,9 +42,9 @@ if (!function_exists('pageUrl'))
|
||||
|
||||
<?php if (!empty($pagesVisible)): ?>
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('paginator.css');
|
||||
CustomAssetViewDecorator::addStylesheet('paginator.css');
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
LayoutHelper::addScript('paginator-endless.js');
|
||||
CustomAssetViewDecorator::addScript('paginator-endless.js');
|
||||
?>
|
||||
|
||||
<nav class="paginator-wrapper">
|
||||
|
@ -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">
|
||||
<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>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<div class="form-row safety">
|
||||
<label>Safety:</label>
|
||||
<div class="input-wrapper">
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<label>
|
||||
<input type="radio" name="safety" value="<?php echo $safety ?>" <?php if ($this->context->transport->post->safety == $safety) echo 'checked="checked"' ?>/>
|
||||
@ -10,45 +11,46 @@
|
||||
</label>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="tags">
|
||||
<label class="left" for="tags">Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags…" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->getTags())) ?>"/></div>
|
||||
<div class="form-row tags">
|
||||
<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 htmlspecialchars($tag->name); }, $this->context->transport->post->getTags())) ?>"/></div>
|
||||
</div>
|
||||
<input type="hidden" name="edit-token" id="edit-token" value="<?php echo $this->context->transport->post->getEditToken() ?>"/>
|
||||
<input type="hidden" name="edit-token" id="edit-token" value="<?php echo htmlspecialchars($this->context->transport->post->getEditToken()) ?>"/>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="source">
|
||||
<label class="left" for="source">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" id="source" value="<?php echo $this->context->transport->post->source ?>"/></div>
|
||||
<div class="form-row source">
|
||||
<label for="source">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" id="source" value="<?php echo htmlspecialchars($this->context->transport->post->source) ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="relations">Relations:</label>
|
||||
<div class="form-row thumb">
|
||||
<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>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="url">
|
||||
<label class="left" for="url">File:</label>
|
||||
<div class="form-row url">
|
||||
<label for="url">File:</label>
|
||||
<div class="input-wrapper"><input type="text" name="url" id="url" placeholder="Some url…"/></div>
|
||||
</div>
|
||||
|
||||
<div class="file">
|
||||
<label class="left" for="file"></label>
|
||||
<div class="form-row file">
|
||||
<label for="file"></label>
|
||||
<div class="input-wrapper"><input type="file" name="file" id="file"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="thumb">Thumb:</label>
|
||||
<div class="form-row thumb">
|
||||
<label for="thumb">Thumb:</label>
|
||||
<div class="input-wrapper">
|
||||
<input type="file" name="thumb" id="thumb"/>
|
||||
<?php if ($this->context->transport->post->hasCustomThumb()): ?>
|
||||
@ -60,8 +62,8 @@
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php LayoutHelper::setPageThumb(\Chibi\UrlHelper::route('post', 'thumb', ['name' => $this->context->transport->post->name])) ?>
|
||||
<?php CustomAssetViewDecorator::setPageThumb(\Chibi\UrlHelper::route('post', 'thumb', ['name' => $this->context->transport->post->name])) ?>
|
||||
<?php $post = $this->context->transport->post ?>
|
||||
|
||||
<?php if ($post->type == PostType::Image): ?>
|
||||
@ -15,10 +15,13 @@
|
||||
|
||||
<?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): ?>
|
||||
|
||||
<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 ?>
|
||||
|
@ -1,11 +1,20 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('posts');
|
||||
CustomAssetViewDecorator::setSubTitle('posts');
|
||||
|
||||
$tabs = [];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['All posts', \Chibi\UrlHelper::route('post', 'list')];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['Random', \Chibi\UrlHelper::route('post', 'random')];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts)) $tabs []= ['Favorites', \Chibi\UrlHelper::route('post', 'favorites')];
|
||||
if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs []= ['Mass tag', \Chibi\UrlHelper::route('post', 'list', ['query' => isset($this->context->transport->searchQuery) ? htmlspecialchars($this->context->transport->searchQuery) : '', 'source' => 'mass-tag', 'page' => $this->context->transport->paginator->page])];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts))
|
||||
$tabs []= ['All posts', \Chibi\UrlHelper::route('post', 'list')];
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts))
|
||||
$tabs []= ['Random', \Chibi\UrlHelper::route('post', 'random')];
|
||||
if (PrivilegesHelper::confirm(Privilege::ListPosts))
|
||||
$tabs []= ['Favorites', \Chibi\UrlHelper::route('post', 'favorites')];
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::MassTag))
|
||||
$tabs []= ['Mass tag', \Chibi\UrlHelper::route('post', 'list', [
|
||||
'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;
|
||||
if ($this->context->route->simpleActionName == 'random') $activeTab = 1;
|
||||
@ -30,4 +39,6 @@ if ($this->context->source == 'mass-tag') $activeTab = 3;
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content">
|
||||
<?php $this->renderFile('post-list') ?>
|
||||
</div>
|
||||
|
@ -1,13 +1,16 @@
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('post-list.css');
|
||||
LayoutHelper::addScript('post-list.js');
|
||||
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 $this->renderFile('tag-mass-tag') ?>
|
||||
<?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>
|
||||
<?php else: ?>
|
||||
<div class="posts-wrapper">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('post-small.css');
|
||||
CustomAssetViewDecorator::addStylesheet('post-small.css');
|
||||
|
||||
$classNames =
|
||||
[
|
||||
|
@ -1,9 +1,9 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('upload');
|
||||
LayoutHelper::addStylesheet('post-upload.css');
|
||||
LayoutHelper::addScript('post-upload.js');
|
||||
LayoutHelper::addStylesheet('../lib/tagit/jquery.tagit.css');
|
||||
LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
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">
|
||||
@ -18,38 +18,18 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
|
||||
<div id="inner-content">
|
||||
<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">
|
||||
<input type=file multiple style="display: none"/>
|
||||
<div id="file-handler">
|
||||
Drop files here!<br>
|
||||
Or just click on this box.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab url">
|
||||
<div id="url-handler-wrapper">
|
||||
<div id="url-handler">
|
||||
<div class="input-wrapper"><textarea placeholder="Paste some URLs here, one per line." name="urls"></textarea></div>
|
||||
</div>
|
||||
<button class="submit" type="submit">Add</button>
|
||||
<div class="input-wrapper"><input placeholder="Alternatively, paste an URL here." name="url"/></div>
|
||||
<button class="submit" type="submit">Add URL</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -87,14 +67,15 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>" method="post" class="aligned">
|
||||
<div class="file-name">
|
||||
<label class="left">File:</label>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'upload') ?>" method="post">
|
||||
<div class="form-row file-name">
|
||||
<label>File:</label>
|
||||
<strong>filename.jpg</strong>
|
||||
</div>
|
||||
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<div class="form-row safety">
|
||||
<label>Safety:</label>
|
||||
<div class="input-wrapper">
|
||||
<?php $checked = false ?>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<label>
|
||||
@ -108,16 +89,16 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
<input type="checkbox" name="anonymous" value="1"/>
|
||||
Upload anonymously
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tags">
|
||||
<label class="left">Tags:</label>
|
||||
<div class="form-row tags">
|
||||
<label>Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" placeholder="enter some tags…"/></div>
|
||||
</div>
|
||||
|
||||
<div class="source">
|
||||
<label class="left">Source:</label>
|
||||
<div class="form-row source">
|
||||
<label>Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" placeholder="where did you get this from? (optional)"/></div>
|
||||
</div>
|
||||
|
||||
@ -126,3 +107,5 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img id="lightbox" src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="Preview"/>
|
||||
|
@ -1,9 +1,23 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('showing ' . TextHelper::reprPost($this->context->transport->post) . ' – ' . TextHelper::reprTags($this->context->transport->post->getTags()));
|
||||
LayoutHelper::addStylesheet('post-view.css');
|
||||
LayoutHelper::addScript('post-view.js');
|
||||
LayoutHelper::addStylesheet('../lib/tagit/jquery.tagit.css');
|
||||
LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
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">
|
||||
@ -41,9 +55,9 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
</nav>
|
||||
|
||||
<div class="unit tags">
|
||||
<h1>tags (<?php echo count($this->context->transport->post->getTags()) ?>)</h1>
|
||||
<ul>
|
||||
<?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 foreach ($tags as $tag): ?>
|
||||
<li title="<?php echo $tag->name ?>">
|
||||
@ -61,13 +75,12 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
<div class="unit details">
|
||||
<h1>details</h1>
|
||||
|
||||
<div class="key-value uploader">
|
||||
<span class="key">Uploader:</span>
|
||||
<div class="uploader">
|
||||
<?php $uploader = $this->context->transport->post->getUploader() ?>
|
||||
<?php if ($uploader): ?>
|
||||
<span class="value" title="<?php echo $val = $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 ?>
|
||||
</a>
|
||||
</span>
|
||||
@ -77,6 +90,10 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
<?php echo UserModel::getAnonymousName() ?>
|
||||
</span>
|
||||
<?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 class="key-value safety">
|
||||
@ -86,12 +103,34 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
</span>
|
||||
</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">
|
||||
<span class="key">Score:</span>
|
||||
<span class="value">
|
||||
<?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]); } ?>
|
||||
|
||||
@ -115,51 +154,49 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value date">
|
||||
<span class="key">Date:</span>
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d H:i', $this->context->transport->post->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 class="unit hl-options">
|
||||
<?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">
|
||||
<i class="icon-dl"></i>
|
||||
<span class="ext">
|
||||
<?php $mimes = ['image/jpeg' => 'JPG', 'image/gif' => 'GIF', 'image/png' => 'PNG', 'application/x-shockwave-flash' => 'SWF'] ?>
|
||||
<?php $mime = $this->context->transport->post->mimeType ?>
|
||||
<?php echo isset($mimes[$mime]) ? $mimes[$mime] : 'unknown' ?>
|
||||
</span>
|
||||
<span class="size">
|
||||
<?php echo TextHelper::useBytesUnits($this->context->transport->post->fileSize) ?>
|
||||
<span>
|
||||
<?php
|
||||
printf(
|
||||
'Download %s (%s)',
|
||||
strtoupper(TextHelper::resolveMimeType($this->context->transport->post->mimeType)) ?: 'Unknown',
|
||||
TextHelper::useBytesUnits($this->context->transport->post->fileSize));
|
||||
?>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<?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>
|
||||
|
||||
<?php if (count($this->context->transport->post->getFavorites()) > 0): ?>
|
||||
@ -178,7 +215,7 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (count($this->context->transport->post->getRelations())): ?>
|
||||
<div class="relations unit">
|
||||
<div class="unit relations">
|
||||
<h1>related</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->getRelations() as $relatedPost): ?>
|
||||
@ -193,53 +230,43 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
<?php endif ?>
|
||||
|
||||
<?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 = [];
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::FavoritePost))
|
||||
{
|
||||
if (!$this->context->favorite)
|
||||
if (PrivilegesHelper::confirm(Privilege::FeaturePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
'class' => 'add-fav',
|
||||
'text' => 'Add to favorites',
|
||||
'simple-action' => \Chibi\UrlHelper::route('post', 'add-favorite', ['id' => $this->context->transport->post->id]),
|
||||
'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, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||
{
|
||||
if ($this->context->flagged)
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
'class' => 'flag',
|
||||
'text' => 'Flagged',
|
||||
'inactive' => true,
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
'class' => 'rem-fav',
|
||||
'text' => 'Remove from favorites',
|
||||
'simple-action' => \Chibi\UrlHelper::route('post', 'rem-favorite', ['id' => $this->context->transport->post->id]),
|
||||
'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 ($canEditAnything)
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
'class' => 'edit',
|
||||
'text' => 'Edit',
|
||||
];
|
||||
}
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||
{
|
||||
if ($this->context->transport->post->hidden)
|
||||
@ -262,41 +289,6 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
}
|
||||
}
|
||||
|
||||
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())))
|
||||
{
|
||||
$options []=
|
||||
@ -315,23 +307,23 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
</div>
|
||||
|
||||
<div id="inner-content">
|
||||
<div class="post-wrapper post-type-<?php echo strtolower(PostType::toString($this->context->transport->post->type)) ?>">
|
||||
<?php echo $this->renderFile('post-file-render') ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canEditAnything): ?>
|
||||
<div class="edit-post unit">
|
||||
<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)) ?>">
|
||||
<?php echo $this->renderFile('post-file-render') ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('comment-list.css');
|
||||
LayoutHelper::addStylesheet('comment-small.css');
|
||||
CustomAssetViewDecorator::addStylesheet('comment-list.css');
|
||||
CustomAssetViewDecorator::addStylesheet('comment-small.css');
|
||||
?>
|
||||
<div class="comments-wrapper">
|
||||
<?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>
|
||||
<div class="comments">
|
||||
<?php foreach ($this->context->transport->post->getComments() as $comment): ?>
|
||||
@ -344,7 +336,7 @@ LayoutHelper::addScript('../lib/tagit/jquery.tagit.js');
|
||||
</div>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::AddComment)): ?>
|
||||
<div class="unit">
|
||||
<div class="unit comment-add">
|
||||
<?php $this->renderFile('comment-add') ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
@ -1,15 +1,16 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('tags');
|
||||
LayoutHelper::addStylesheet('tag-list.css');
|
||||
CustomAssetViewDecorator::setSubTitle('tags');
|
||||
CustomAssetViewDecorator::addStylesheet('tag-list.css');
|
||||
|
||||
$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 (count(array_diff($tabs, ['list'])) > 1): ?>
|
||||
<?php if ($showTabs): ?>
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
<?php foreach ($tabs as $tab => $name): ?>
|
||||
@ -25,6 +26,8 @@ if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs['mass-tag-redirect'] =
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content">
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->route->simpleActionName == 'merge'): ?>
|
||||
@ -42,3 +45,7 @@ if (PrivilegesHelper::confirm(Privilege::MassTag)) $tabs['mass-tag-redirect'] =
|
||||
<?php if ($this->context->route->simpleActionName == 'mass-tag-redirect'): ?>
|
||||
<?php $this->renderFile('tag-mass-tag') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($showTabs): ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
@ -1,19 +1,16 @@
|
||||
<nav class="sort-styles">
|
||||
<ul>
|
||||
<?php
|
||||
$sortStyles =
|
||||
$filters =
|
||||
[
|
||||
'order:alpha,asc' => 'Sort A→Z',
|
||||
'order:alpha,desc' => 'Sort Z→A',
|
||||
'order:popularity,desc' => 'Often used first',
|
||||
'order:popularity,asc' => 'Rarely used first',
|
||||
];
|
||||
|
||||
if ($this->config->registration->staffActivation)
|
||||
$sortStyles['pending'] = 'Pending staff review';
|
||||
?>
|
||||
|
||||
<?php foreach ($sortStyles as $key => $text): ?>
|
||||
<?php foreach ($filters as $key => $text): ?>
|
||||
<?php if ($this->context->filter == $key): ?>
|
||||
<li class="active">
|
||||
<?php else: ?>
|
||||
@ -28,11 +25,11 @@
|
||||
<?php if (empty($this->context->transport->tags)): ?>
|
||||
<p class="alert alert-warning">No tags to show.</p>
|
||||
<?php else: ?>
|
||||
<?php $max = max([0]+array_map(function($x) { return $x['post_count']; }, $this->context->transport->tags)); ?>
|
||||
<?php $max = $this->context->highestUsage ?>
|
||||
<?php $add = 0. ?>
|
||||
<?php $mul = 10. / max(1, log(max(1, $max))) ?>
|
||||
<?php $url = \Chibi\UrlHelper::route('post', 'list', ['query' => '_query_']) ?>
|
||||
<div class="tags">
|
||||
<div class="tags paginator-content">
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->tags as $tag): ?>
|
||||
<?php $name = $tag['name'] ?>
|
||||
@ -45,4 +42,6 @@
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php $this->renderFile('paginator') ?>
|
||||
<?php endif ?>
|
||||
|
@ -1,20 +1,23 @@
|
||||
<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>
|
||||
<div>
|
||||
<label class="left" 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) ? $this->context->massTagQuery : '' ?>" data-autocomplete-url="<?php echo \Chibi\UrlHelper::route('tag', 'list') ?>"/></div>
|
||||
|
||||
<div class="form-row">
|
||||
<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>
|
||||
<label class="left" 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="form-row">
|
||||
<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) ? htmlspecialchars($this->context->massTagTag) : '' ?>"/></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"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Tag!</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,20 +1,23 @@
|
||||
<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>
|
||||
<div>
|
||||
<label class="left" for="merge-source-tag">Source tag:</label>
|
||||
<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>
|
||||
|
||||
<div class="form-row">
|
||||
<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>
|
||||
<label class="left" 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="form-row">
|
||||
<label for="merge-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input class="autocomplete" type="text" name="target-tag" id="merge-target-tag"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Merge!</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,20 +1,23 @@
|
||||
<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>
|
||||
<div>
|
||||
<label class="left" for="rename-source-tag">Source tag:</label>
|
||||
<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>
|
||||
|
||||
<div class="form-row">
|
||||
<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>
|
||||
<label class="left" for="rename-target-tag">Target tag:</label>
|
||||
<div class="form-row">
|
||||
<label for="rename-target-tag">Target tag:</label>
|
||||
<div class="input-wrapper"><input type="text" name="target-tag" id="rename-target-tag"/></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Rename!</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -127,7 +127,7 @@
|
||||
|
||||
<li class="search">
|
||||
<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>
|
||||
</li>
|
||||
</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): ?>
|
||||
<div class="current-password">
|
||||
<label class="left" for="current-password">Current password:</label>
|
||||
<div class="form-row current-password">
|
||||
<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>
|
||||
<?php endif ?>
|
||||
@ -10,8 +10,8 @@
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Delete account</button>
|
||||
</div>
|
||||
</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): ?>
|
||||
<div class="current-password">
|
||||
<label class="left" for="current-password">Current password:</label>
|
||||
<div class="form-row current-password">
|
||||
<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>
|
||||
<hr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="nickname">
|
||||
<label class="left" for="name">Name:</label>
|
||||
<div class="input-wrapper"><input type="text" name="name" id="name" placeholder="New name…" value="<?php echo $this->context->suppliedName ?>"/></div>
|
||||
<div class="form-row nickname">
|
||||
<label for="name">Name:</label>
|
||||
<div class="input-wrapper"><input type="text" name="name" id="name" placeholder="New name…" value="<?php echo htmlspecialchars($this->context->suppliedName) ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="email">
|
||||
<label class="left" for="name">E-mail:</label>
|
||||
<div class="input-wrapper"><input type="text" name="email" id="email" placeholder="New e-mail…" value="<?php echo $this->context->suppliedEmail ?>"/></div>
|
||||
<div class="form-row email">
|
||||
<label for="name">E-mail:</label>
|
||||
<div class="input-wrapper"><input type="text" name="email" id="email" placeholder="New e-mail…" value="<?php echo htmlspecialchars($this->context->suppliedEmail) ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="password1">
|
||||
<label class="left" for="password1">New password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="password1" id="password1" placeholder="New password…" value="<?php echo $this->context->suppliedPassword1 ?>"/></div>
|
||||
<div class="form-row password1">
|
||||
<label for="password1">New password:</label>
|
||||
<div class="input-wrapper"><input type="password" name="password1" id="password1" placeholder="New password…" value="<?php echo htmlspecialchars($this->context->suppliedPassword1) ?>"/></div>
|
||||
</div>
|
||||
<div class="password2">
|
||||
<label class="left" for="password2"></label>
|
||||
<div class="input-wrapper"><input type="password" name="password2" id="password2" placeholder="New password… (repeat)" value="<?php echo $this->context->suppliedPassword2 ?>"/></div>
|
||||
<div class="form-row password2">
|
||||
<label for="password2"></label>
|
||||
<div class="input-wrapper"><input type="password" name="password2" id="password2" placeholder="New password… (repeat)" value="<?php echo htmlspecialchars($this->context->suppliedPassword2) ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="access-rank">
|
||||
<label class="left" for="access-rank">Access rank:</label>
|
||||
<div class="form-row access-rank">
|
||||
<label for="access-rank">Access rank:</label>
|
||||
<div class="input-wrapper"><select name="access-rank" id="access-rank">
|
||||
<?php foreach (AccessRank::getAll() as $rank): ?>
|
||||
<?php if ($rank == AccessRank::Nobody) continue ?>
|
||||
@ -54,8 +54,8 @@
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,33 +1,33 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('users');
|
||||
LayoutHelper::addStylesheet('user-list.css');
|
||||
LayoutHelper::addStylesheet('paginator.css');
|
||||
CustomAssetViewDecorator::setSubTitle('users');
|
||||
CustomAssetViewDecorator::addStylesheet('user-list.css');
|
||||
CustomAssetViewDecorator::addStylesheet('paginator.css');
|
||||
if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
LayoutHelper::addScript('paginator-endless.js');
|
||||
CustomAssetViewDecorator::addScript('paginator-endless.js');
|
||||
?>
|
||||
|
||||
<nav class="sort-styles">
|
||||
<ul>
|
||||
<?php
|
||||
$sortStyles =
|
||||
$filters =
|
||||
[
|
||||
'alpha,asc' => 'Sort A→Z',
|
||||
'alpha,desc' => 'Sort Z→A',
|
||||
'date,asc' => 'Sort old→new',
|
||||
'date,desc' => 'Sort new→old',
|
||||
'order:alpha,asc' => 'Sort A→Z',
|
||||
'order:alpha,desc' => 'Sort Z→A',
|
||||
'order:date,asc' => 'Sort old→new',
|
||||
'order:date,desc' => 'Sort new→old',
|
||||
];
|
||||
|
||||
if ($this->config->registration->staffActivation)
|
||||
$sortStyles['pending'] = 'Pending staff review';
|
||||
$filters['pending'] = 'Pending staff review';
|
||||
?>
|
||||
|
||||
<?php foreach ($sortStyles as $key => $text): ?>
|
||||
<?php if ($this->context->sortStyle == $key): ?>
|
||||
<?php foreach ($filters as $key => $text): ?>
|
||||
<?php if ($this->context->filter == $key): ?>
|
||||
<li class="active">
|
||||
<?php else: ?>
|
||||
<li>
|
||||
<?php endif ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'list', ['sortStyle' => $key]) ?>"><?php echo $text ?></a>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'list', ['filter' => $key]) ?>"><?php echo $text ?></a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
@ -36,10 +36,11 @@ if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
<?php if (empty($this->context->transport->users)): ?>
|
||||
<p class="alert alert-warning">No users to show.</p>
|
||||
<?php else: ?>
|
||||
<div class="users-wrapper">
|
||||
<div class="users paginator-content">
|
||||
<?php foreach ($this->context->transport->users as $user): ?>
|
||||
<div class="user">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $user->name]) ?>">
|
||||
<a class="avatar" href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $user->name]) ?>">
|
||||
<img src="<?php echo htmlspecialchars($user->getAvatarUrl(100)) ?>" alt="<?php echo $user->name ?>"/>
|
||||
</a>
|
||||
<div class="details">
|
||||
@ -49,13 +50,22 @@ if ($this->context->user->hasEnabledEndlessScrolling())
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<div class="date-registered">Date registered: <?php echo date('Y-m-d H:i', $user->joinDate) ?></div>
|
||||
<div class="fav-count">Favorite count: <?php echo $user->getFavoriteCount() ?></div>
|
||||
<div class="post-count">Post count: <?php echo $user->getPostCount() ?></div>
|
||||
<div class="date-registered" title="<?php echo TextHelper::formatDate($user->joinDate, true) ?>">
|
||||
Registered: <?php echo TextHelper::formatDate($user->joinDate, false) ?>
|
||||
</div>
|
||||
|
||||
<div class="post-count">
|
||||
Uploaded: <?php echo TextHelper::useDecimalUnits($user->getPostCount()) ?>
|
||||
</div>
|
||||
|
||||
<div class="fav-count">
|
||||
Favorites: <?php echo TextHelper::useDecimalUnits($user->getFavoriteCount()) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $this->renderFile('paginator') ?>
|
||||
<?php endif ?>
|
||||
|
@ -1,51 +1,45 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle('registration form');
|
||||
CustomAssetViewDecorator::setSubTitle('registration form');
|
||||
?>
|
||||
|
||||
<?php if ($this->context->transport->success === true): ?>
|
||||
<?php $this->renderFile('message') ?>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('auth.css');
|
||||
CustomAssetViewDecorator::addStylesheet('auth.css');
|
||||
?>
|
||||
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'register') ?>" class="auth aligned" method="post">
|
||||
<div>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'register') ?>" class="auth" method="post">
|
||||
<p>Registered users can view more content,<br/>upload files and add posts to favorites.</p>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="name">User name:</label>
|
||||
<div class="input-wrapper"><input type="text" id="name" name="name" value="<?php echo htmlspecialchars($this->context->suppliedName) ?>" placeholder="e.g. darth_vader" autocomplete="off"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="name">User name:</label>
|
||||
<div class="input-wrapper"><input type="text" id="name" name="name" value="<?php echo $this->context->suppliedName ?>" placeholder="e.g. darth_vader" autocomplete="off"/></div>
|
||||
<div class="form-row">
|
||||
<label for="password1">Password:</label>
|
||||
<div class="input-wrapper"><input type="password" id="password1" name="password1" value="<?php echo htmlspecialchars($this->context->suppliedPassword1) ?>" placeholder="e.g. <?php echo str_repeat('●', 8) ?>" autocomplete="off"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="password1">Password:</label>
|
||||
<div class="input-wrapper"><input type="password" id="password1" name="password1" value="<?php echo $this->context->suppliedPassword1 ?>" placeholder="e.g. <?php echo str_repeat('●', 8) ?>" autocomplete="off"/></div>
|
||||
<div class="form-row">
|
||||
<label for="password2">Password (repeat):</label>
|
||||
<div class="input-wrapper"><input type="password" id="password2" name="password2" value="<?php echo htmlspecialchars($this->context->suppliedPassword2) ?>" placeholder="e.g. <?php echo str_repeat('●', 8) ?>" autocomplete="off"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="password2">Password (repeat):</label>
|
||||
<div class="input-wrapper"><input type="password" id="password2" name="password2" value="<?php echo $this->context->suppliedPassword2 ?>" placeholder="e.g. <?php echo str_repeat('●', 8) ?>" autocomplete="off"/></div>
|
||||
<div class="form-row">
|
||||
<label for="email">E-mail address:</label>
|
||||
<div class="input-wrapper"><input type="text" id="email" name="email" value="<?php echo htmlspecialchars($this->context->suppliedEmail) ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="left" for="email">E-mail address:</label>
|
||||
<div class="input-wrapper"><input type="text" id="email" name="email" value="<?php echo $this->context->suppliedEmail ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p id="email-info">Your e-mail will be used to show your <a href="http://gravatar.com/">Gravatar</a>.<br/>Leave blank for random Gravatar.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<?php $this->renderFile('message') ?>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="submit" value="1"/>
|
||||
|
||||
<div>
|
||||
<label class="left"></label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -2,12 +2,12 @@
|
||||
<?php $this->renderFile('message') ?>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
LayoutHelper::addStylesheet('auth.css');
|
||||
CustomAssetViewDecorator::addStylesheet('auth.css');
|
||||
?>
|
||||
|
||||
<form action="<?php echo \Chibi\UrlHelper::route($this->context->route->simpleControllerName, $this->context->route->simpleActionName) ?>" method="post" class="auth aligned" autocomplete="off">
|
||||
<div>
|
||||
<label class="left">User:</label>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route($this->context->route->simpleControllerName, $this->context->route->simpleActionName) ?>" method="post" class="auth" autocomplete="off">
|
||||
<div class="form-row">
|
||||
<label>User:</label>
|
||||
<div class="input-wrapper">
|
||||
<input name="name" placeholder="Name or e-mail address" type="text"/>
|
||||
</div>
|
||||
@ -17,8 +17,8 @@
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Continue</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'settings', ['name' => $this->context->transport->user->name]) ?>" method="post" class="settings aligned">
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<form action="<?php echo \Chibi\UrlHelper::route('user', 'settings', ['name' => $this->context->transport->user->name]) ?>" method="post" class="settings">
|
||||
<div class="form-row safety">
|
||||
<label>Safety:</label>
|
||||
<div class="input-wrapper">
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety))): ?>
|
||||
@ -22,8 +22,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endless-scrolling">
|
||||
<label class="left" for="endless-scrolling">Endless scrolling:</label>
|
||||
<div class="form-row endless-scrolling">
|
||||
<label for="endless-scrolling">Endless scrolling:</label>
|
||||
<div class="input-wrapper">
|
||||
<label>
|
||||
<?php
|
||||
@ -41,8 +41,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-tag-titles">
|
||||
<label class="left" for="post-tag-titles">Tags in thumbs:</label>
|
||||
<div class="form-row post-tag-titles">
|
||||
<label for="post-tag-titles">Tags in thumbs:</label>
|
||||
<div class="input-wrapper">
|
||||
<label>
|
||||
<?php
|
||||
@ -60,8 +60,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hide-disliked-posts">
|
||||
<label class="left" for="hide-disliked-posts">Hide down-voted:</label>
|
||||
<div class="form-row hide-disliked-posts">
|
||||
<label for="hide-disliked-posts">Hide down-voted:</label>
|
||||
<div class="input-wrapper">
|
||||
<label>
|
||||
<?php
|
||||
@ -83,8 +83,8 @@
|
||||
|
||||
<?php $this->renderFile('message') ?>
|
||||
|
||||
<div>
|
||||
<label class="left"> </label>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<button class="submit" type="submit">Update settings</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
LayoutHelper::setSubTitle($this->context->transport->user->name);
|
||||
LayoutHelper::addStylesheet('user-view.css');
|
||||
CustomAssetViewDecorator::setSubTitle($this->context->transport->user->name);
|
||||
CustomAssetViewDecorator::addStylesheet('user-view.css');
|
||||
?>
|
||||
|
||||
<div id="sidebar">
|
||||
@ -16,23 +16,33 @@ LayoutHelper::addStylesheet('user-view.css');
|
||||
|
||||
<div class="key-value join-date">
|
||||
<span class="key">Joined:</span>
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d', $this->context->transport->user->joinDate) ?>"><?php echo $val ?></span>
|
||||
<span class="value" title="<?php echo TextHelper::formatDate($this->context->transport->user->joinDate, true) ?>">
|
||||
<?php echo TextHelper::formatDate($this->context->transport->user->joinDate, false) ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value last-login">
|
||||
<span class="key">Last login:</span>
|
||||
<span class="value" title="<?php echo $val = $this->context->transport->user->lastLoginDate ? date('Y-m-d', $this->context->transport->user->lastLoginDate) : 'Unknown' ?>"><?php echo $val ?></span>
|
||||
<span class="value" title="<?php echo TextHelper::formatDate($this->context->transport->user->lastLoginDate, true) ?>">
|
||||
<?php echo TextHelper::formatDate($this->context->transport->user->lastLoginDate, false) ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="key-value access-rank">
|
||||
<span class="key">Access rank:</span>
|
||||
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(AccessRank::toString($this->context->transport->user->accessRank)) ?>"><?php echo $val ?></span>
|
||||
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(AccessRank::toString($this->context->transport->user->accessRank)) ?>">
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ViewUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="key-value email">
|
||||
<span class="key">E-mail:</span>
|
||||
<span class="value" title="<?php echo $val = ($this->context->transport->user->emailUnconfirmed ? '(unconfirmed) ' . $this->context->transport->user->emailUnconfirmed : $this->context->transport->user->emailConfirmed ?: 'none specified') ?>"><?php echo $val ?></span>
|
||||
<span class="value" title="<?php echo $val = ($this->context->transport->user->emailUnconfirmed
|
||||
? '(unconfirmed) ' . $this->context->transport->user->emailUnconfirmed
|
||||
: $this->context->transport->user->emailConfirmed ?: 'none specified') ?>">
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
<br>(only you and staff can see this)
|
||||
</div>
|
||||
<?php endif ?>
|
||||
@ -66,17 +76,17 @@ LayoutHelper::addStylesheet('user-view.css');
|
||||
];
|
||||
}
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::DeleteUser, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user)))
|
||||
if (PrivilegesHelper::confirm(Privilege::AcceptUserRegistration) and !$this->context->transport->user->staffConfirmed and $this->config->registration->staffActivation)
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
'class' => 'delete',
|
||||
'text' => 'Delete account',
|
||||
'link' => \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name, 'tab' => 'delete']),
|
||||
'class' => 'accept-registration',
|
||||
'text' => 'Accept registration',
|
||||
'simple-action' => \Chibi\UrlHelper::route('user', 'accept-registration', ['name' => $this->context->transport->user->name]),
|
||||
];
|
||||
}
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::FlagUser))
|
||||
if (PrivilegesHelper::confirm(Privilege::FlagUser, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user)))
|
||||
{
|
||||
if ($this->context->flagged)
|
||||
{
|
||||
@ -108,7 +118,7 @@ LayoutHelper::addStylesheet('user-view.css');
|
||||
'class' => 'ban',
|
||||
'text' => 'Ban user',
|
||||
'simple-action' => \Chibi\UrlHelper::route('user', 'ban', ['name' => $this->context->transport->user->name]),
|
||||
'data-confirm-text' => 'Are you sure?',
|
||||
'data-confirm-text' => 'Are you sure you want to ban this user?',
|
||||
];
|
||||
}
|
||||
else
|
||||
@ -118,18 +128,18 @@ LayoutHelper::addStylesheet('user-view.css');
|
||||
'class' => 'unban',
|
||||
'text' => 'Unban user',
|
||||
'simple-action' => \Chibi\UrlHelper::route('user', 'unban', ['name' => $this->context->transport->user->name]),
|
||||
'data-confirm-text' => 'Are you sure?',
|
||||
'data-confirm-text' => 'Are you sure you want to unban this user?',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::AcceptUserRegistration) and !$this->context->transport->user->staffConfirmed and $this->config->registration->staffActivation)
|
||||
if (PrivilegesHelper::confirm(Privilege::DeleteUser, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user)))
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
'class' => 'accept-registration',
|
||||
'text' => 'Accept registration',
|
||||
'simple-action' => \Chibi\UrlHelper::route('user', 'accept-registration', ['name' => $this->context->transport->user->name]),
|
||||
'class' => 'delete',
|
||||
'text' => 'Delete account',
|
||||
'link' => \Chibi\UrlHelper::route('user', 'delete', ['name' => $this->context->transport->user->name, 'tab' => 'delete']),
|
||||
];
|
||||
}
|
||||
|
||||
@ -199,6 +209,7 @@ LayoutHelper::addStylesheet('user-view.css');
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content">
|
||||
<?php if (isset($this->context->transport->posts)): ?>
|
||||
<?php $this->renderFile('post-list') ?>
|
||||
<?php endif ?>
|
||||
@ -210,5 +221,6 @@ LayoutHelper::addStylesheet('user-view.css');
|
||||
<?php elseif ($this->context->transport->tab == 'delete'): ?>
|
||||
<?php $this->renderFile('user-delete') ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
10
src/core.php
10
src/core.php
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
define('SZURU_VERSION', '0.6.1');
|
||||
define('SZURU_VERSION', '0.7.0');
|
||||
define('SZURU_LINK', 'http://github.com/rr-/szurubooru');
|
||||
|
||||
//basic settings and preparation
|
||||
@ -13,7 +13,7 @@ ini_set('memory_limit', '128M');
|
||||
//basic include calls, autoloader init
|
||||
require_once $rootDir . 'lib' . DS . 'php-markdown' . DS . 'Michelf' . DS . 'Markdown.php';
|
||||
require_once $rootDir . 'lib' . DS . 'chibi-core' . DS . 'Facade.php';
|
||||
\Chibi\AutoLoader::init(__DIR__);
|
||||
\Chibi\AutoLoader::init([__DIR__, $rootDir . 'lib' . DS . 'chibi-sql']);
|
||||
|
||||
//load config manually
|
||||
$configPaths =
|
||||
@ -39,7 +39,11 @@ $context = \Chibi\Registry::getContext();
|
||||
$context->startTime = $startTime;
|
||||
$context->rootDir = $rootDir;
|
||||
|
||||
Database::connect($config->main->dbDriver, TextHelper::absolutePath($config->main->dbLocation), $config->main->dbUser, $config->main->dbPass);
|
||||
\Chibi\Database::connect(
|
||||
$config->main->dbDriver,
|
||||
TextHelper::absolutePath($config->main->dbLocation),
|
||||
$config->main->dbUser,
|
||||
$config->main->dbPass);
|
||||
|
||||
//wire models
|
||||
foreach (\Chibi\AutoLoader::getAllIncludablePaths() as $path)
|
||||
|
@ -3,8 +3,15 @@ require_once 'src/core.php';
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
function getDbVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
$dbVersion = PropertyModel::get(PropertyModel::DbVersion);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return [null, null];
|
||||
}
|
||||
if (strpos($dbVersion, '.') !== false)
|
||||
{
|
||||
list ($dbVersionMajor, $dbVersionMinor) = explode('.', $dbVersion);
|
||||
@ -50,7 +57,7 @@ foreach ($upgrades as $upgradePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
Database::query((new SqlQuery)->raw($query));
|
||||
\Chibi\Database::exec(new \Chibi\Sql\RawStatement($query));
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
|
Reference in New Issue
Block a user