Massive cleanup of project, added support for MySQL, implemented visibility

This commit is contained in:
Conner Harkness 2025-06-28 06:40:16 -06:00
parent 166a1d7e2b
commit bd3c6f065f
32 changed files with 785 additions and 399 deletions

View File

@ -0,0 +1,8 @@
CREATE table if not exists links (
id integer primary key auto_increment,
label text not null,
url text not null,
icon text not null,
position text not null,
visibility text null,
sort integer not null default 0)

View File

@ -0,0 +1,9 @@
CREATE table if not exists posts (
id integer primary key auto_increment,
username text not null,
content text not null,
location text not null,
visibility text null,
created timestamp not null default current_timestamp,
updated timestamp not null default current_timestamp,
sort integer not null default 0)

View File

@ -0,0 +1,5 @@
CREATE table if not exists sessions (
id integer primary key auto_increment,
username text not null,
token text not null,
expires timestamp null)

View File

@ -0,0 +1,4 @@
CREATE table if not exists settings (
id integer primary key auto_increment,
setting text not null,
value text not null)

View File

@ -0,0 +1,7 @@
CREATE table if not exists users (
id integer primary key auto_increment,
username text not null,
hash text not null,
can_post integer not null default 0,
is_admin integer not null default 0,
created timestamp not null default current_timestamp)

View File

@ -0,0 +1,4 @@
SELECT *
from posts
where
id = last_insert_id()

View File

@ -0,0 +1,8 @@
CREATE table if not exists links (
id integer primary key autoincrement,
label text not null,
url text not null,
icon text not null,
position text not null,
visibility text null,
sort integer not null default 0)

View File

@ -0,0 +1,9 @@
CREATE table if not exists posts (
id integer primary key autoincrement,
username text not null,
content text not null,
location text not null,
visibility text null,
created timestamp not null default current_timestamp,
updated timestamp not null default current_timestamp,
sort integer not null default 0)

View File

@ -0,0 +1,5 @@
CREATE table if not exists sessions (
id integer primary key autoincrement,
username text not null,
token text not null,
expires timestamp null)

View File

@ -0,0 +1,4 @@
CREATE table if not exists settings (
id integer primary key autoincrement,
setting text not null,
value text not null)

View File

@ -0,0 +1,7 @@
CREATE table if not exists users (
id integer primary key autoincrement,
username text not null,
hash text not null,
can_post integer not null default 0,
is_admin integer not null default 0,
created timestamp not null default current_timestamp)

View File

@ -0,0 +1,4 @@
SELECT *
from posts
where
rowid = last_insert_rowid()

View File

@ -0,0 +1,46 @@
$(function() {
FormValidator = {};
FormValidator.onError = function(str)
{
alert(str);
console.log(str);
};
FormValidator.validate = function(callback)
{
var pass = 1;
$("[fv-regex]").each(function(i, x) {
if (pass == 0)
return;
x = $(x);
let input = x.val();
let regex = new RegExp(x.attr("fv-regex"), "g");
if (!input.match(regex))
{
x.addClass("border-danger");
x.focus();
x.select();
let warning = x.attr("fv-warning") ?? "Please correct the highlighted input and try again";
FormValidator.onError(warning)
pass = 0;
return;
}
if (pass == 1)
if (typeof callback === "function")
callback();
x.removeClass("border-danger");
});
};
});

View File

@ -0,0 +1,37 @@
div.searchable-input input
{
width: 100%;
box-sizing: border-box;
}
div.searchable-input div.items
{
padding-top: 0.5em;
padding-bottom: 0.5em;
border: 1px solid #aaa;
/*border-top: 0;*/
max-width: 100%;
max-height: 10em;
overflow-x: scroll;
overflow-y: scroll;
position: absolute;
background: #fff;
box-sizing: border-box;
}
div.searchable-input div.item
{
white-space: normal;
padding-left: 0.5em;
padding-right: 0.5em;
user-select: none;
}
div.searchable-input div.item:hover
{
/*background: #0af;
color: #fff;*/
background: highlight;
color: highlighttext;
cursor: pointer;
}

View File

@ -0,0 +1,206 @@
$(function() {
SearchableInput = {};
SearchableInput.initItems = function()
{
var items = $("div.searchable-input div.item");
items.each(function(i, x) {
x = $(x);
var parent = x.parents("div.searchable-input").first();
var input = parent.find("input").first();
var item = x;
var itemContainer = parent.find("div.items").first();
if (item.is("[onclick]"))
return;
if (!item.is("[data-value]"))
return;
item.unbind("mousedown");
item.bind("mousedown", function() {
input.attr("data-value", item.attr("data-value"));
input.val(item.text().trim());
itemContainer.hide();
});
});
};
SearchableInput.init = function()
{
$("div.searchable-input input").each(function(i, x) {
x = $(x);
var parent = x.parents("div.searchable-input").first();
var backgroundColor = null;
var selectColor = null;
var input = x;
var itemContainer = parent.find("div.items").first();
var items = itemContainer.find("div.item");
var strictSearch = parent.attr("strict-search");
// Get first opaque body color:
parent.parents("*").each(function(i, x) {
if (backgroundColor !== null)
return;
x = $(x);
var color = x.css("backgroundColor");
if (/^rgba/.test(color))
{
var alphaPart = parseFloat(color.split(",")[3]);
if (isNaN(alphaPart))
return;
if (alphaPart > 0.9)
backgroundColor = color;
return;
}
if (/^rgb/.test(color))
backgroundColor = color;
});
itemContainer.hide();
x.unbind("blur");
x.bind("blur", function() {
setTimeout(function() {
itemContainer.hide();
if (input.val().trim().length < 1)
input.attr("data-value", null);
}, 10);
});
x.unbind("input");
x.bind("input", function() {
var fnOnInputCooldown = function(itemContainer)
{
var parent = itemContainer.parents("div.searchable-input").first();
var input = parent.find("input").first();
var items = itemContainer.find("div.item");
var searchTermsString = input.val().toLowerCase().trim();
var searchTerms = searchTermsString.split(" ");
items.hide();
items.each(function(i, x) {
x = $(x);
var item = x;
var pass = 1;
var itemText = item.text().toLowerCase().trim();
if (strictSearch)
{
if (itemText.includes(searchTermsString))
item.show();
return;
}
for (var i = 0; i < searchTerms.length; i++)
{
var searchTerm = searchTerms[i];
if (searchTerm.length < 1)
continue;
if (!itemText.includes(searchTerm))
pass = 0;
}
//item.hide();
if (pass == 1)
item.show();
});
};
var dataSourceApi = itemContainer.attr("data-source-api");
if (dataSourceApi)
{
if (typeof(SearchableInput.timeout) !== "undefined")
clearTimeout(SearchableInput.timeout);
SearchableInput.timeout = setTimeout(function() {
var searchTermsString = input.val().toLowerCase().trim();
console.log(dataSourceApi);
$.get({
url: `${dataSourceApi}?q=${searchTermsString}`,
//url: `${dataSourceApi}`,
method: "get",
success: function(data)
{
console.log(data);
itemContainer.html("");
for (var i = 0; i < data.length; i++)
{
var item = data[i];
var itemElement = $("<div></div>");
itemElement.addClass("item");
itemElement.attr("data-value", item.value);
itemElement.html(item.name);
itemContainer.append(itemElement);
}
SearchableInput.initItems();
fnOnInputCooldown(itemContainer);
},
error: function(xhr, e, m)
{
console.log(e);
console.log(m);
}
});
}, 1000);
}
fnOnInputCooldown(itemContainer);
});
x.unbind("focus");
x.bind("focus", function() {
//x.trigger("input");
itemContainer.css("width", parent.width() + "px");
itemContainer.css("background", backgroundColor);
// If data-source is a valid jQuery selector, use that container's innerHTML:
var dataSource = itemContainer.attr("data-source");
if (dataSource)
{
var dataSourceObject = $(dataSource);
if (dataSourceObject.length > 0)
{
itemContainer.html(dataSourceObject.first().html());
//SearchableInput.init();
SearchableInput.initItems();
}
}
itemContainer.show();
items.show();
input.select();
});
SearchableInput.initItems();
});
};
SearchableInput.init();
});

View File

@ -1,5 +1,6 @@
<?php
global $c;
$varUser = UserAuth::getUser();
$varFooterLinks = $c->query("SELECT * from links where position like 'footer' order by sort");
?>
@ -9,6 +10,8 @@
<div class="row">
<div class="col-lg-4">
<?php foreach ($varFooterLinks as $varLink): ?>
<?php if (!UserAuth::visible($varLink["visibility"])) continue; ?>
<div>
<a class="link-underline link-underline-opacity-0" href="<?= $varLink["url"]; ?>"><i class="fa fa-fw fa-<?= $varLink["icon"]; ?> pe-2"></i> <?= $varLink["label"]; ?></a>
</div>
@ -16,3 +19,24 @@
</div>
</div>
</div>
<script>
$(function() {
$("input, textarea").each(function(i, x) {
x = $(x);
x.attr("autocomplete", "0");
x.attr("autocorrect", "0");
x.attr("autocapitalize", "0");
x.attr("spellcheck", "false");
});
});
</script>
<script>
<?php
echo Settings::get(
"js",
"$(function() {\n // My script here.\n});",
true);
?>
</script>

View File

@ -1,10 +1,35 @@
<meta name="viewport" content="initial-scale=1, width=device-width" />
<!-- -->
<!-- Bootstrap -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" />
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" ></script>
<!-- JQuery -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- FontAwesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/js/all.min.js"></script>
<!-- SearchableInput -->
<link rel="stylesheet" href="/files/SearchableInput/SearchableInput.css" />
<script src="/files/SearchableInput/SearchableInput.js"></script>
<!-- FormValidator -->
<script src="/files/FormValidator/FormValidator.js"></script>
<style>
/* https://github.com/twbs/bootstrap/issues/37184 */
.dropdown-menu {
z-index: 1040 !important;
}
</style>
<style>
<?php
echo Settings::get(
"css",
"/* Put in your custom CSS here: */\nblockquote {\n padding: 1em;\n background:\n rgba(127, 127, 127, 0.2);\n border-left: 3px solid rgba(127, 127, 127, 0.2); \n}",
true);
?>
</style>

View File

@ -1,17 +1,11 @@
<?php
global $c;
$varUser = UserAuth::getUser();
$varNavbarLinks = $c->query("SELECT * from links where position like 'navbar' order by sort");
$varSidebarLinks = $c->query("SELECT * from links where position like 'sidebar' order by sort");
$varFirstNavbarLink = array_shift($varNavbarLinks);
?>
<style>
/* https://github.com/twbs/bootstrap/issues/37184 */
.dropdown-menu {
z-index: 1040 !important;
}
</style>
<script>
// Make the page's theme dark:
$("body").first().attr("data-bs-theme", "dark");
@ -20,6 +14,8 @@
<div class="offcanvas offcanvas-start" id="sidebar">
<div class="offcanvas-body">
<?php foreach ($varSidebarLinks as $varLink): ?>
<?php if (!UserAuth::visible($varLink["visibility"])) continue; ?>
<a class="btn btn-outline-secondary d-block w-100 mb-2" href="<?= $varLink["url"]; ?>"><i class="fa fa-fw fa-<?= $varLink["icon"]; ?> pe-2"></i> <?= $varLink["label"]; ?></a>
<?php endforeach; ?>
</div>
@ -43,42 +39,31 @@
<div class="dropdown-menu">
<?php foreach ($varNavbarLinks as $varLink): ?>
<?php if (!UserAuth::visible($varLink["visibility"])) continue; ?>
<a class="dropdown-item" href="<?= $varLink["url"]; ?>"><i class="fa fa-fw fa-<?= $varLink["icon"]; ?> pe-2"></i> <?= $varLink["label"]; ?></a>
<?php endforeach; ?>
</div>
</div>
<?php foreach ($varNavbarLinks as $varLink): ?>
<?php if (!UserAuth::visible($varLink["visibility"])) continue; ?>
<a class="nav-link d-none d-lg-inline" href="<?= $varLink["url"]; ?>"><?= $varLink["label"]; ?></a>
<?php endforeach; ?>
</div>
<div class="navbar-nav d-inline-flex align-items-center">
<div class="dropdown">
<?php
$varUser = UserAuth::getUser();
$strUserText = "User";
if ($varUser !== null)
$strUserText = $varUser["user_name"] ?? $varUser["email"] ?? "User";
?>
<a class="btn btn-secondary dropdown-toggle h-100" data-bs-toggle="dropdown"><i class="fa fa-fw fa-user"></i> &nbsp;</a>
<div class="dropdown-menu dropdown-menu-end">
<?php if ($varUser !== null): ?>
<a class="dropdown-item" href="/user/info"><i class="fa fa-fw fa-user pe-2"></i> <?= $strUserText; ?></a>
<a class="dropdown-item" href="/user/info"><i class="fa fa-fw fa-user pe-2"></i> <?= $varUser["username"]; ?></a>
<a class="dropdown-item" href="/user/signout"><i class="fa fa-fw fa-right-from-bracket pe-2"></i> Sign Out</a>
<!--
<a class="nav-link" href="/user/signin">Account</a>
-->
<?php else: ?>
<a class="dropdown-item" href="/user/signin"><i class="fa fa-fw fa-right-to-bracket pe-2"></i> Sign In</a>

190
init.php
View File

@ -1,84 +1,101 @@
<?php
global $c;
$strDBCSFile = "dbcs.txt";
$strDBCSFile = "dbcs.txt";
$strDBCS = "sqlite:sqlite.db";
if (!file_exists($strDBCSFile))
file_put_contents($strDBCSFile, "sqlite:sqlite.db");
file_put_contents($strDBCSFile, $strDBCS);
$c = new DatabaseConnection(
trim(file_get_contents($strDBCSFile)));
$strDBCS = trim(file_get_contents($strDBCSFile));
$c = new DatabaseConnection($strDBCS);
$intInitialize = 1;
if ($intInitialize == 1)
{
$c->query(
"CREATE table if not exists globals (
id integer primary key autoincrement,
global text not null,
content text not null)");
$c->query(
"CREATE table if not exists credentials (
id integer primary key autoincrement,
email text not null,
hash text not null)");
$c->query(
"CREATE table if not exists users (
id integer primary key autoincrement,
email text not null,
user_name text not null,
display_name text not null)");
$c->query(
"CREATE table if not exists sessions (
id integer primary key autoincrement,
email text not null,
token text not null,
expires timestamp null)");
$c->query(
"CREATE table if not exists permissions (
id integer primary key autoincrement,
email text not null,
permission text not null)");
$c->query(
"CREATE table if not exists links (
id integer primary key autoincrement,
label text not null,
url text not null,
icon text not null,
position text not null,
sort integer not null default 0)");
$c->query(
"CREATE table if not exists posts (
id integer primary key autoincrement,
email text not null,
path text not null,
content text not null,
created timestamp not null default current_timestamp,
updated timestamp not null default current_timestamp,
sort integer not null default 0)");
$c->query([
"create_users_table.sql",
"create_sessions_table.sql",
"create_links_table.sql",
"create_posts_table.sql",
"create_settings_table.sql"]);
$varLinks = $c->query("SELECT * from links");
if (count($varLinks) < 1)
{
$c->query(
"INSERT into links (label, url, icon, position)
"INSERT into links (label, url, icon, position, visibility)
values
('Home', '/', 'home', 'navbar'),
('Post', '/post?to=/', 'edit', 'navbar'),
('Links', '/edit/links', 'link', 'navbar'),
('Go home', '/', 'home', 'sidebar'),
('Copyright © 2025 Your Company.', '/', 'home', 'footer')");
('Home', '/', 'home', 'navbar', ''),
('Post', '/post', 'edit', 'navbar', 'user'),
('Home', '/', 'home', 'sidebar', ''),
('Edit Links', '/edit/links', 'link', 'sidebar', 'admin'),
('Edit CSS', '/settings/css', 'code', 'sidebar', 'admin'),
('Edit JS', '/settings/js', 'code', 'sidebar', 'admin'),
('Copyright © 2025 Your Company.', '/', 'building', 'footer', ''),
('Edit this page', '?edit=1', 'edit', 'footer', 'admin')"
);
}
}
class Settings
{
public static function get($strSettingName, $strDefault="", $intSave=0)
{
global $c;
$varExisting = $c->query("
SELECT *
from settings
where
setting like ?
order by
id desc",
$strSettingName);
if (count($varExisting) > 0)
return $varExisting[0]["value"];
if ($intSave)
Settings::set($strSettingName, $strDefault);
return $strDefault;
}
public static function set($strSettingName, $strValue)
{
global $c;
$varExisting = $c->query("
SELECT *
from settings
where
setting like ?
order by
id desc",
$strSettingName);
if (count($varExisting) !== 1)
{
$c->query("DELETE from settings where setting like ?", $strSettingName);
$c->query("INSERT into settings (setting, value) values (?, ?)", $strSettingName, $strValue);
}
$c->query(
"UPDATE settings
set
value = ?
where setting like ?",
$strValue,
$strSettingName);
}
}
class UserAuth
{
@ -93,13 +110,9 @@
if (strlen($strToken) > 0)
{
$varSessions = $c->query(
"SELECT
u.*,
c.*,
s.*
"SELECT *
from sessions as s
join credentials as c on c.email = s.email
left join users as u on u.email = s.email
join users as u on u.username = s.username
where
s.token = ?
and (
@ -116,7 +129,7 @@
return null;
}
public static function hasPermission($strPermission)
public static function has($strColumnName)
{
global $c;
$varUser = UserAuth::getUser();
@ -124,31 +137,42 @@
if ($varUser == null)
return false;
$varPermissions = $c->query(
"SELECT *
from permissions
where
email like ?
and (
permission like ?
or permission like '*'
)",
$varUser["email"],
$strPermission);
if (count($varPermissions) > 0)
return true;
if (array_key_exists($strColumnName, $varUser))
if (intval($varUser[$strColumnName]) > 0)
return true;
return false;
}
public static function requirePermission($strPermission)
public static function require($strColumnName)
{
if (!UserAuth::hasPermission($strPermission))
if (!UserAuth::has($strColumnName))
{
BootstrapRender::message("You do not have permission to do that, please sign into an account that does.", "warning");
Respond::redirect("/user/signin");
}
}
public static function visible($strVisibility)
{
global $c;
$varUser = UserAuth::getUser();
$varRegex = [
["/user/i", ($varUser == null)],
["/admin/i", (!UserAuth::has("is_admin"))],
];
$intExit = 0;
foreach ($varRegex as $re)
if (preg_match($re[0], $strVisibility))
if ($re[1])
$intExit = 1;
if ($intExit == 1)
return false;
return true;
}
}
?>

View File

@ -20,20 +20,49 @@
$strMessageClass = "info";
?>
<?php if (isset($strMessage) && $strMessage !== null && strlen($strMessage) > 0): ?>
<div class="alert alert-<?= $strMessageClass; ?> d-none" id="page-message">
<?= $strMessage; ?>
</div>
<script>
$(function() {
$("#page-message")
<div id="page-message-container">
<?php if (isset($strMessage) && $strMessage !== null && strlen($strMessage) > 0): ?>
<div class="alert alert-<?= $strMessageClass; ?> d-none" id="page-message">
<?= $strMessage; ?>
</div>
<script>
$(function() {
$("#page-message")
.hide()
.removeClass("d-none")
.fadeIn();
});
</script>
<?php endif; ?>
</div>
<script>
$(function() {
BootstrapRender = {};
BootstrapRender.message = function(message, messageClass = "info") {
var messageElem = $("<div></div>");
messageElem.addClass(`alert alert-${messageClass} d-none`);
messageElem.attr("id", "page-message");
messageElem.html(message);
$("#page-message-container")
.empty()
.append(messageElem);
messageElem
.hide()
.removeClass("d-none")
.fadeIn();
});
</script>
<?php endif; ?>
return messageElem;
};
});
</script>
<?php
Cookie::set("message");
@ -42,68 +71,69 @@
public static function input($varOptions)
{
$strName = $varOptions["name"];
$strLabel = $varOptions["label"] ?? $strName;
$strPlaceholder = $varOptions["placeholder"] ?? "Enter {$strLabel}";
$strValue = $varOptions["value"] ?? "";
$intReadonly = $varOptions["readonly"] ?? 0;
$intDisabled = $varOptions["disabled"] ?? 0;
$strType = $varOptions["type"] ?? "text";
$intInline = $varOptions["inline"] ?? 0;
$strTag = $varOptions["tag"] ?? "input";
$varOptions["tag"] = $varOptions["tag"] ?? "input";
$varOptionsExtras = $varOptions;
$varDefaultKeys = ["tag", "label", "name", "type", "value", "hint"];
foreach ($varDefaultKeys as $k)
if (array_key_exists($k, $varOptionsExtras))
unset($varOptionsExtras[$k]);
?>
<div class="mb-3">
<label class="form-label"><?= $varOptions["label"] ?? $varOptions["name"] ?? "input"; ?></label>
<<?= $varOptions["tag"]; ?>
type="<?= $varOptions["type"] ?? "text"; ?>"
class="form-control"
name="<?= $varOptions["name"] ?? "text"; ?>"
placeholder="Enter <?= $varOptions["label"] ?? "value"; ?>"
<?php if ($varOptions["tag"] !== "textarea"): ?>
value="<?= $varOptions["value"] ?? ""; ?>"
<?php endif; ?>
<?php foreach ($varOptionsExtras as $k => $v): ?>
<?= $k; ?>="<?= $v; ?>"
<?php endforeach; ?>
/><?= $varOptions["tag"] == "textarea"? "{$varOptions["value"]}</textarea>" : ""; ?>
<small class="text-muted"><?= $varOptions["hint"] ?? ""; ?></small>
</div>
<?php
}
<?php if ($intInline == 1): ?>
public static function button($varOptions)
{
$varOptions["tag"] = $varOptions["tag"] ?? "a";
$varOptionsExtras = $varOptions;
$varDefaultKeys = ["tag", "label", "name", "type", "value", "hint"];
<div class="row g-3 align-items-center mb-3">
<div class="col-3">
<label class="col-form-label"><?= $strLabel; ?></label>
</div>
foreach ($varDefaultKeys as $k)
if (array_key_exists($k, $varOptionsExtras))
unset($varOptionsExtras[$k]);
?>
<<?= $varOptions["tag"]; ?>
class="btn btn-<?= $varOptions["class"] ?? "secondary"; ?>"
<?php foreach ($varOptionsExtras as $k => $v): ?>
<?= $k; ?>="<?= $v; ?>"
<?php endforeach; ?>
>
<?php if (array_key_exists("icon", $varOptions)): ?>
<i class="fa fa-fw fa-<?= $varOptions["icon"]; ?>"></i>
<?php endif; ?>
<div class="col-8">
<<?= $strTag; ?> type="<?= $strType; ?>"
class="form-control"
name="<?= $strName; ?>"
placeholder="Enter <?= $strLabel; ?>"
value="<?= $strValue; ?>"
<?= $intReadonly? "readonly": ""; ?>
<?= $intDisabled? "disabled": ""; ?>
<?php if ($strTag == "textarea"): ?>
><?= $strValue; ?></<?= $strTag; ?>>
<?php else: ?>
/>
<?php endif; ?>
</div>
<div class="col-auto">
<span class="form-text">test</span>
</div>
</div>
<?php else: ?>
<?= $varOptions["label"] ?? "Button"; ?>
</<?= $varOptions["tag"]; ?>>
<?php
}
public static function buttons($varButtons)
{
?>
<div class="mb-3">
<label class="form-label"><?= $strLabel; ?></label>
<div class="input-group">
<<?= $strTag; ?> type="<?= $strType; ?>"
class="form-control"
name="<?= $strName; ?>"
placeholder="Enter <?= $strLabel; ?>"
value="<?= $strValue; ?>"
<?= $intReadonly? "readonly": ""; ?>
<?= $intDisabled? "disabled": ""; ?>
<?php if ($strTag == "textarea"): ?>
><?= $strValue; ?></<?= $strTag; ?>>
<?php else: ?>
/>
<?php endif; ?>
<div>
<?php foreach ($varButtons as $b): ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php
}

View File

@ -3,14 +3,14 @@
{
public static function rows($varRows)
{
$varParsedown = new Parsedown();
$varUser = UserAuth::getUser();
$varParsedown = new Parsedown();
$intRenderedRows = 0;
?>
<?php if (file_exists("files/site.css")): ?>
<link rel="stylesheet" href="/files/site.css" />
<?php endif; ?>
<?php foreach ($varRows as $r): ?>
<?php if (!UserAuth::visible($r["visibility"])) continue; ?>
<div class="container my-5">
<div class="row">
<div class="col-lg-8">
@ -19,7 +19,7 @@
</div>
<hr />
<div class="text-muted">
<div>by <?= $r["display_name"] ?? $r["user_name"] ?? $r["email"]; ?></div>
<div>by <?= $r["username"]; ?></div>
<div>on <?= $r["created"]; ?> UTC</div>
</div>
<?php if (Request::getParam("edit")): ?>
@ -30,9 +30,11 @@
</div>
</div>
</div>
<?php $intRenderedRows++; ?>
<?php endforeach; ?>
<?php if (count($varRows) < 1): ?>
<?php if ($intRenderedRows < 1): ?>
<div class="container my-5">
<div class="row">
<div class="col-lg-8">
@ -41,10 +43,6 @@
</div>
</div>
<?php endif; ?>
<?php if (file_exists("files/site.js")): ?>
<script src="/files/site.js"></script>
<?php endif; ?>
<?php
}
}

View File

@ -1,67 +0,0 @@
<?php
global $c;
$strFile = "files/site.js";
$strContent = "";
UserAuth::requirePermission("admin");
if (file_exists($strFile))
$strContent = file_get_contents($strFile);
if (Request::posts("content"))
{
$strContent = Request::getPosted("content");
file_put_contents($strFile, $strContent);
}
?>
<style>
textarea {
font-family: monospace;
}
</style>
<form method="post">
<div class="navbar navbar-expand bg-body-tertiary d-flex px-3 sticky-top">
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand"><?= $strFile; ?></span>
<a class="btn btn-outline-success text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-save"></i> Save</a>
</div>
<div class="navbar-nav d-inline-flex">
</div>
</div>
</div>
<?php /**/ ?>
<div class="container my-5">
<div class="row">
<textarea
class="form-control border-0 shadow-none"
name="content"
placeholder="Enter content here..."
oninput="fnResize(this);"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"><?= $strContent; ?></textarea>
</div>
</div>
</form>
<script>
$(function() {
fnSave = function() {
$("form").first().submit();
};
fnResize = function(x) {
x.style.height = "auto";
x.style.height = x.scrollHeight + "px";
};
fnResize($("textarea").first()[0]);
});
</script>

View File

@ -1,5 +1,5 @@
<?php
global $c;
UserAuth::requirePermission("admin");
TableEditor::render("links", ["label", "url", "icon", "position", "sort"]);
UserAuth::require("is_admin");
TableEditor::render("links", ["label", "url", "icon", "position", "visibility", "sort"]);
?>

View File

@ -5,59 +5,16 @@
$strPath .= implode("/", Request::getPathParts());
$varPosts = $c->query(
"SELECT
p.*,
u.user_name,
u.display_name
"SELECT *
from posts as p
left join users as u on u.email = p.email
where
path like ?
or path like '*'
location like ?
or location like '*'
order by
created desc",
$strPath);
$strSearchQuery = Request::getParam("q");
if ($strSearchQuery)
{
$varPosts = $c->query(
"SELECT
p.*,
u.user_name,
u.display_name
from posts as p
left join users as u on u.email = p.email
where
content like concat('%', ?, '%')
order by
created desc",
$strSearchQuery);
}
$varParsedown = new Parsedown();
?>
<?php if ($strSearchQuery): ?>
<div class="navbar navbar-expand bg-body-tertiary d-flex px-3 sticky-top">
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand">Search</span>
<input class="form-control me-2" type="text" name="path" placeholder="e.g. /home" value="<?= $strSearchQuery; ?>" />
<a class="btn btn-outline-primary text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-search"></i> Search</a>
</div>
<div class="navbar-nav d-inline-flex">
<?php BootstrapRender::message(); ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if (Request::getParam("edit")): ?>
<div class="container my-5">
<div class="row">

View File

@ -1,13 +1,15 @@
<?php
global $c;
UserAuth::requirePermission("admin");
UserAuth::require("can_post");
$varUser = UserAuth::getUser();
$strId = Request::getArg(0);
$strPath = Request::getParam("to") ?? "";
$strContent = "";
$strVerb = "Create";
$varUser = UserAuth::getUser();
$strId = Request::getArg(0);
$strContent = "";
$strLocation = Request::getParam("to") ?? "";
$strVisibility = "";
$strVerb = "Create";
if (strlen($strId) > 0)
{
@ -20,27 +22,30 @@
Respond::redirect("/post");
}
$varRow = $varRows[0];
$strPath = $varRow["path"];
$strContent = $varRow["content"];
$varRow = $varRows[0];
$strContent = $varRow["content"];
$strLocation = $varRow["location"];
$strVisibility = $varRow["visibility"];
}
if (Request::posts("path", "content"))
if (Request::posts("location", "content", "visibility"))
{
$strPath = Request::getPosted("path");
$strContent = Request::getPosted("content");
$strLocation = Request::getPosted("location");
$strContent = Request::getPosted("content");
$strVisibility = Request::getPosted("visibility");
if ($strId == null || strlen($strId) < 1)
{
$c->query(
"INSERT into posts (email, path, content)
values (?, ?, ?)",
$varUser["email"],
$strPath,
$strContent);
"INSERT into posts (username, content, location, visibility)
values (?, ?, ?, ?)",
$varUser["username"],
$strContent,
$strLocation,
$strVisibility);
$strId = $c->query("SELECT * from posts where rowid = last_insert_rowid()")[0]["id"];
$strId = $c->query("get_last_post.sql")[0]["id"];
}
if (strlen($strContent) < 1)
@ -53,15 +58,18 @@
$c->query(
"UPDATE posts
set
path = ?,
content = ?,
updated = current_timestamp
content = ?,
location = ?,
visibility = ?,
updated = current_timestamp
where
id = ?",
$strPath,
$strContent,
$strLocation,
$strVisibility,
$strId);
BootstrapRender::message("Post saved.", "success");
Respond::redirect("/post/{$strId}");
}
?>
@ -77,35 +85,75 @@
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand"><?= $strVerb; ?> Post</span>
<span class="nav-item text-nowrap me-2">Location</span>
<input class="form-control me-2" type="text" name="path" placeholder="e.g. /home" value="<?= $strPath; ?>" />
<?php if ($strId == null || strlen($strId) < 1): ?>
<a class="btn btn-outline-primary text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-paper-plane"></i> Submit</a>
<?php else: ?>
<a class="btn btn-outline-success text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-save"></i> Save</a>
<?php endif; ?>
</div>
<div class="navbar-nav d-inline-flex">
<?php BootstrapRender::message(); ?>
</div>
</div>
</div>
<?php /**/ ?>
<div class="container my-5">
<div class="row">
<textarea
class="form-control border-0 shadow-none"
name="content"
placeholder="Enter content here..."
oninput="fnResize(this);"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"><?= $strContent; ?></textarea>
<div class="col-lg-3">
<?php BootstrapRender::message(); ?>
</div>
<div class="col-lg-12">
<div class="mb-3">
<label class="form-label">Content</label>
<textarea
class="form-control"
name="content"
placeholder="Enter markdown content here..."
oninput="fnResize(this);"
><?= $strContent; ?></textarea>
<small class="text-muted">Test</small>
</div>
</div>
<div class="col-lg-3">
<div class="mb-3">
<label class="form-label">Location</label>
<input
type="text"
class="form-control"
name="location"
placeholder="/"
value="<?= $strLocation; ?>"
fv-regex="^\/"
fv-warning="Location must start with a forward slash" />
<small class="text-muted">e.g. /home or /info</small>
</div>
</div>
<div class="col-lg-3">
<div class="mb-3">
<label class="form-label">Visible To</label>
<input
type="text"
class="form-control"
name="visibility"
placeholder="everyone"
value="<?= $strVisibility; ?>"
fv-regex="^(|everyone|users|admins)$"
fv-warning="Visibility must be empty, everyone, users, or admins" />
<small class="text-muted">e.g. everyone, users, admins</small>
</div>
</div>
<div class="col-lg-3">
<div class="mb-3">
<label class="form-label">Actions</label>
<div>
<?php if ($strId == null || strlen($strId) < 1): ?>
<a class="btn btn-primary" onclick="fnSave();"><i class="fa fa-fw fa-paper-plane"></i> Submit</a>
<?php else: ?>
<a class="btn btn-success" onclick="fnSave();"><i class="fa fa-fw fa-save"></i> Save</a>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</form>
@ -113,10 +161,18 @@
<script>
$(function() {
fnSave = function() {
$("form").first().submit();
FormValidator.onError = function(str)
{
BootstrapRender.message(str, "danger");
};
FormValidator.validate(function() {
$("form").first().submit();
});
};
fnResize = function(x) {
x.style.minHeight = "2in";
x.style.height = "auto";
x.style.height = x.scrollHeight + "px";
};

View File

@ -1,18 +1,18 @@
<?php
global $c;
$strFile = "files/site.css";
$strContent = "";
UserAuth::requirePermission("admin");
UserAuth::require("is_admin");
if (file_exists($strFile))
$strContent = file_get_contents($strFile);
$varUser = UserAuth::getUser();
$strId = Request::getArg(0);
if ($strId == null || strlen($strId) < 1)
$strId = "none";
if (Request::posts("content"))
{
$strContent = Request::getPosted("content");
file_put_contents($strFile, $strContent);
}
Settings::set($strId, Request::getPosted("content"));
$strContent = Settings::get($strId, "");
?>
<style>
@ -25,12 +25,16 @@
<div class="navbar navbar-expand bg-body-tertiary d-flex px-3 sticky-top">
<div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand"><?= $strFile; ?></span>
<span class="navbar-brand">Setting</span>
<span class="nav-item text-nowrap me-2">Name</span>
<input class="form-control me-2" type="text" name="location" value="<?= $strId; ?>" readonly disabled />
<a class="btn btn-outline-success text-nowrap" onclick="fnSave();"><i class="fa fa-fw fa-save"></i> Save</a>
</div>
<div class="navbar-nav d-inline-flex">
<?php BootstrapRender::message(); ?>
</div>
</div>
</div>

View File

@ -5,9 +5,7 @@
if (UserAuth::getUser() == null)
Respond::redirect("/user/signin");
$varUser = UserAuth::getUser();
$strUsername = $varUser["user_name"] ?? "";
$strDisplayName = $varUser["display_name"] ?? "";
$varUser = UserAuth::getUser();
if ($varUser == null)
Respond::redirect("/");
@ -43,20 +41,15 @@
<div class="container">
<div class="row my-5">
<div class="col-md-4">
<div class="mb-3">Edit your account details here.</div>
</div>
<div class="col-md-4">
<?php BootstrapRender::message(); ?>
<form method="post">
<?php BootstrapRender::input([
"name" => "email",
"label" => "E-Mail Address",
"value" => $varUser["email"],
"name" => "username",
"label" => "Username",
"value" => $varUser["username"],
"disabled" => 1,
]); ?>
@ -72,14 +65,13 @@
"value" => $strDisplayName,
]); ?>
<?php BootstrapRender::buttons([
"input_group" => 0,
"buttons" => [[
"icon" => "save",
"label" => "Save",
"type" => "submit",
"class" => "outline-success"
]]]); ?>
<?php BootstrapRender::button([
"tag" => "button",
"type" => "submit",
"class" => "outline-success",
"icon" => "save",
"label" => "Save"
]); ?>
</form>
</div>

View File

@ -1,5 +1,5 @@
<?php
global $c;
UserAuth::requirePermission("admin");
UserAuth::require("is_admin");
TableEditor::render("credentials", ["email", "hash"]);
?>

View File

@ -1,5 +1,5 @@
<?php
global $c;
UserAuth::requirePermission("admin");
UserAuth::require("is_admin");
TableEditor::render("permissions", ["email", "permission"]);
?>

View File

@ -3,7 +3,7 @@
try
{
$intUserCount = $c->query("SELECT count(*) as c from credentials")[0]["c"];
$intUserCount = $c->query("SELECT count(*) as c from users")[0]["c"];
if ($intUserCount < 1)
{
@ -12,14 +12,14 @@
"warning");
}
if (Request::posts("email", "password", "repeat"))
if (Request::posts("username", "password", "repeat"))
{
$strEmail = Request::getPosted("email");
$strUsername = Request::getPosted("username");
$strPassword = Request::getPosted("password");
$strRepeat = Request::getPosted("repeat");
if (!preg_match("/^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$/", $strEmail))
throw new Exception("Not a valid e-mail address");
if (!preg_match("/^[A-Za-z0-9]{1,}$/", $strUsername))
throw new Exception("Not a valid username");
if (Request::getPosted("password") !== Request::getPosted("repeat"))
throw new Exception("Passwords do not match");
@ -27,27 +27,22 @@
if (strlen($strPassword) < 6)
throw new Exception("Password must be at least 6 characters");
$varUsers = $c->query("SELECT * from credentials where email like ?", $strEmail);
$varUsers = $c->query("SELECT * from users where username like ?", $strUsername);
if (count($varUsers) > 0)
throw new Exception("E-mail address in use");
throw new Exception("Username in use");
$strHash = sha1($strPassword);
$c->query(
"INSERT into credentials (email, hash) values (?, ?)",
$strEmail,
"INSERT into users (username, hash) values (?, ?)",
$strUsername,
$strHash);
$intUserCount = $c->query("SELECT count(*) as c from credentials")[0]["c"];
$intUserCount = $c->query("SELECT count(*) as c from users")[0]["c"];
if ($intUserCount == 1)
{
$c->query(
"INSERT into permissions (email, permission) values (?, ?)",
$strEmail,
"admin");
}
$c->query("UPDATE users set can_post = 1, is_admin = 1");
BootstrapRender::message("Registration was a success, please sign in to continue.");
@ -74,9 +69,9 @@
<form method="post">
<?php BootstrapRender::input([
"name" => "email",
"label" => "E-Mail Address",
"value" => Request::getPosted("email")
"name" => "username",
"label" => "Username",
"value" => Request::getPosted("username")
]); ?>
<?php BootstrapRender::input([

View File

@ -3,23 +3,23 @@
try
{
$intUserCount = $c->query("SELECT count(*) as c from credentials")[0]["c"];
$intUserCount = $c->query("SELECT count(*) as c from users")[0]["c"];
if ($intUserCount < 1)
Respond::redirect("/user/register");
if (Request::posts("email", "password"))
if (Request::posts("username", "password"))
{
$strEmail = Request::getPosted("email");
$strUsername = Request::getPosted("username");
$strPassword = Request::getPosted("password");
$strHash = sha1($strPassword);
$varUsers = $c->query(
"SELECT *
from credentials
from users
where
email like ?
username like ?
and hash = ?",
$strEmail,
$strUsername,
$strHash);
if (count($varUsers) !== 1)
@ -28,8 +28,8 @@
$strToken = sha1(microtime());
$c->query(
"INSERT into sessions (email, token) values (?, ?)",
$strEmail,
"INSERT into sessions (username, token) values (?, ?)",
$strUsername,
$strToken);
Cookie::set("token", $strToken);
@ -59,8 +59,8 @@
<form method="post">
<?php BootstrapRender::input([
"name" => "email",
"label" => "E-Mail Address",
"name" => "username",
"label" => "Username",
"value" => Request::getPosted("email")
]); ?>

View File

@ -10,8 +10,8 @@
"UPDATE sessions
set
expires = current_timestamp
where email = ?",
$varUser["email"]);
where username = ?",
$varUser["username"]);
}
else
{