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 <?php
global $c; global $c;
$varUser = UserAuth::getUser();
$varFooterLinks = $c->query("SELECT * from links where position like 'footer' order by sort"); $varFooterLinks = $c->query("SELECT * from links where position like 'footer' order by sort");
?> ?>
@ -9,6 +10,8 @@
<div class="row"> <div class="row">
<div class="col-lg-4"> <div class="col-lg-4">
<?php foreach ($varFooterLinks as $varLink): ?> <?php foreach ($varFooterLinks as $varLink): ?>
<?php if (!UserAuth::visible($varLink["visibility"])) continue; ?>
<div> <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> <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> </div>
@ -16,3 +19,24 @@
</div> </div>
</div> </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" /> <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" /> <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> <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> <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" /> <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> <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 <?php
global $c; global $c;
$varUser = UserAuth::getUser();
$varNavbarLinks = $c->query("SELECT * from links where position like 'navbar' order by sort"); $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"); $varSidebarLinks = $c->query("SELECT * from links where position like 'sidebar' order by sort");
$varFirstNavbarLink = array_shift($varNavbarLinks); $varFirstNavbarLink = array_shift($varNavbarLinks);
?> ?>
<style>
/* https://github.com/twbs/bootstrap/issues/37184 */
.dropdown-menu {
z-index: 1040 !important;
}
</style>
<script> <script>
// Make the page's theme dark: // Make the page's theme dark:
$("body").first().attr("data-bs-theme", "dark"); $("body").first().attr("data-bs-theme", "dark");
@ -20,6 +14,8 @@
<div class="offcanvas offcanvas-start" id="sidebar"> <div class="offcanvas offcanvas-start" id="sidebar">
<div class="offcanvas-body"> <div class="offcanvas-body">
<?php foreach ($varSidebarLinks as $varLink): ?> <?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> <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; ?> <?php endforeach; ?>
</div> </div>
@ -43,42 +39,31 @@
<div class="dropdown-menu"> <div class="dropdown-menu">
<?php foreach ($varNavbarLinks as $varLink): ?> <?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> <a class="dropdown-item" href="<?= $varLink["url"]; ?>"><i class="fa fa-fw fa-<?= $varLink["icon"]; ?> pe-2"></i> <?= $varLink["label"]; ?></a>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div> </div>
<?php foreach ($varNavbarLinks as $varLink): ?> <?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> <a class="nav-link d-none d-lg-inline" href="<?= $varLink["url"]; ?>"><?= $varLink["label"]; ?></a>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div class="navbar-nav d-inline-flex align-items-center"> <div class="navbar-nav d-inline-flex align-items-center">
<div class="dropdown"> <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> <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"> <div class="dropdown-menu dropdown-menu-end">
<?php if ($varUser !== null): ?> <?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="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: ?> <?php else: ?>
<a class="dropdown-item" href="/user/signin"><i class="fa fa-fw fa-right-to-bracket pe-2"></i> Sign In</a> <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 <?php
global $c; global $c;
$strDBCSFile = "dbcs.txt"; $strDBCSFile = "dbcs.txt";
$strDBCS = "sqlite:sqlite.db";
if (!file_exists($strDBCSFile)) if (!file_exists($strDBCSFile))
file_put_contents($strDBCSFile, "sqlite:sqlite.db"); file_put_contents($strDBCSFile, $strDBCS);
$c = new DatabaseConnection( $strDBCS = trim(file_get_contents($strDBCSFile));
trim(file_get_contents($strDBCSFile)));
$c = new DatabaseConnection($strDBCS);
$intInitialize = 1; $intInitialize = 1;
if ($intInitialize == 1) if ($intInitialize == 1)
{ {
$c->query( $c->query([
"CREATE table if not exists globals ( "create_users_table.sql",
id integer primary key autoincrement, "create_sessions_table.sql",
global text not null, "create_links_table.sql",
content text not null)"); "create_posts_table.sql",
"create_settings_table.sql"]);
$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)");
$varLinks = $c->query("SELECT * from links"); $varLinks = $c->query("SELECT * from links");
if (count($varLinks) < 1) if (count($varLinks) < 1)
{ {
$c->query( $c->query(
"INSERT into links (label, url, icon, position) "INSERT into links (label, url, icon, position, visibility)
values values
('Home', '/', 'home', 'navbar'), ('Home', '/', 'home', 'navbar', ''),
('Post', '/post?to=/', 'edit', 'navbar'), ('Post', '/post', 'edit', 'navbar', 'user'),
('Links', '/edit/links', 'link', 'navbar'),
('Go home', '/', 'home', 'sidebar'), ('Home', '/', 'home', 'sidebar', ''),
('Copyright © 2025 Your Company.', '/', 'home', 'footer')"); ('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 class UserAuth
{ {
@ -93,13 +110,9 @@
if (strlen($strToken) > 0) if (strlen($strToken) > 0)
{ {
$varSessions = $c->query( $varSessions = $c->query(
"SELECT "SELECT *
u.*,
c.*,
s.*
from sessions as s from sessions as s
join credentials as c on c.email = s.email join users as u on u.username = s.username
left join users as u on u.email = s.email
where where
s.token = ? s.token = ?
and ( and (
@ -116,7 +129,7 @@
return null; return null;
} }
public static function hasPermission($strPermission) public static function has($strColumnName)
{ {
global $c; global $c;
$varUser = UserAuth::getUser(); $varUser = UserAuth::getUser();
@ -124,31 +137,42 @@
if ($varUser == null) if ($varUser == null)
return false; return false;
$varPermissions = $c->query( if (array_key_exists($strColumnName, $varUser))
"SELECT * if (intval($varUser[$strColumnName]) > 0)
from permissions return true;
where
email like ?
and (
permission like ?
or permission like '*'
)",
$varUser["email"],
$strPermission);
if (count($varPermissions) > 0)
return true;
return false; 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"); BootstrapRender::message("You do not have permission to do that, please sign into an account that does.", "warning");
Respond::redirect("/user/signin"); 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"; $strMessageClass = "info";
?> ?>
<?php if (isset($strMessage) && $strMessage !== null && strlen($strMessage) > 0): ?>
<div class="alert alert-<?= $strMessageClass; ?> d-none" id="page-message">
<?= $strMessage; ?>
</div>
<script> <div id="page-message-container">
$(function() { <?php if (isset($strMessage) && $strMessage !== null && strlen($strMessage) > 0): ?>
$("#page-message") <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() .hide()
.removeClass("d-none") .removeClass("d-none")
.fadeIn(); .fadeIn();
});
</script> return messageElem;
<?php endif; ?> };
});
</script>
<?php <?php
Cookie::set("message"); Cookie::set("message");
@ -42,68 +71,69 @@
public static function input($varOptions) public static function input($varOptions)
{ {
$strName = $varOptions["name"]; $varOptions["tag"] = $varOptions["tag"] ?? "input";
$strLabel = $varOptions["label"] ?? $strName; $varOptionsExtras = $varOptions;
$strPlaceholder = $varOptions["placeholder"] ?? "Enter {$strLabel}"; $varDefaultKeys = ["tag", "label", "name", "type", "value", "hint"];
$strValue = $varOptions["value"] ?? "";
$intReadonly = $varOptions["readonly"] ?? 0;
$intDisabled = $varOptions["disabled"] ?? 0;
$strType = $varOptions["type"] ?? "text";
$intInline = $varOptions["inline"] ?? 0;
$strTag = $varOptions["tag"] ?? "input";
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"> foreach ($varDefaultKeys as $k)
<div class="col-3"> if (array_key_exists($k, $varOptionsExtras))
<label class="col-form-label"><?= $strLabel; ?></label> unset($varOptionsExtras[$k]);
</div> ?>
<<?= $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"> <?= $varOptions["label"] ?? "Button"; ?>
<<?= $strTag; ?> type="<?= $strType; ?>" </<?= $varOptions["tag"]; ?>>
class="form-control" <?php
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: ?>
public static function buttons($varButtons)
{
?>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= $strLabel; ?></label> <label class="form-label"><?= $strLabel; ?></label>
<div class="input-group"> <div>
<<?= $strTag; ?> type="<?= $strType; ?>" <?php foreach ($varButtons as $b): ?>
class="form-control" <?php endforeach; ?>
name="<?= $strName; ?>"
placeholder="Enter <?= $strLabel; ?>"
value="<?= $strValue; ?>"
<?= $intReadonly? "readonly": ""; ?>
<?= $intDisabled? "disabled": ""; ?>
<?php if ($strTag == "textarea"): ?>
><?= $strValue; ?></<?= $strTag; ?>>
<?php else: ?>
/>
<?php endif; ?>
</div> </div>
</div> </div>
<?php endif; ?>
<?php <?php
} }

View File

@ -3,14 +3,14 @@
{ {
public static function rows($varRows) 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 foreach ($varRows as $r): ?>
<?php if (!UserAuth::visible($r["visibility"])) continue; ?>
<div class="container my-5"> <div class="container my-5">
<div class="row"> <div class="row">
<div class="col-lg-8"> <div class="col-lg-8">
@ -19,7 +19,7 @@
</div> </div>
<hr /> <hr />
<div class="text-muted"> <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>on <?= $r["created"]; ?> UTC</div>
</div> </div>
<?php if (Request::getParam("edit")): ?> <?php if (Request::getParam("edit")): ?>
@ -30,9 +30,11 @@
</div> </div>
</div> </div>
</div> </div>
<?php $intRenderedRows++; ?>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (count($varRows) < 1): ?> <?php if ($intRenderedRows < 1): ?>
<div class="container my-5"> <div class="container my-5">
<div class="row"> <div class="row">
<div class="col-lg-8"> <div class="col-lg-8">
@ -41,10 +43,6 @@
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if (file_exists("files/site.js")): ?>
<script src="/files/site.js"></script>
<?php endif; ?>
<?php <?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 <?php
global $c; global $c;
UserAuth::requirePermission("admin"); UserAuth::require("is_admin");
TableEditor::render("links", ["label", "url", "icon", "position", "sort"]); TableEditor::render("links", ["label", "url", "icon", "position", "visibility", "sort"]);
?> ?>

View File

@ -5,59 +5,16 @@
$strPath .= implode("/", Request::getPathParts()); $strPath .= implode("/", Request::getPathParts());
$varPosts = $c->query( $varPosts = $c->query(
"SELECT "SELECT *
p.*,
u.user_name,
u.display_name
from posts as p from posts as p
left join users as u on u.email = p.email
where where
path like ? location like ?
or path like '*' or location like '*'
order by order by
created desc", created desc",
$strPath); $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")): ?> <?php if (Request::getParam("edit")): ?>
<div class="container my-5"> <div class="container my-5">
<div class="row"> <div class="row">

View File

@ -1,13 +1,15 @@
<?php <?php
global $c; global $c;
UserAuth::requirePermission("admin"); UserAuth::require("can_post");
$varUser = UserAuth::getUser(); $varUser = UserAuth::getUser();
$strId = Request::getArg(0); $strId = Request::getArg(0);
$strPath = Request::getParam("to") ?? "";
$strContent = ""; $strContent = "";
$strVerb = "Create"; $strLocation = Request::getParam("to") ?? "";
$strVisibility = "";
$strVerb = "Create";
if (strlen($strId) > 0) if (strlen($strId) > 0)
{ {
@ -20,27 +22,30 @@
Respond::redirect("/post"); Respond::redirect("/post");
} }
$varRow = $varRows[0]; $varRow = $varRows[0];
$strPath = $varRow["path"]; $strContent = $varRow["content"];
$strContent = $varRow["content"]; $strLocation = $varRow["location"];
$strVisibility = $varRow["visibility"];
} }
if (Request::posts("path", "content")) if (Request::posts("location", "content", "visibility"))
{ {
$strPath = Request::getPosted("path"); $strLocation = Request::getPosted("location");
$strContent = Request::getPosted("content"); $strContent = Request::getPosted("content");
$strVisibility = Request::getPosted("visibility");
if ($strId == null || strlen($strId) < 1) if ($strId == null || strlen($strId) < 1)
{ {
$c->query( $c->query(
"INSERT into posts (email, path, content) "INSERT into posts (username, content, location, visibility)
values (?, ?, ?)", values (?, ?, ?, ?)",
$varUser["email"], $varUser["username"],
$strPath, $strContent,
$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) if (strlen($strContent) < 1)
@ -53,15 +58,18 @@
$c->query( $c->query(
"UPDATE posts "UPDATE posts
set set
path = ?, content = ?,
content = ?, location = ?,
updated = current_timestamp visibility = ?,
updated = current_timestamp
where where
id = ?", id = ?",
$strPath,
$strContent, $strContent,
$strLocation,
$strVisibility,
$strId); $strId);
BootstrapRender::message("Post saved.", "success");
Respond::redirect("/post/{$strId}"); Respond::redirect("/post/{$strId}");
} }
?> ?>
@ -77,35 +85,75 @@
<div class="container justify-content-between"> <div class="container justify-content-between">
<div class="navbar-nav d-inline-flex align-items-center"> <div class="navbar-nav d-inline-flex align-items-center">
<span class="navbar-brand"><?= $strVerb; ?> Post</span> <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>
<div class="navbar-nav d-inline-flex"> <div class="navbar-nav d-inline-flex">
<?php BootstrapRender::message(); ?>
</div> </div>
</div> </div>
</div> </div>
<?php /**/ ?>
<div class="container my-5"> <div class="container my-5">
<div class="row"> <div class="row">
<textarea <div class="col-lg-3">
class="form-control border-0 shadow-none" <?php BootstrapRender::message(); ?>
name="content" </div>
placeholder="Enter content here..."
oninput="fnResize(this);" <div class="col-lg-12">
autocomplete="off" <div class="mb-3">
autocorrect="off" <label class="form-label">Content</label>
autocapitalize="off" <textarea
spellcheck="false"><?= $strContent; ?></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>
</div> </div>
</form> </form>
@ -113,10 +161,18 @@
<script> <script>
$(function() { $(function() {
fnSave = function() { fnSave = function() {
$("form").first().submit(); FormValidator.onError = function(str)
{
BootstrapRender.message(str, "danger");
};
FormValidator.validate(function() {
$("form").first().submit();
});
}; };
fnResize = function(x) { fnResize = function(x) {
x.style.minHeight = "2in";
x.style.height = "auto"; x.style.height = "auto";
x.style.height = x.scrollHeight + "px"; x.style.height = x.scrollHeight + "px";
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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