@ -1,25 +1,60 @@
<?php /*
<?php
Copyright © 2022 Pk11
/* Copyright © 2022 Pk11
*
Permission is hereby granted, free of charge, to any person obtaining a
* Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
* copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation
* to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
* and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
* Software is furnished to do so, subject to the following conditions:
*
The above copyright notice and this permission notice shall be included in
* The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
* all copies or substantial portions of the Software.
*
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
* DEALINGS IN THE SOFTWARE.
*/ ?>
*/
// Return source code
if(isset($_GET['source'])) {
header("Content-Type: text/plain");
die(file_get_contents(basename($_SERVER['PHP_SELF'])));
}
/* vars.php should contain the following, but with your variables as needed:
*
* // Application constants
* define('UPLOAD_PATH', 'image/'); // Folder to save images to
* define('MAX_FILE_SIZE', 8 < < 20 ) ; / / Maximum image size , 8 MiB
* define('ADMIN_ID', '< SHA1 uid from your cookies > '); // uid that is able to delete anything
* define('UID_SALT', '< some string to salt uids with > ');
* define('MAX_POSTS', 50); // Max posts after which old posts will be auto deleted. Only existing posts count, not manually deleted ones.
* define('DISCORD_WEBHOOK', 'https://discord.com/api/webhooks/< webhook id > /< webhook token > '); // A message will be sent to this webhook on post, for easier moderation
*
* // Database constants for PostgreSQL database
* $DB_HOST = 'localhost';
* $DB_NAME = '< db name > ';
* $DB_USER = '< db role name > ';
* $DB_PASSWORD = '< db role password > ';
*
* You also need to make the following table:
*
* CEATE TABLE posts (
* post_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
* post_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
* user_id VARCHAR(40),
* name VARCHAR(256),
* comment VARCHAR(2048),
* img VARCHAR(256)
* );
*/
require_once('vars.php');
?>
< !DOCTYPE html
< !DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -36,6 +71,7 @@
< / p >
< / p >
<?php
<?php
// Adds a post to the database
function post($name, $comment, $img, $save_cookie) {
function post($name, $comment, $img, $save_cookie) {
$extensions = [
$extensions = [
'image/bmp' => '.bmp',
'image/bmp' => '.bmp',
@ -56,13 +92,13 @@
if(!move_uploaded_file($img['tmp_name'], $target)) {
if(!move_uploaded_file($img['tmp_name'], $target)) {
// The new image file move failed, so delete the temporary file and return an error
// The new image file move failed, so delete the temporary file and return an error
@unlink($img['tmp_name']);
@unlink($img['tmp_name']);
return 'Sorry, there was a problem uploading your image .';
return 'Unable to upload image, please contact the webmaster .';
}
}
}
}
} else {
} else {
// The new picture fil e is not valid, so delete the temporary file and return an error
// The imag e is not valid, so delete the temporary file and return an error
@unlink($img['tmp_name']);
@unlink($img['tmp_name']);
return "Your picture must be a PNG, GIF, JPEG, or BMP image file no greater than {MM_MAXFILESIZE >> 10} KiB." ;
return 'Your image must be a PNG, GIF, JPEG, or BMP image file no greater than ' . (MAX_FILE_SIZE >> 10) . ' KiB.' ;
}
}
}
}
@ -91,6 +127,7 @@
return ""; // Success, no error
return ""; // Success, no error
}
}
// Regex callback, makes >>quotes into links
function quote_link($match) {
function quote_link($match) {
$query = "SELECT post_id FROM posts WHERE post_id=$1";
$query = "SELECT post_id FROM posts WHERE post_id=$1";
$result = pg_query_params($query, [$match[1]]) or die('Query failed: ' . pg_last_error());
$result = pg_query_params($query, [$match[1]]) or die('Query failed: ' . pg_last_error());
@ -102,9 +139,8 @@
return "< del > {$match[0]}< / del > ";
return "< del > {$match[0]}< / del > ";
}
}
// Prints the post list
function show_posts() {
function show_posts() {
$show_delete = FALSE;
$query = 'SELECT post_id, user_id, name, comment, img, TO_CHAR(post_time, \'YYYY-MM-DD HH24:MI (TZ)\') AS post_time FROM posts';
$query = 'SELECT post_id, user_id, name, comment, img, TO_CHAR(post_time, \'YYYY-MM-DD HH24:MI (TZ)\') AS post_time FROM posts';
$result = pg_query($query) or die('Query failed: ' . pg_last_error());
$result = pg_query($query) or die('Query failed: ' . pg_last_error());
@ -117,12 +153,14 @@
}
}
}
}
// Print posts
$show_delete = FALSE;
echo '< form action = "' . $_SERVER['PHP_SELF'] .'#bottom" method = "post" > ';
echo '< form action = "' . $_SERVER['PHP_SELF'] .'#bottom" method = "post" > ';
while ($row = pg_fetch_array($result)) {
while ($row = pg_fetch_array($result)) {
echo "< fieldset id = \"p{$row['post_id']}\" > ";
echo "< fieldset id = \"p{$row['post_id']}\" > ";
echo '< legend > ';
echo '< legend > ';
if((!empty($row['user_id']) & & ($row['user_id'] == $_COOKIE['uid'])) || $_COOKIE['uid'] == ADMIN_ID) {
if((!empty($row['user_id']) & & ($row['user_id'] == $_COOKIE['uid'])) || (!empty(ADMIN_ID) & & ( $_COOKIE['uid'] == ADMIN_ID)) ) {
echo '< input type = "checkbox" name = "delete[]" value = "' . $row['post_id'] . '" / > ';
echo '< input type = "checkbox" name = "delete[]" value = "' . $row['post_id'] . '" / > ';
$show_delete = TRUE;
$show_delete = TRUE;
}
}
@ -130,40 +168,50 @@
echo "< a href = \"#p{$row['post_id']}\" > #{$row['post_id']}< / a > ";
echo "< a href = \"#p{$row['post_id']}\" > #{$row['post_id']}< / a > ";
echo '< / legend > ';
echo '< / legend > ';
if($row['img']){
if(!empty( $row['img']) ){
echo '< a href = "' . UPLOAD_PATH . $row['img'] . '" target = "_blank" > ';
echo '< a href = "' . UPLOAD_PATH . $row['img'] . '" target = "_blank" > ';
echo '< img src = "' . UPLOAD_PATH . $row['img'] . '" alt = "' . $row['img'] . '" / > ';
echo '< img src = "' . UPLOAD_PATH . $row['img'] . '" alt = "' . $row['img'] . '" / > ';
echo '< / a > ';
echo '< / a > ';
}
}
// Process quotes, links, and newlines
if(!empty($row['comment'])) {
$comment = $row['comment'];
$comment = $row['comment'];
$comment = preg_replace('/^>[^>].*/m', '< strong > $0< / strong > ', $comment);
$comment = preg_replace('/^>[^>].*/m', '< strong > $0< / strong > ', $comment);
$comment = preg_replace('/https?:\/\/[^\s]+/m', '< a href = "$0" > $0< / a > ', $comment);
$comment = preg_replace('/https?:\/\/[^\s]+/m', '< a href = "$0" > $0< / a > ', $comment);
$comment = preg_replace_callback('/>>\s*(\d+)/', quote_link, $comment);
$comment = preg_replace_callback('/>>\s*(\d+)/', quote_link, $comment);
$comment = str_replace("\n", "< br / > ", $comment);
$comment = str_replace("\n", "< br / > ", $comment);
echo "< p > $comment< / p > ";
echo "< p > $comment< / p > ";
}
echo '< / fieldset > ';
echo '< / fieldset > ';
}
}
pg_free_result($result);
if($show_delete)
if($show_delete)
echo '< p > < input type = "submit" name = "submit" value = "Delete" / > < / p > ';
echo '< p > < input type = "submit" name = "submit" value = "Delete" / > < / p > ';
echo '< / form > ';
echo '< / form > ';
}
}
// Removes a post from the database and its image
function cleanup($id, $force = FALSE) {
function cleanup($id, $force = FALSE) {
$query = "SELECT user_id, img FROM posts WHERE post_id=$1";
$query = "SELECT user_id, img FROM posts WHERE post_id=$1";
$result = pg_query_params($query, [$id]) or die('Query failed: ' . pg_last_error());
$result = pg_query_params($query, [$id]) or die('Query failed: ' . pg_last_error());
$row = pg_fetch_array($result);
$row = pg_fetch_array($result);
pg_free_result($result);
pg_free_result($result);
if($force || $row['user_id'] == $_COOKIE['uid'] || $_COOKIE['uid'] == ADMIN_ID) {
if($force || $row['user_id'] == $_COOKIE['uid'] || (!empty(ADMIN_ID) & & ( $_COOKIE['uid'] == ADMIN_ID)) ) {
unlink(UPLOAD_PATH . $row['img']);
unlink(UPLOAD_PATH . $row['img']);
$query = "DELETE FROM posts WHERE post_id=$1";
$query = "DELETE FROM posts WHERE post_id=$1";
pg_query_params($query, [$id]) or die('Query failed: ' . pg_last_error());
pg_query_params($query, [$id]) or die('Query failed: ' . pg_last_error());
}
}
}
}
// Sends a webhook to Discord
function webhook($name, $message, $img) {
function webhook($name, $message, $img) {
if(empty(DISCORD_WEBHOOK))
return;
$data = [
$data = [
'username' => $name,
'username' => $name,
'embeds' => [
'embeds' => [
@ -191,11 +239,6 @@
die("Error: Sending webhook failed with status $status.");
die("Error: Sending webhook failed with status $status.");
}
}
require_once('appvars.php');
require_once('connectvars.php');
$err = "";
// Connect to the database
// Connect to the database
$dbc = pg_connect("host=$DB_HOST dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD")
$dbc = pg_connect("host=$DB_HOST dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD")
or die('Could not connect: ' . pg_last_error());
or die('Could not connect: ' . pg_last_error());
@ -216,8 +259,6 @@
show_posts();
show_posts();
pg_free_result($result);
pg_close($dbc);
pg_close($dbc);
?>
?>
@ -237,7 +278,7 @@
< / tr >
< / tr >
< tr >
< tr >
< td > < label for = "img" > Image:< / label > < / td >
< td > < label for = "img" > Image:< / label > < / td >
< td > <input type = "file" id = "img" name = "img" / > < /td >
< td ><input type = "file" id = "img" name = "img" / > (Limit: <?php echo MAX_FILE_SIZE >> 10 ; ?> KiB)< /td >
< / tr >
< / tr >
< tr >
< tr >
< td > < label for = "save-cookie" > Save cookie:< / label > < / td >
< td > < label for = "save-cookie" > Save cookie:< / label > < / td >
@ -254,11 +295,13 @@
< / form >
< / form >
< p >
< p >
Old posts are automatically deleted once there are more than 50 , anything inappropriate will be deleted.
Old posts are automatically deleted once there are more than <?php echo MAX_POSTS ; ?> , anything inappropriate will be deleted.
< / p >
< / p >
< p >
< p >
[< a href = "#top" > top< / a > ] [< a href = "javascript:window.location.reload();" > reload< / a > ]
[< a href = "#top" > top< / a > ]
[< a href = "javascript:window.location.reload();" > reload< / a > ]
[< a href = "?source" > source< / a > ]
< / p >
< / p >
< p >
< p >