Compare commits

...

77 Commits
1.0 ... master

Author SHA1 Message Date
7b6ec52d5e Add Content-Disposition filename header 2021-04-02 15:31:39 -06:00
1696f97b80 Update dependencies 2020-10-23 21:33:06 -06:00
892878313a Update list of bot UAs 2020-10-23 21:11:31 -06:00
ec3214d1f9 Fix message date sorting 2020-10-23 21:04:02 -06:00
6a53db6ec6 Update dependencies (fix CVE-2018-19296) 2020-03-09 21:39:32 -06:00
f93a528822 Add contact form spambot honeytrap 2019-09-17 20:19:25 -06:00
3a08a2e50c Fix Unsplash 2019-03-19 19:48:28 -06:00
cbf36ebafa Update public/editor FontAwesome 5.3.1 -> 5.7.2 2019-03-19 19:12:16 -06:00
b62e86f7a6 Cleanup merge of BusinessAppTemplate 2019-03-19 19:01:57 -06:00
063c8398a9 Merge ../BusinessAppTemplate
# Conflicts:
#	README.md
#	langs/en/titles.json
#	pages.php
#	required.php
#	settings.template.php
2019-03-19 19:01:17 -06:00
53e158b553 Update FontAwesome 5.6.0 -> 5.7.2 2019-03-19 17:12:45 -06:00
474047ab34 Update Bootstrap 4.1.3 -> 4.3.1 2019-03-19 17:11:54 -06:00
c97e058786 API: Check for user permission 2019-03-01 23:43:31 -07:00
26a662c399 Add addTextInput and addSelect methods that are simpler than addInput 2019-03-01 23:37:36 -07:00
289aaeaa9f Minor text changes 2019-03-01 23:37:06 -07:00
3ca062d995 Enforce app passwords in API for users with two-factor enabled 2019-02-11 16:19:27 -07:00
7d30251cd6 Add CORS header to API 2019-01-07 22:18:02 -07:00
7173a50c36 Add textarea to FormBuilder 2019-01-04 17:53:01 -07:00
e66280e07a FormBuilder: add d-flex to footer 2019-01-04 17:29:08 -07:00
3ed75822a1 Update license and readme 2019-01-03 00:13:41 -07:00
7531dc362d Whoops 2019-01-02 23:54:53 -07:00
b250908663 Add more permissions checks 2019-01-02 23:51:47 -07:00
892102528b Strip tags from aria-label 2018-12-31 14:22:14 -07:00
69c634ea99 Add checkbox to form builder 2018-12-31 14:14:00 -07:00
6ceeeaa087 Add support for regex matching on API vars 2018-12-27 14:44:10 -07:00
f1c36fdeb1 Add getRequestUser() function 2018-12-27 00:55:58 -07:00
d36b340692 Make API work with user/pass combo 2018-12-27 00:51:54 -07:00
d7ca7125ce Nicer access denied message 2018-12-27 00:15:27 -07:00
1729b842ba Add permission check during login 2018-12-26 16:32:43 -07:00
4d2b78bdba Remove unused strings 2018-12-26 16:28:32 -07:00
106e697fc3 Remove captcha-related code, since login is done by AccountHub now 2018-12-26 16:25:48 -07:00
e0802f582b Remove unneeded index.css 2018-12-26 16:22:09 -07:00
016c71d30d Fix index.php not redirecting to app.php when already logged in 2018-12-22 22:38:50 -07:00
ba1369d842 Add app icon to login flow 2018-12-22 21:26:57 -07:00
a559901ac0 Redirect to AccountHub for user login 2018-12-22 16:57:45 -07:00
3f32258ba0 Fix bug 2018-12-20 23:58:35 -07:00
129efd13c7 Add documentation comments to settings 2018-12-20 23:54:25 -07:00
c179ed7ebb Make settings.php an array, not a bunch of defines 2018-12-20 23:45:45 -07:00
f1a85f47fd Add comment 2018-12-20 23:25:34 -07:00
61d660be69 Add FormBuilder 2018-12-20 23:24:47 -07:00
5b7ab65946 Make better API system, use new AccountHub API 2018-12-14 21:16:31 -07:00
59ace4fa05 Remove is_empty() 2018-12-14 19:17:37 -07:00
3b6df75195 Merge ../BusinessAppTemplate 2018-12-14 19:15:06 -07:00
13b60de915 Remove is_empty() 2018-12-14 19:14:22 -07:00
32cd18933d Update FontAwesome from 5.3.1 to 5.6.0 2018-12-11 21:39:25 -07:00
4f1b81ff4b Deprecate is_empty() 2018-12-04 19:48:23 -07:00
cb3c8aaf2d Support undefined messages 2018-12-04 19:46:08 -07:00
ec44a6740f Fix "language key ... is defined more than once" warning 2018-11-28 22:50:29 -07:00
7f0bb2a18d Merge BusinessAppTemplate 2018-09-25 13:23:29 -06:00
47539de2d7 Fix bootstrap font URL 2018-09-25 13:07:54 -06:00
12aea4a2e2 Update Bootstrap (https://github.com/thomaspark/bootswatch/issues/861) 2018-09-25 12:54:45 -06:00
20082e2ba2 Update FontAwesome icon gallery/picker (5.0.12 to 5.3.1) 2018-09-24 01:11:06 -06:00
0f889f9590 Update public FontAwesome (5.0.13 to 5.3.1) 2018-09-24 01:00:24 -06:00
56a223c430 Merge BusinessAppTemplate 2018-09-24 00:52:13 -06:00
34f49bfd01 Update README.md 2018-09-22 22:46:43 -06:00
d4621de80f Update README.md 2018-09-22 22:43:56 -06:00
41bb633940 Escape double quotes ("") in sw-text (fix #34) 2018-09-22 20:11:35 -06:00
c36c365a1b Update Bootstrap and FontAwesome 2018-09-21 16:38:34 -06:00
80d0a017ed Update Bootstrap to 4.1.3 2018-09-07 15:11:43 -06:00
a17f51b72d Update FontAwesome 5.1.0 to 5.3.1 2018-09-07 15:09:04 -06:00
1271317eb9 Rewrite to use classes, aligning with AccountHub 2.0 2018-09-07 15:03:42 -06:00
404b9220b8 Remove href from anchors wrapping .sw-text, fix issue #33 2018-07-03 17:36:34 -06:00
9887a96c74 Add support for email notification for contact form messages, close #29 2018-07-03 17:28:46 -06:00
7bf050d730 Merge ../BusinessAppTemplate 2018-06-29 15:38:35 -06:00
963fbfbf00 Upgrade FontAwesome to 5.1.0 2018-06-29 14:58:54 -06:00
027f20cb7e Fix bug with Massively 2018-06-04 00:51:33 -06:00
cfbd9a3706 Merge https://source.netsyms.com/Business/BusinessAppTemplate 2018-06-01 14:29:46 -06:00
10575f6f59 Fix another visual bug 2018-06-01 14:28:30 -06:00
724f57531a Merge https://source.netsyms.com/Business/BusinessAppTemplate 2018-06-01 14:26:21 -06:00
2f9eccf931 Fix bug where clicking close on msg alert didn't remove progress bar 2018-06-01 14:23:14 -06:00
0c4f9fce64 Merge https://source.netsyms.com/Business/BusinessAppTemplate
# Conflicts:
#	lang/en_us.php
2018-06-01 14:07:59 -06:00
769d24b4b7 Improve alert fadeout 2018-06-01 14:04:04 -06:00
66aa3d6fdc Make msg alert time out with smooth visual effects 2018-06-01 13:59:31 -06:00
ee0c0f65e3 Fix PHP variable warnings 2018-05-26 20:51:13 -06:00
5bd9b908ce Improve UX when there are no Unsplash results 2018-05-25 13:57:27 -06:00
66fa86e04e Change string 2018-05-24 19:53:26 -06:00
dafc3b76ea Add message if user is kicked out of application for lack of permissions 2018-05-24 19:52:21 -06:00
94 changed files with 73767 additions and 9713 deletions

View File

@ -1,19 +1,7 @@
Copyright (c) 2018 Netsyms Technologies.
Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved.
If you modify and redistribute this project, you must replace the branding
assets with your own.
The branding assets include:
* the application icon
* the Netsyms N punchcard logo
* the Netsyms for Business graph logo
If you are unsure if your usage is allowed, please contact us:
https://netsyms.com/contact
legal@netsyms.com
All other portions of this application,
unless otherwise noted (in comments, headers, etc), are licensed as follows:
Licensed under the Mozilla Public License Version 2.0. Files without MPL header
comments, including third party code, may be under a different license.
Mozilla Public License Version 2.0
==================================

View File

@ -39,3 +39,4 @@ Installing
8. Set the URL of this app ("URL")
9. Copy webroot.htaccess to your webroot and adjust paths if needed
10. Run `composer install` (or `composer.phar install`) to install dependency libraries
11. Run `git submodule init` and `git submodule update` to install other dependencies via git.

View File

@ -9,7 +9,6 @@
*/
require_once __DIR__ . "/required.php";
require_once __DIR__ . "/lib/util.php";
require_once __DIR__ . "/lib/login.php";
if ($VARS['action'] !== "signout") {
dieifnotloggedin();
@ -23,11 +22,11 @@ if ($VARS['action'] !== "signout") {
*/
function returnToSender($msg, $arg = "") {
global $VARS;
if ($arg == "") {
header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=" . $msg);
} else {
header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg&arg=$arg");
$header = "Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg";
if ($arg != "") {
$header .= "&arg=$arg";
}
header($header);
die();
}
@ -37,18 +36,20 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST' && empty($_POST) &&
returnToSender("upload_too_big");
}
$user = new User($_SESSION['uid']);
switch ($VARS['action']) {
case "newpage":
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_EDIT")) {
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_EDIT")) {
returnToSender("no_permission");
}
if (is_empty($VARS['siteid']) || !$database->has("sites", ["siteid" => $VARS['siteid']])) {
if (empty($VARS['siteid']) || !$database->has("sites", ["siteid" => $VARS['siteid']])) {
returnToSender("invalid_parameters");
}
if (is_empty($VARS['title'])) {
if (empty($VARS['title'])) {
returnToSender("invalid_parameters", $VARS['siteid']);
}
if (!is_empty($VARS['slug'])) {
if (!empty($VARS['slug'])) {
$slug = strtolower($VARS['slug']);
$slug = preg_replace("/[^[:alnum:][:space:]]/u", '', $slug);
$slug = preg_replace("/[[:space:]]/u", '-', $slug);
@ -69,7 +70,7 @@ switch ($VARS['action']) {
}
}
$template = "default";
if (!is_empty($VARS['template'])) {
if (!empty($VARS['template'])) {
$template = preg_replace("/[^A-Za-z0-9]/", '', $VARS['template']);
}
$theme = $database->get("sites", "theme", ["siteid" => $VARS['siteid']]);
@ -80,24 +81,24 @@ switch ($VARS['action']) {
returnToSender("page_added", $VARS['siteid'] . "|" . $database->id());
break;
case "pagesettings":
if (!account_has_permission($_SESSION['username'], "SITEWRITER")) {
if (!$user->hasPermission("SITEWRITER")) {
returnToSender("no_permission");
}
if (is_empty($VARS['siteid']) || !$database->has("sites", ["siteid" => $VARS['siteid']])) {
if (empty($VARS['siteid']) || !$database->has("sites", ["siteid" => $VARS['siteid']])) {
returnToSender("invalid_parameters");
}
if (is_empty($VARS['pageid']) || !$database->has("pages", ["AND" => ["pageid" => $VARS['pageid'], "siteid" => $VARS['siteid']]])) {
if (empty($VARS['pageid']) || !$database->has("pages", ["AND" => ["pageid" => $VARS['pageid'], "siteid" => $VARS['siteid']]])) {
returnToSender("invalid_parameters");
}
if (is_empty($VARS['title'])) {
if (empty($VARS['title'])) {
returnToSender("invalid_parameters", $VARS['siteid']);
}
if (is_empty($VARS['template'])) {
if (empty($VARS['template'])) {
returnToSender("invalid_parameters", $VARS['siteid']);
}
$nav = null;
if ($VARS['innavbar'] == 1) {
if (is_empty($VARS['navbartitle'])) {
if (empty($VARS['navbartitle'])) {
returnToSender("invalid_parameters", $VARS['siteid']);
}
$nav = $VARS['navbartitle'];
@ -122,7 +123,7 @@ switch ($VARS['action']) {
"pageid" => $VARS['pageid']
]
]);
if (!is_empty($VARS['navorder']) && preg_match("/^[0-9]+([0-9|]*([0-9])|[0-9])$/", $VARS['navorder'])) {
if (!empty($VARS['navorder']) && preg_match("/^[0-9]+([0-9|]*([0-9])|[0-9])$/", $VARS['navorder'])) {
$pages = explode("|", preg_replace("/[|]{2,}/", "", $VARS['navorder']));
for ($i = 0; $i < count($pages); $i++) {
$database->update("pages", [
@ -138,24 +139,24 @@ switch ($VARS['action']) {
returnToSender("settings_saved", $VARS['siteid'] . "|" . $VARS['pageid']);
break;
case "sitesettings":
if (!account_has_permission($_SESSION['username'], "SITEWRITER")) {
if (!$user->hasPermission("SITEWRITER")) {
returnToSender("no_permission");
}
if (!is_empty($VARS['siteid'])) {
if (!empty($VARS['siteid'])) {
if (!$database->has("sites", ["siteid" => $VARS['siteid']])) {
returnToSender("invalid_parameters");
}
}
if (is_empty($VARS['name'])) {
if (empty($VARS['name'])) {
returnToSender("invalid_parameters");
}
if (is_empty($VARS['url'])) {
if (empty($VARS['url'])) {
returnToSender("invalid_parameters");
}
if (is_empty($VARS['theme'])) {
if (empty($VARS['theme'])) {
returnToSender("invalid_parameters");
}
if (is_empty($VARS['color'])) {
if (empty($VARS['color'])) {
returnToSender("invalid_parameters");
}
$url = formatsiteurl($VARS['url']);
@ -167,7 +168,7 @@ switch ($VARS['action']) {
if ($color != "default" && !file_exists(__DIR__ . "/public/themes/$theme/colors/$color")) {
returnToSender("invalid_parameters");
}
if (is_empty($VARS['siteid'])) {
if (empty($VARS['siteid'])) {
$database->insert('sites', ["sitename" => $VARS['name'], "url" => $url, "theme" => $theme, "color" => $color]);
$siteid = $database->id();
$template = (file_exists(__DIR__ . "/public/themes/$theme/home.php") ? "home" : "default");
@ -198,8 +199,8 @@ switch ($VARS['action']) {
break;
case "saveedits":
header("Content-Type: application/json");
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_EDIT")) {
exit(json_encode(['status' => "ERROR", 'message' => lang("no permission", false)]));
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_EDIT")) {
exit(json_encode(['status' => "ERROR", 'message' => $Strings->get("no permission", false)]));
}
$slug = $VARS['slug'];
$site = $VARS['site'];
@ -228,7 +229,7 @@ switch ($VARS['action']) {
exit(json_encode(["status" => "OK"]));
break;
case "deletemessage":
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_CONTACT")) {
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_CONTACT")) {
returnToSender("no_permission");
}
if ($database->count('messages', ["mid" => $VARS['id']]) !== 1) {
@ -238,11 +239,11 @@ switch ($VARS['action']) {
returnToSender("message_deleted");
break;
case "fileupload":
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_FILES")) {
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_FILES")) {
returnToSender("no_permission");
}
$destpath = FILE_UPLOAD_PATH . $VARS['path'];
if (strpos(realpath($destpath), FILE_UPLOAD_PATH) !== 0) {
$destpath = $SETTINGS["file_upload_path"] . $VARS['path'];
if (strpos(realpath($destpath), $SETTINGS["file_upload_path"]) !== 0) {
returnToSender("file_security_error");
}
if (!file_exists($destpath) || !is_dir($destpath)) {
@ -310,11 +311,11 @@ switch ($VARS['action']) {
returnToSender("upload_success", "&path=" . $VARS['path']);
break;
case "newfolder":
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_FILES")) {
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_FILES")) {
returnToSender("no_permission");
}
$foldername = preg_replace("/[^a-z0-9_\-]/", "_", strtolower($VARS['folder']));
$newfolder = FILE_UPLOAD_PATH . $VARS['path'] . '/' . $foldername;
$newfolder = $SETTINGS["file_upload_path"] . $VARS['path'] . '/' . $foldername;
if (mkdir($newfolder, 0755)) {
returnToSender("folder_created", "&path=" . $VARS['path']);
@ -322,18 +323,18 @@ switch ($VARS['action']) {
returnToSender("folder_not_created", "&path=" . $VARS['path']);
break;
case "filedelete":
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_FILES")) {
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_FILES")) {
returnToSender("no_permission");
}
$file = FILE_UPLOAD_PATH . $VARS['file'];
if (strpos(realpath($file), FILE_UPLOAD_PATH) !== 0) {
$file = $SETTINGS["file_upload_path"] . $VARS['file'];
if (strpos(realpath($file), $SETTINGS["file_upload_path"]) !== 0) {
returnToSender("file_security_error");
}
if (!file_exists($file)) {
// Either way the file is gone
returnToSender("file_deleted");
}
if (!is_writable($file) || realpath($file) == realpath(FILE_UPLOAD_PATH)) {
if (!is_writable($file) || realpath($file) == realpath($SETTINGS["file_upload_path"])) {
returnToSender("undeletable_file");
}
if (is_dir($file)) {
@ -349,9 +350,9 @@ switch ($VARS['action']) {
break;
case "unsplash_download":
Crew\Unsplash\HttpClient::init([
'applicationId' => UNSPLASH_ACCESSKEY,
'secret' => UNSPLASH_SECRETKEY,
'utmSource' => UNSPLASH_UTMSOURCE
'applicationId' => $SETTINGS["unsplash"]["accesskey"],
'secret' => $SETTINGS["unsplash"]["secretkey"],
'utmSource' => $SETTINGS["unsplash"]["utmsource"]
]);
Crew\Unsplash\Photo::find($VARS['imageid'])->download();
header('Content-Type: application/json');
@ -359,6 +360,6 @@ switch ($VARS['action']) {
break;
case "signout":
session_destroy();
header('Location: index.php');
header('Location: index.php?logout=1');
die("Logged out.");
}

35
api.php
View File

@ -4,37 +4,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Simple JSON API to allow other apps to access data from this app.
*
* Requests can be sent via either GET or POST requests. POST is recommended
* as it has a lower chance of being logged on the server, exposing unencrypted
* user passwords.
*/
require __DIR__ . '/required.php';
require_once __DIR__ . '/lib/login.php';
require_once __DIR__ . '/lib/userinfo.php';
header("Content-Type: application/json");
$username = $VARS['username'];
$password = $VARS['password'];
if (user_exists($username) !== true || authenticate_user($username, $password, $errmsg) !== true) {
header("HTTP/1.1 403 Unauthorized");
die("\"403 Unauthorized\"");
}
$userinfo = getUserByUsername($username);
// query max results
$max = 20;
if (preg_match("/^[0-9]+$/", $VARS['max']) === 1 && $VARS['max'] <= 1000) {
$max = (int) $VARS['max'];
}
switch ($VARS['action']) {
case "ping":
$out = ["status" => "OK", "maxresults" => $max, "pong" => true];
exit(json_encode($out));
default:
header("HTTP/1.1 400 Bad Request");
die("\"400 Bad Request\"");
}
// Load in new API from legacy location (a.k.a. here)
require __DIR__ . "/api/index.php";

5
api/.htaccess Normal file
View File

@ -0,0 +1,5 @@
# Rewrite for Nextcloud Notes API
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT]
</IfModule>

9
api/actions/ping.php Normal file
View File

@ -0,0 +1,9 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
sendJsonResp();

15
api/apisettings.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
$APIS = [
"ping" => [
"load" => "ping.php",
"vars" => [
]
]
];

149
api/functions.php Normal file
View File

@ -0,0 +1,149 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* Build and send a simple JSON response.
* @param string $msg A message
* @param string $status "OK" or "ERROR"
* @param array $data More JSON data
*/
function sendJsonResp(string $msg = null, string $status = "OK", array $data = null) {
$resp = [];
if (!is_null($data)) {
$resp = $data;
}
if (!is_null($msg)) {
$resp["msg"] = $msg;
}
$resp["status"] = $status;
header("Content-Type: application/json");
exit(json_encode($resp));
}
function exitWithJson(array $json) {
header("Content-Type: application/json");
exit(json_encode($json));
}
/**
* Get the API key with most of the characters replaced with *s.
* @global string $key
* @return string
*/
function getCensoredKey() {
global $key;
$resp = $key;
if (strlen($key) > 5) {
for ($i = 2; $i < strlen($key) - 2; $i++) {
$resp[$i] = "*";
}
}
return $resp;
}
/**
* Check if the request is allowed
* @global array $VARS
* @return bool true if the request should continue, false if the request is bad
*/
function authenticate(): bool {
global $VARS, $SETTINGS;
// HTTP basic auth
if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
} else if (!empty($VARS['username']) && !empty($VARS['password'])) {
$username = $VARS['username'];
$password = $VARS['password'];
} else {
return false;
}
$user = User::byUsername($username);
if (!$user->exists()) {
return false;
}
if ($user->checkPassword($password, true)) {
// Check that the user has permission to access the app
$perms = is_array($SETTINGS['api_permissions']) ? $SETTINGS['api_permissions'] : $SETTINGS['permissions'];
foreach ($perms as $perm) {
if (!$user->hasPermission($perm)) {
return false;
}
}
return true;
}
return false;
}
/**
* Get the User whose credentials were used to make the request.
*/
function getRequestUser(): User {
global $VARS;
if (!empty($_SERVER['PHP_AUTH_USER'])) {
return User::byUsername($_SERVER['PHP_AUTH_USER']);
} else {
return User::byUsername($VARS['username']);
}
}
function checkVars($vars, $or = false) {
global $VARS;
$ok = [];
foreach ($vars as $key => $val) {
if (strpos($key, "OR") === 0) {
checkVars($vars[$key], true);
continue;
}
// Only check type of optional variables if they're set, and don't
// mark them as bad if they're not set
if (strpos($key, " (optional)") !== false) {
$key = str_replace(" (optional)", "", $key);
if (empty($VARS[$key])) {
continue;
}
} else {
if (empty($VARS[$key])) {
$ok[$key] = false;
continue;
}
}
if (strpos($val, "/") === 0) {
// regex
$ok[$key] = preg_match($val, $VARS[$key]) === 1;
} else {
$checkmethod = "is_$val";
$ok[$key] = !($checkmethod($VARS[$key]) !== true);
}
}
if ($or) {
$success = false;
$bad = "";
foreach ($ok as $k => $v) {
if ($v) {
$success = true;
break;
} else {
$bad = $k;
}
}
if (!$success) {
http_response_code(400);
die("400 Bad request: variable $bad is missing or invalid");
}
} else {
foreach ($ok as $key => $bool) {
if (!$bool) {
http_response_code(400);
die("400 Bad request: variable $key is missing or invalid");
}
}
}
}

81
api/index.php Normal file
View File

@ -0,0 +1,81 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
require __DIR__ . '/../required.php';
require __DIR__ . '/functions.php';
require __DIR__ . '/apisettings.php';
header("Access-Control-Allow-Origin: *");
$VARS = $_GET;
if ($_SERVER['REQUEST_METHOD'] != "GET") {
$VARS = array_merge($VARS, $_POST);
}
$requestbody = file_get_contents('php://input');
$requestjson = json_decode($requestbody, TRUE);
if (json_last_error() == JSON_ERROR_NONE) {
$VARS = array_merge($VARS, $requestjson);
}
// If we're not using the old api.php file, allow more flexible requests
if (strpos($_SERVER['REQUEST_URI'], "/api.php") === FALSE) {
$route = explode("/", substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], "api/") + 4));
if (count($route) >= 1) {
$VARS["action"] = $route[0];
}
if (count($route) >= 2 && strpos($route[1], "?") !== 0) {
for ($i = 1; $i < count($route); $i++) {
if (empty($route[$i]) || strpos($route[$i], "=") === false) {
continue;
}
$key = explode("=", $route[$i], 2)[0];
$val = explode("=", $route[$i], 2)[1];
$VARS[$key] = $val;
}
}
if (strpos($route[count($route) - 1], "?") === 0) {
$morevars = explode("&", substr($route[count($route) - 1], 1));
foreach ($morevars as $var) {
$key = explode("=", $var, 2)[0];
$val = explode("=", $var, 2)[1];
$VARS[$key] = $val;
}
}
}
if (!authenticate()) {
header('WWW-Authenticate: Basic realm="' . $SETTINGS['site_title'] . '"');
header('HTTP/1.1 401 Unauthorized');
die("401 Unauthorized: you need to supply valid credentials.");
}
if (empty($VARS['action'])) {
http_response_code(404);
die("404 No action specified");
}
if (!isset($APIS[$VARS['action']])) {
http_response_code(404);
die("404 Action not defined");
}
$APIACTION = $APIS[$VARS["action"]];
if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) {
http_response_code(404);
die("404 Action not found");
}
if (!empty($APIACTION["vars"])) {
checkVars($APIACTION["vars"]);
}
require_once __DIR__ . "/actions/" . $APIACTION["load"];

47
app.php
View File

@ -1,5 +1,4 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -14,7 +13,7 @@ if ($_SESSION['loggedin'] != true) {
require_once __DIR__ . "/pages.php";
$pageid = "home";
if (isset($_GET['page']) && !is_empty($_GET['page'])) {
if (!empty($_GET['page'])) {
$pg = strtolower($_GET['page']);
$pg = preg_replace('/[^0-9a-z_]/', "", $pg);
if (array_key_exists($pg, PAGES) && file_exists(__DIR__ . "/pages/" . $pg . ".php")) {
@ -28,10 +27,10 @@ header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
header("Link: <static/css/app.css>; rel=preload; as=style", false);
header("Link: <static/css/fa-svg-with-js.css>; rel=preload; as=style", false);
header("Link: <static/css/svg-with-js.min.css>; rel=preload; as=style", false);
header("Link: <static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
header("Link: <static/js/bootstrap.bundle.min.js>; rel=preload; as=script", false);
?>
<!DOCTYPE html>
<html>
@ -40,14 +39,14 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo SITE_TITLE; ?></title>
<title><?php echo $SETTINGS['site_title']; ?></title>
<link rel="icon" href="static/img/logo.svg">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
<link href="static/css/app.css" rel="stylesheet">
<link href="static/css/fa-svg-with-js.css" rel="stylesheet">
<link href="static/css/svg-with-js.min.css" rel="stylesheet">
<script nonce="<?php echo $SECURE_NONCE; ?>">
FontAwesomeConfig = {autoAddCss: false}
</script>
@ -66,12 +65,13 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
<?php
// Alert messages
if (isset($_GET['msg']) && !is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
if (!empty($_GET['msg'])) {
if (array_key_exists($_GET['msg'], MESSAGES)) {
// optional string generation argument
if (!isset($_GET['arg']) || is_empty($_GET['arg'])) {
$alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
if (empty($_GET['arg'])) {
$alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
} else {
$alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
$alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
}
$alerttype = MESSAGES[$_GET['msg']]['type'];
$alerticon = "square-o";
@ -89,13 +89,24 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
$alerticon = "check";
break;
}
} else {
// We don't have a message for this, so just assume an error and escape stuff.
$alertmsg = htmlentities($Strings->get($_GET['msg'], false));
$alerticon = "times";
$alerttype = "danger";
}
echo <<<END
<div class="row justify-content-center" id="msg-alert-box">
<div class="col-11 col-sm-6 col-md-5 col-lg-4 col-xl-4">
<div class="alert alert-dismissible alert-$alerttype">
<div class="alert alert-dismissible alert-$alerttype mt-2 p-0 border-0 shadow">
<div class="p-2 pl-3">
<button type="button" class="close">&times;</button>
<i class="fas fa-$alerticon"></i> $alertmsg
</div>
<div class="progress">
<div class="progress-bar bg-$alerttype w-0" id="msg-alert-timeout-bar"></div>
</div>
</div>
</div>
</div>
END;
@ -116,7 +127,7 @@ END;
</button>
<a class="navbar-brand py-0 mr-auto" href="app.php">
<img src="static/img/logo.svg" alt="" class="d-none d-<?php echo $navbar_breakpoint; ?>-inline brand-img py-0" />
<?php echo SITE_TITLE; ?>
<?php echo $SETTINGS['site_title']; ?>
</a>
<div class="collapse navbar-collapse py-0" id="navbar-collapse">
@ -141,7 +152,7 @@ END;
if (isset($pg['icon'])) {
?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
}
lang($pg['title']);
$Strings->get($pg['title']);
?>
</a>
</span>
@ -152,13 +163,13 @@ END;
</div>
<div class="navbar-nav ml-auto py-0" id="navbar-right">
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="<?php echo PORTAL_URL; ?>">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="<?php echo $SETTINGS['accounthub']['home']; ?>">
<i class="fas fa-user fa-fw"></i><span>&nbsp;<?php echo $_SESSION['realname'] ?></span>
</a>
</span>
<span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="action.php?action=signout">
<i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php lang("sign out") ?></span>
<i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php $Strings->get("sign out") ?></span>
</a>
</span>
</div>
@ -172,12 +183,12 @@ END;
?>
</div>
<div class="footer">
<?php echo FOOTER_TEXT; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
<?php echo $SETTINGS['footer_text']; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo $SETTINGS['copyright']; ?>
</div>
</div>
<script src="static/js/jquery-3.3.1.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
<script src="static/js/bootstrap.bundle.min.js"></script>
<script src="static/js/app.js"></script>
<?php
// custom page scripts

View File

@ -3,10 +3,11 @@
"description": "Template for a webapp integrated with an AccountHub server.",
"type": "project",
"require": {
"catfan/medoo": "^1.5",
"catfan/medoo": "^1.7",
"guzzlehttp/guzzle": "^6.2",
"geoip2/geoip2": "~2.0",
"unsplash/unsplash": "^2.4"
"geoip2/geoip2": "^2.11",
"unsplash/unsplash": "^3.1",
"phpmailer/phpmailer": "^6.1"
},
"license": "MPL-2.0",
"authors": [

610
composer.lock generated
View File

@ -4,21 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "14eefa1d98fa62ca2f2fe52de606868c",
"content-hash": "e9f318b9d8bd1dfa6c7fb56c820d1910",
"content-hash": "fca7101f9c5ca04ba9cf15ae46d3867a",
"packages": [
{
"name": "catfan/medoo",
"version": "v1.5.6",
"version": "v1.7.10",
"source": {
"type": "git",
"url": "https://github.com/catfan/Medoo.git",
"reference": "f77a93f72864e892c99d1033b8733e5da8fb0b3b"
"reference": "2d675f73e23f63bbaeb9a8aa33318659a3d3c32f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/catfan/Medoo/zipball/f77a93f72864e892c99d1033b8733e5da8fb0b3b",
"reference": "f77a93f72864e892c99d1033b8733e5da8fb0b3b",
"url": "https://api.github.com/repos/catfan/Medoo/zipball/2d675f73e23f63bbaeb9a8aa33318659a3d3c32f",
"reference": "2d675f73e23f63bbaeb9a8aa33318659a3d3c32f",
"shasum": ""
},
"require": {
@ -32,7 +31,7 @@
"ext-pdo_oci8": "For Oracle version 8 database",
"ext-pdo_pqsql": "For PostgreSQL database",
"ext-pdo_sqlite": "For SQLite database",
"ext-pdo_sqlsrv": "For MSSQL database"
"ext-pdo_sqlsrv": "For MSSQL database on both Window/Liunx platform"
},
"type": "framework",
"autoload": {
@ -50,10 +49,11 @@
"email": "angel@catfan.me"
}
],
"description": "The lightest PHP database framework to accelerate development",
"description": "The lightweight PHP database framework to accelerate development",
"homepage": "https://medoo.in",
"keywords": [
"database",
"database library",
"lightweight",
"mariadb",
"mssql",
@ -64,31 +64,31 @@
"sql",
"sqlite"
],
"time": "2018-03-26 17:54:24"
"time": "2020-02-11T08:20:42+00:00"
},
{
"name": "composer/ca-bundle",
"version": "1.1.1",
"version": "1.2.8",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169"
"reference": "8a7ecad675253e4654ea05505233285377405215"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169",
"reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/8a7ecad675253e4654ea05505233285377405215",
"reference": "8a7ecad675253e4654ea05505233285377405215",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0"
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0"
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
},
"type": "library",
"extra": {
@ -120,30 +120,31 @@
"ssl",
"tls"
],
"time": "2018-03-29 19:57:20"
"time": "2020-08-23T12:54:47+00:00"
},
{
"name": "geoip2/geoip2",
"version": "v2.9.0",
"version": "v2.11.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/GeoIP2-php.git",
"reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77"
"reference": "d01be5894a5c1a3381c58c9b1795cd07f96c30f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/a807fbf65212eef5d8d2db1a1b31082b53633d77",
"reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77",
"url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/d01be5894a5c1a3381c58c9b1795cd07f96c30f7",
"reference": "d01be5894a5c1a3381c58c9b1795cd07f96c30f7",
"shasum": ""
},
"require": {
"maxmind-db/reader": "~1.0",
"maxmind/web-service-common": "~0.5",
"php": ">=5.4"
"ext-json": "*",
"maxmind-db/reader": "~1.8",
"maxmind/web-service-common": "~0.8",
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "4.*",
"phpunit/phpunit": "^8.0 || ^9.0",
"squizlabs/php_codesniffer": "3.*"
},
"type": "library",
@ -160,7 +161,7 @@
{
"name": "Gregory J. Oschwald",
"email": "goschwald@maxmind.com",
"homepage": "http://www.maxmind.com/"
"homepage": "https://www.maxmind.com/"
}
],
"description": "MaxMind GeoIP2 PHP API",
@ -172,31 +173,33 @@
"geolocation",
"maxmind"
],
"time": "2018-04-10 15:32:59"
"time": "2020-10-01T18:48:34+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.2",
"version": "6.5.5",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90"
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/68d0ea14d5a3f42a20e87632a5f84931e2709c90",
"reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.4",
"php": ">=5.5"
"guzzlehttp/psr7": "^1.6.1",
"php": ">=5.5",
"symfony/polyfill-intl-idn": "^1.17.0"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4",
"psr/log": "^1.0"
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.1"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
@ -204,16 +207,16 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.3-dev"
"dev-master": "6.5-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -237,27 +240,27 @@
"rest",
"web service"
],
"time": "2018-03-26 16:33:04"
"time": "2020-06-16T21:01:06+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "v1.3.1",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
"reference": "60d379c243457e073cff02bc323a2a86cb355631"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631",
"reference": "60d379c243457e073cff02bc323a2a86cb355631",
"shasum": ""
},
"require": {
"php": ">=5.5.0"
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
"symfony/phpunit-bridge": "^4.4 || ^5.1"
},
"type": "library",
"extra": {
@ -288,36 +291,41 @@
"keywords": [
"promise"
],
"time": "2016-12-20 10:07:11"
"time": "2020-09-30T07:37:28+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.4.2",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
"reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3",
"reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"ext-zlib": "*",
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.7-dev"
}
},
"autoload": {
@ -347,13 +355,14 @@
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2017-03-20 17:10:46"
"time": "2020-09-30T07:37:11+00:00"
},
{
"name": "hughbertd/oauth2-unsplash",
@ -405,25 +414,25 @@
"oauth2",
"single sign on"
],
"time": "2017-12-14 13:08:42"
"time": "2017-12-14T13:08:42+00:00"
},
{
"name": "league/oauth2-client",
"version": "2.3.0",
"version": "2.5.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth2-client.git",
"reference": "aa2e3df188f0bfd87f7880cc880e906e99923580"
"reference": "d9f2a1e000dc14eb3c02e15d15759385ec7ff0fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/aa2e3df188f0bfd87f7880cc880e906e99923580",
"reference": "aa2e3df188f0bfd87f7880cc880e906e99923580",
"url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/d9f2a1e000dc14eb3c02e15d15759385ec7ff0fb",
"reference": "d9f2a1e000dc14eb3c02e15d15759385ec7ff0fb",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0",
"paragonie/random_compat": "^1|^2",
"guzzlehttp/guzzle": "^6.0 || ^7.0",
"paragonie/random_compat": "^1|^2|^9.99",
"php": "^5.6|^7.0"
},
"require-dev": {
@ -472,29 +481,33 @@
"oauth2",
"single sign on"
],
"time": "2018-01-13 05:27:58"
"time": "2020-07-18T17:54:32+00:00"
},
{
"name": "maxmind-db/reader",
"version": "v1.3.0",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
"reference": "e042b4f8a2dff41e19019faf16427178b07fbd58"
"reference": "b566d429ac9aec10594b0935be8ff38302f8d5c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/e042b4f8a2dff41e19019faf16427178b07fbd58",
"reference": "e042b4f8a2dff41e19019faf16427178b07fbd58",
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/b566d429ac9aec10594b0935be8ff38302f8d5c8",
"reference": "b566d429ac9aec10594b0935be8ff38302f8d5c8",
"shasum": ""
},
"require": {
"php": ">=5.4"
"php": ">=7.2"
},
"conflict": {
"ext-maxminddb": "<1.8.0,>=2.0.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "4.* || 5.*",
"satooshi/php-coveralls": "1.0.*",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpcov": ">=6.0.0",
"phpunit/phpunit": ">=8.0.0,<10.0.0",
"squizlabs/php_codesniffer": "3.*"
},
"suggest": {
@ -516,7 +529,7 @@
{
"name": "Gregory J. Oschwald",
"email": "goschwald@maxmind.com",
"homepage": "http://www.maxmind.com/"
"homepage": "https://www.maxmind.com/"
}
],
"description": "MaxMind DB Reader API",
@ -528,31 +541,31 @@
"geolocation",
"maxmind"
],
"time": "2018-02-21 21:23:33"
"time": "2020-10-01T17:30:21+00:00"
},
{
"name": "maxmind/web-service-common",
"version": "v0.5.0",
"version": "v0.8.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/web-service-common-php.git",
"reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b"
"reference": "ba67d9532cfaf499bd71774b8170d05df4f75fb7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/61a9836fa3bb1743ab89752bae5005d71e78c73b",
"reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b",
"url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/ba67d9532cfaf499bd71774b8170d05df4f75fb7",
"reference": "ba67d9532cfaf499bd71774b8170d05df4f75fb7",
"shasum": ""
},
"require": {
"composer/ca-bundle": "^1.0.3",
"ext-curl": "*",
"ext-json": "*",
"php": ">=5.4"
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "4.*",
"phpunit/phpunit": "^8.0 || ^9.0",
"squizlabs/php_codesniffer": "3.*"
},
"type": "library",
@ -574,37 +587,33 @@
],
"description": "Internal MaxMind Web Service API",
"homepage": "https://github.com/maxmind/web-service-common-php",
"time": "2018-02-12 22:31:54"
"time": "2020-10-01T15:28:36+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.12",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb"
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
"reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
@ -619,10 +628,74 @@
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"time": "2018-04-04 21:24:14"
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.1.8",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "917ab212fa00dc6eacbb26e8bc387ebe40993bc1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/917ab212fa00dc6eacbb26e8bc387ebe40993bc1",
"reference": "917ab212fa00dc6eacbb26e8bc387ebe40993bc1",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"ext-hash": "*",
"php": ">=5.5.0"
},
"require-dev": {
"doctrine/annotations": "^1.2",
"friendsofphp/php-cs-fixer": "^2.2",
"phpunit/phpunit": "^4.8 || ^5.7"
},
"suggest": {
"ext-mbstring": "Needed to send email in multibyte encoding charset",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
},
"type": "library",
"autoload": {
"psr-4": {
"PHPMailer\\PHPMailer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-only"
],
"authors": [
{
"name": "Marcus Bointon",
"email": "phpmailer@synchromedia.co.uk"
},
{
"name": "Jim Jagielski",
"email": "jimjag@gmail.com"
},
{
"name": "Andy Prevost",
"email": "codeworxtech@users.sourceforge.net"
},
{
"name": "Brent R. Matzelle"
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2020-10-09T14:55:58+00:00"
},
{
"name": "psr/http-message",
@ -672,40 +745,339 @@
"request",
"response"
],
"time": "2016-08-06 14:39:51"
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "unsplash/unsplash",
"version": "2.4.3",
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/unsplash/unsplash-php.git",
"reference": "7c6fed642cf4234545624a1263d57c9cb0970fbf"
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/unsplash/unsplash-php/zipball/7c6fed642cf4234545624a1263d57c9cb0970fbf",
"reference": "7c6fed642cf4234545624a1263d57c9cb0970fbf",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.3.0",
"hughbertd/oauth2-unsplash": ">=1.0.3",
"league/oauth2-client": ">=1.4.2",
"php": ">=5.6.0"
"php": ">=5.6"
},
"require-dev": {
"mockery/mockery": "~0.9.0",
"php-vcr/php-vcr": "dev-master",
"php-vcr/phpunit-testlistener-vcr": "*",
"phpunit/phpunit": "~5.1",
"satooshi/php-coveralls": "dev-master",
"vlucas/phpdotenv": "dev-master"
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.19.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "4ad5115c0f5d5172a9fe8147675ec6de266d8826"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/4ad5115c0f5d5172a9fe8147675ec6de266d8826",
"reference": "4ad5115c0f5d5172a9fe8147675ec6de266d8826",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php70": "^1.10",
"symfony/polyfill-php72": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.19-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laurent Bassin",
"email": "laurent@bassin.info"
},
{
"name": "Trevor Rowbotham",
"email": "trevor.rowbotham@pm.me"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"idn",
"intl",
"polyfill",
"portable",
"shim"
],
"time": "2020-10-21T09:57:48+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.19.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8db0ae7936b42feb370840cf24de1a144fb0ef27"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8db0ae7936b42feb370840cf24de1a144fb0ef27",
"reference": "8db0ae7936b42feb370840cf24de1a144fb0ef27",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.19-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"time": "2020-10-23T09:01:57+00:00"
},
{
"name": "symfony/polyfill-php70",
"version": "v1.19.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php70.git",
"reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3fe414077251a81a1b15b1c709faf5c2fbae3d4e",
"reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e",
"shasum": ""
},
"require": {
"paragonie/random_compat": "~1.0|~2.0|~9.99",
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.19-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php70\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"time": "2020-10-23T09:01:57+00:00"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.19.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "beecef6b463b06954638f02378f52496cb84bacc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/beecef6b463b06954638f02378f52496cb84bacc",
"reference": "beecef6b463b06954638f02378f52496cb84bacc",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.19-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"time": "2020-10-23T09:01:57+00:00"
},
{
"name": "unsplash/unsplash",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/unsplash/unsplash-php.git",
"reference": "6f9cf13ff4538589717bd7eadabd65a2b175c321"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/unsplash/unsplash-php/zipball/6f9cf13ff4538589717bd7eadabd65a2b175c321",
"reference": "6f9cf13ff4538589717bd7eadabd65a2b175c321",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.3.0|^7.0.1",
"hughbertd/oauth2-unsplash": ">=1.0.3",
"league/oauth2-client": ">=1.4.2",
"php": ">=7.3.0"
},
"require-dev": {
"mockery/mockery": "~1.4.0",
"php-vcr/php-vcr": "~1.4",
"php-vcr/phpunit-testlistener-vcr": "~3.1",
"phpunit/phpunit": "~9.0",
"vlucas/phpdotenv": "~4.1.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Crew\\Unsplash\\": "src/"
"Unsplash\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -713,14 +1085,6 @@
"MIT"
],
"authors": [
{
"name": "Charles Lalonde",
"email": "charles@pickcrew.com"
},
{
"name": "Hugh Downer",
"email": "hugh.downer@gmail.com"
},
{
"name": "Aaron Klaassen",
"email": "aaron@unsplash.com"
@ -728,10 +1092,18 @@
{
"name": "Luke Chesser",
"email": "luke@unsplash.com"
},
{
"name": "Charles Lalonde",
"email": "charles@pickcrew.com"
},
{
"name": "Hugh Downer",
"email": "hugh.downer@gmail.com"
}
],
"description": "Wrapper to access the Unsplash API and photo library",
"time": "2018-03-30 17:45:15"
"time": "2020-10-05T22:04:54+00:00"
}
],
"packages-dev": [],

242
index.php
View File

@ -1,161 +1,131 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
require_once __DIR__ . "/required.php";
require_once __DIR__ . "/lib/login.php";
// if we're logged in, we don't need to be here.
if ($_SESSION['loggedin']) {
if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
header('Location: app.php');
die();
}
/* Authenticate user */
$userpass_ok = false;
$multiauth = false;
if (checkLoginServer()) {
if ($VARS['progress'] == "1") {
if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && verifyCaptcheck($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) {
$errmsg = "";
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
switch (get_account_status($VARS['username'])) {
case "LOCKED_OR_DISABLED":
$alert = lang("account locked", false);
break;
case "TERMINATED":
$alert = lang("account terminated", false);
break;
case "CHANGE_PASSWORD":
$alert = lang("password expired", false);
case "NORMAL":
$userpass_ok = true;
break;
case "ALERT_ON_ACCESS":
sendLoginAlertEmail($VARS['username']);
$userpass_ok = true;
break;
}
if ($userpass_ok) {
$_SESSION['passok'] = true; // stop logins using only username and authcode
if (userHasTOTP($VARS['username'])) {
$multiauth = true;
} else {
doLoginUser($VARS['username'], $VARS['password']);
header('Location: app.php');
die("Logged in, go to app.php");
}
}
} else {
if (!is_empty($errmsg)) {
$alert = lang2("login server error", ['arg' => $errmsg], false);
} else {
$alert = lang("login incorrect", false);
}
}
} else {
$alert = lang("captcha error", false);
}
} else if ($VARS['progress'] == "2") {
if ($_SESSION['passok'] !== true) {
// stop logins using only username and authcode
sendError("Password integrity check failed!");
}
if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
if (doLoginUser($VARS['username'])) {
header('Location: app.php');
die("Logged in, go to app.php");
} else {
$alert = lang("login server user data error", false);
}
} else {
$alert = lang("2fa incorrect", false);
}
}
} else {
$alert = lang("login server unavailable", false);
}
header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
header("Link: <static/css/index.css>; rel=preload; as=style", false);
header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
?>
<!DOCTYPE html>
<html>
<head>
/**
* Show a simple HTML page with a line of text and a button. Matches the UI of
* the AccountHub login flow.
*
* @global type $SETTINGS
* @global type $SECURE_NONCE
* @global type $Strings
* @param string $title Text to show, passed through i18n
* @param string $button Button text, passed through i18n
* @param string $url URL for the button
*/
function showHTML(string $title, string $button, string $url) {
global $SETTINGS, $SECURE_NONCE, $Strings;
?>
<!DOCTYPE html>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo SITE_TITLE; ?></title>
<title><?php echo $SETTINGS['site_title']; ?></title>
<link rel="icon" href="static/img/logo.svg">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
<link href="static/css/index.css" rel="stylesheet">
<?php if (CAPTCHA_ENABLED) { ?>
<script src="<?php echo CAPTCHA_SERVER ?>/captcheck.dist.js"></script>
<?php } ?>
</head>
<body>
<div class="row justify-content-center">
<div class="col-auto">
<img class="banner-image" src="static/img/logo.svg" />
</div>
</div>
<div class="row justify-content-center">
<div class="card col-11 col-xs-11 col-sm-8 col-md-6 col-lg-4">
<div class="card-body">
<h5 class="card-title"><?php lang("sign in"); ?></h5>
<form action="" method="POST">
<?php
if (!is_empty($alert)) {
?>
<div class="alert alert-danger">
<i class="fa fa-fw fa-exclamation-triangle"></i> <?php echo $alert; ?>
</div>
<?php
<style nonce="<?php echo $SECURE_NONCE; ?>">
.display-5 {
font-size: 2.5rem;
font-weight: 300;
line-height: 1.2;
}
if ($multiauth != true) {
?>
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
<?php if (CAPTCHA_ENABLED) { ?>
<div class="captcheck_container" data-stylenonce="<?php echo $SECURE_NONCE; ?>"></div>
<br />
<?php } ?>
<input type="hidden" name="progress" value="1" />
<?php
} else if ($multiauth) {
?>
<div class="alert alert-info">
<?php lang("2fa prompt"); ?>
</div>
<input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
<input type="hidden" name="progress" value="2" />
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
<?php
.banner-image {
max-height: 100px;
margin: 2em auto;
border: 1px solid grey;
border-radius: 15%;
}
?>
<button type="submit" class="btn btn-primary">
<?php lang("continue"); ?>
</button>
</form>
</style>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 text-center">
<img class="banner-image" src="./static/img/logo.svg" />
</div>
<div class="col-12 text-center">
<h1 class="display-5 mb-4"><?php $Strings->get($title); ?></h1>
</div>
<div class="col-12 col-sm-8 col-lg-6">
<div class="card mt-4">
<div class="card-body">
<a href="<?php echo $url; ?>" class="btn btn-primary btn-block"><?php $Strings->get($button); ?></a>
</div>
</div>
</div>
<div class="footer">
<?php echo FOOTER_TEXT; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
</div>
</div>
<script src="static/js/jquery-3.3.1.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
</body>
</html>
<?php
}
if (!empty($_GET['logout'])) {
showHTML("You have been logged out.", "Log in again", "./index.php");
die();
}
if (empty($_SESSION["login_code"])) {
$redirecttologin = true;
} else {
try {
$uidinfo = AccountHubApi::get("checkloginkey", ["code" => $_SESSION["login_code"]]);
if ($uidinfo["status"] == "ERROR") {
throw new Exception();
}
if (is_numeric($uidinfo['uid'])) {
$user = new User($uidinfo['uid'] * 1);
foreach ($SETTINGS['permissions'] as $perm) {
if (!$user->hasPermission($perm)) {
showHTML("no access permission", "sign out", "./action.php?action=signout");
die();
}
}
Session::start($user);
$_SESSION["login_code"] = null;
header('Location: app.php');
showHTML("Logged in", "Continue", "./app.php");
die();
} else {
throw new Exception();
}
} catch (Exception $ex) {
$redirecttologin = true;
}
}
if ($redirecttologin) {
try {
$urlbase = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "");
$iconurl = $urlbase . str_replace("index.php", "", $_SERVER["REQUEST_URI"]) . "static/img/logo.svg";
$codedata = AccountHubApi::get("getloginkey", ["appname" => $SETTINGS["site_title"], "appicon" => $iconurl]);
if ($codedata['status'] != "OK") {
throw new Exception($Strings->get("login server unavailable", false));
}
$redirecturl = $urlbase . $_SERVER['REQUEST_URI'];
$_SESSION["login_code"] = $codedata["code"];
$locationurl = $codedata["loginurl"] . "?code=" . htmlentities($codedata["code"]) . "&redirect=" . htmlentities($redirecturl);
header("Location: $locationurl");
showHTML("Continue", "Continue", $locationurl);
die();
} catch (Exception $ex) {
sendError($ex->getMessage());
}
}

View File

@ -1,141 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
define("STRINGS", [
"sign in" => "Sign In",
"username" => "Username",
"password" => "Password",
"continue" => "Continue",
"authcode" => "Authentication code",
"2fa prompt" => "Enter the six-digit code from your mobile authenticator app.",
"2fa incorrect" => "Authentication code incorrect.",
"login incorrect" => "Login incorrect.",
"login server unavailable" => "Login server unavailable. Try again later or contact technical support.",
"account locked" => "This account has been disabled. Contact technical support.",
"password expired" => "You must change your password before continuing.",
"account terminated" => "Account terminated. Access denied.",
"account state error" => "Your account state is not stable. Log out, restart your browser, and try again.",
"welcome user" => "Welcome, {user}!",
"sign out" => "Sign out",
"settings" => "Settings",
"options" => "Options",
"404 error" => "404 Error",
"page not found" => "Page not found.",
"invalid parameters" => "Invalid request parameters.",
"login server error" => "The login server returned an error: {arg}",
"login server user data error" => "The login server refused to provide account information. Try again or contact technical support.",
"captcha error" => "There was a problem with the CAPTCHA (robot test). Try again.",
"actions" => "Actions",
"no permission" => "You don't have permission to do that.",
"home" => "Home",
"editor" => "Editor",
"sites" => "Sites",
"theme" => "Theme",
"name" => "Name",
"new site" => "New Site",
"site name" => "Site Name",
"url" => "URL",
"adding site" => "Creating site {site}",
"editing site" => "Editing {site}",
"settings saved" => "Settings saved",
"theme type" => "Theme type",
"single page" => "Single page",
"multiple page" => "Multiple page",
"templates" => "Templates",
"template" => "Template",
"color styles" => "Color styles",
"save" => "Save",
"edit" => "Edit",
"view" => "View",
"preview" => "Preview",
"cancel" => "Cancel",
"save needed" => "Press Save to see recent changes.",
"saved" => "Saved",
"icon" => "Icon",
"image" => "Image",
"link" => "Link",
"text" => "Text",
"select page or enter url" => "Select a page or enter URL",
"edit component" => "Edit component",
"default" => "Default",
"page added" => "Page added.",
"chosen page id slug already taken" => "Chosen page ID (slug) already taken. Choose another.",
"template missing" => "Template missing from theme.",
"new page" => "New Page",
"title" => "Title",
"page id" => "Page ID (slug)",
"add page" => "Add page",
"page settings" => "Page Settings",
"analytics" => "Analytics",
"today" => "Today",
"this week" => "This Week",
"visit" => "visit",
"visits" => "visits",
"page view" => "page view",
"page views" => "page views",
"site" => "Site",
"filter by site" => "Filter by site",
"all sites" => "All Sites",
"filter" => "Filter",
"start date" => "Start date",
"end date" => "End date",
"recent actions" => "Recent Actions",
"overview" => "Overview",
"views per visit" => "views per visit",
"visits over time" => "Visits Over Time",
"page views over time" => "Page Views Over Time",
"page ranking" => "Page Ranking",
"x views" => "{views} views",
"no data" => "No data.",
"visitor map" => "Visitor Map",
"enable built-in analytics" => "Enable built-in analytics",
"disable built-in analytics" => "Disable built-in analytics",
"extra code" => "Extra code (inserted in site head)",
"company info" => "Company Info",
"phone" => "Phone",
"address" => "Address",
"email" => "Email",
"social links" => "Social Links",
"site info" => "Site Info",
"loading" => "Loading...",
"current" => "Current",
"messages" => "Messages",
"message" => "Message",
"date" => "Date",
"message deleted" => "Message deleted.",
"files" => "Files",
"browse" => "Browse",
"upload" => "Upload",
"operation cancelled for security reasons" => "Operation cancelled for security reasons.",
"upload successful" => "Upload successful.",
"upload warning" => "Upload finished with some problems:<br>{arg}",
"destination folder does not exist" => "Destination folder does not exist.",
"destination folder does not allow uploads" => "Destination folder does not allow uploads.",
"uploaded data too large" => "Uploaded data too large.",
"undeletable file" => "The file could not be deleted.",
"folder not empty" => "Folder must be empty to be deleted.",
"file not deleted" => "The file could not be deleted.",
"file deleted" => "File deleted.",
"folder deleted" => "Folder deleted.",
"folder created" => "Folder created.",
"folder not created" => "Folder not created.",
"nothing here" => "There doesn't seem to be anything here...",
"navbar options" => "Site Menu Options",
"in navbar" => "Add page to menu",
"navbar title" => "Page title for menu",
"navbar position" => "Menu position (drag to change position):",
"remove image" => "Remove image",
"site footer links" => "Site Footer Links",
"uploaded files" => "Uploaded Files",
"stock photos" => "Free Stock Photos",
"load more" => "Load more",
"search images" => "Search images",
"x results" => "{results} results",
"reply" => "Reply",
"delete" => "Delete",
"new folder" => "New Folder",
"new" => "New",
]);

7
langs/en/core.json Normal file
View File

@ -0,0 +1,7 @@
{
"sign out": "Sign out",
"404 error": "404 Error",
"page not found": "Page not found.",
"invalid parameters": "Invalid request parameters.",
"login server error": "The login server returned an error: {arg}"
}

8
langs/en/index.json Normal file
View File

@ -0,0 +1,8 @@
{
"You have been logged out.": "You have been logged out.",
"Log in again": "Log in again",
"login server unavailable": "Login server unavailable. Try again later or contact technical support.",
"no access permission": "You do not have permission to access this system.",
"Logged in": "Logged in",
"Continue": "Continue"
}

116
langs/en/strings.json Normal file
View File

@ -0,0 +1,116 @@
{
"actions": "Actions",
"no permission": "You don't have permission to do that.",
"editor": "Editor",
"sites": "Sites",
"theme": "Theme",
"name": "Name",
"new site": "New Site",
"site name": "Site Name",
"url": "URL",
"adding site": "Creating site {site}",
"editing site": "Editing {site}",
"settings saved": "Settings saved",
"theme type": "Theme type",
"single page": "Single page",
"multiple page": "Multiple page",
"templates": "Templates",
"template": "Template",
"color styles": "Color styles",
"save": "Save",
"edit": "Edit",
"view": "View",
"preview": "Preview",
"cancel": "Cancel",
"save needed": "Press Save to see recent changes.",
"saved": "Saved",
"icon": "Icon",
"image": "Image",
"link": "Link",
"text": "Text",
"select page or enter url": "Select a page or enter URL",
"edit component": "Edit component",
"default": "Default",
"page added": "Page added.",
"chosen page id slug already taken": "Chosen page ID (slug) already taken. Choose another.",
"template missing": "Template missing from theme.",
"new page": "New Page",
"title": "Title",
"page id": "Page ID (slug)",
"add page": "Add page",
"page settings": "Page Settings",
"analytics": "Analytics",
"today": "Today",
"this week": "This Week",
"visit": "visit",
"visits": "visits",
"page view": "page view",
"page views": "page views",
"site": "Site",
"filter by site": "Filter by site",
"all sites": "All Sites",
"filter": "Filter",
"start date": "Start date",
"end date": "End date",
"recent actions": "Recent Actions",
"overview": "Overview",
"views per visit": "views per visit",
"visits over time": "Visits Over Time",
"page views over time": "Page Views Over Time",
"page ranking": "Page Ranking",
"x views": "{views} views",
"no data": "No data.",
"visitor map": "Visitor Map",
"enable built-in analytics": "Enable built-in analytics",
"disable built-in analytics": "Disable built-in analytics",
"extra code": "Extra code (inserted in site head)",
"company info": "Company Info",
"phone": "Phone",
"address": "Address",
"email": "Email",
"social links": "Social Links",
"site info": "Site Info",
"loading": "Loading...",
"current": "Current",
"messages": "Messages",
"message": "Message",
"date": "Date",
"message deleted": "Message deleted.",
"files": "Files",
"browse": "Browse",
"upload": "Upload",
"operation cancelled for security reasons": "Operation cancelled for security reasons.",
"upload successful": "Upload successful.",
"upload warning": "Upload finished with some problems:<br>{arg}",
"destination folder does not exist": "Destination folder does not exist.",
"destination folder does not allow uploads": "Destination folder does not allow uploads.",
"uploaded data too large": "Uploaded data too large.",
"undeletable file": "The file could not be deleted.",
"folder not empty": "Folder must be empty to be deleted.",
"file not deleted": "The file could not be deleted.",
"file deleted": "File deleted.",
"folder deleted": "Folder deleted.",
"folder created": "Folder created.",
"folder not created": "Folder not created.",
"nothing here": "There doesn't seem to be anything here...",
"navbar options": "Site Menu Options",
"in navbar": "Add page to menu",
"navbar title": "Page title for menu",
"navbar position": "Menu position (drag to change position):",
"remove image": "Remove image",
"site footer links": "Site Footer Links",
"uploaded files": "Uploaded Files",
"stock photos": "Free Stock Photos",
"load more": "Load more",
"search images": "Search images",
"x results": "{results} results",
"reply": "Reply",
"delete": "Delete",
"new folder": "New Folder",
"new": "New",
"search": "Search",
"no results": "No results.",
"contact form": "Contact Form",
"contact form messages will be forwarded to this email address": "Contact form messages will be forwarded to this email address, if it is set.",
"settings": "Settings"
}

3
langs/en/titles.json Normal file
View File

@ -0,0 +1,3 @@
{
"home": "Home"
}

56
lib/AccountHubApi.lib.php Normal file
View File

@ -0,0 +1,56 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class AccountHubApi {
public static function get(string $action, array $data = null, bool $throwex = false) {
global $SETTINGS;
$content = [
"action" => $action,
"key" => $SETTINGS['accounthub']['key']
];
if (!is_null($data)) {
$content = array_merge($content, $data);
}
$options = [
'http' => [
'method' => 'POST',
'content' => json_encode($content),
'header' => "Content-Type: application/json\r\n" .
"Accept: application/json\r\n",
"ignore_errors" => true
]
];
$context = stream_context_create($options);
$result = file_get_contents($SETTINGS['accounthub']['api'], false, $context);
$response = json_decode($result, true);
if ($result === false || !AccountHubApi::checkHttpRespCode($http_response_header) || json_last_error() != JSON_ERROR_NONE) {
if ($throwex) {
throw new Exception($result);
} else {
sendError($result);
}
}
return $response;
}
private static function checkHttpRespCode(array $headers): bool {
foreach ($headers as $header) {
if (preg_match("/HTTP\/[0-9]\.[0-9] [0-9]{3}.*/", $header)) {
$respcode = explode(" ", $header)[1] * 1;
if ($respcode >= 200 && $respcode < 300) {
return true;
}
}
}
return false;
}
}

13
lib/Exceptions.lib.php Normal file
View File

@ -0,0 +1,13 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class IncorrectPasswordException extends Exception {
public function __construct(string $message = "Incorrect password.", int $code = 0, \Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}

326
lib/FormBuilder.lib.php Normal file
View File

@ -0,0 +1,326 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class FormBuilder {
private $items = [];
private $hiddenitems = [];
private $title = "";
private $icon = "";
private $buttons = [];
private $action = "action.php";
private $method = "POST";
private $id = "editform";
/**
* Create a form with autogenerated HTML.
*
* @param string $title Form title/heading
* @param string $icon FontAwesone icon next to the title.
* @param string $action URL to submit the form to.
* @param string $method Form submission method (POST, GET, etc.)
*/
public function __construct(string $title = "Untitled Form", string $icon = "fas fa-file-alt", string $action = "action.php", string $method = "POST") {
$this->title = $title;
$this->icon = $icon;
$this->action = $action;
$this->method = $method;
}
/**
* Set the title of the form.
* @param string $title
*/
public function setTitle(string $title) {
$this->title = $title;
}
/**
* Set the icon for the form.
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
*/
public function setIcon(string $icon) {
$this->icon = $icon;
}
/**
* Set the URL the form will submit to.
* @param string $action
*/
public function setAction(string $action) {
$this->action = $action;
}
/**
* Set the form submission method (GET, POST, etc)
* @param string $method
*/
public function setMethod(string $method = "POST") {
$this->method = $method;
}
/**
* Set the form ID.
* @param string $id
*/
public function setID(string $id = "editform") {
$this->id = $id;
}
/**
* Add an input to the form.
*
* @param string $name Element name
* @param string $value Element value
* @param string $type Input type (text, number, date, select, tel...)
* @param bool $required If the element is required for form submission.
* @param string $id Element ID
* @param array $options Array of [value => text] pairs for a select element
* @param string $label Text label to display near the input
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
* @param int $width Bootstrap column width for the input, out of 12.
* @param int $minlength Minimum number of characters for the input.
* @param int $maxlength Maximum number of characters for the input.
* @param string $pattern Regex pattern for custom client-side validation.
* @param string $error Message to show if the input doesn't validate.
*/
public function addInput(string $name, string $value = "", string $type = "text", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
$item = [
"name" => $name,
"value" => $value,
"type" => $type,
"required" => $required,
"label" => $label,
"icon" => $icon,
"width" => $width,
"minlength" => $minlength,
"maxlength" => $maxlength
];
if (!empty($id)) {
$item["id"] = $id;
}
if (!empty($options) && $type == "select") {
$item["options"] = $options;
}
if (!empty($pattern)) {
$item["pattern"] = $pattern;
}
if (!empty($error)) {
$item["error"] = $error;
}
$this->items[] = $item;
}
/**
* Add a text input.
*
* @param string $name Element name
* @param string $value Element value
* @param bool $required If the element is required for form submission.
* @param string $id Element ID
* @param string $label Text label to display near the input
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
* @param int $width Bootstrap column width for the input, out of 12.
* @param int $minlength Minimum number of characters for the input.
* @param int $maxlength Maximum number of characters for the input.
* @param string $pattern Regex pattern for custom client-side validation.
* @param string $error Message to show if the input doesn't validate.
*/
public function addTextInput(string $name, string $value = "", bool $required = true, string $id = "", string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
$this->addInput($name, $value, "text", $required, $id, null, $label, $icon, $width, $minlength, $maxlength, $pattern, $error);
}
/**
* Add a select dropdown.
*
* @param string $name Element name
* @param string $value Element value
* @param bool $required If the element is required for form submission.
* @param string $id Element ID
* @param array $options Array of [value => text] pairs for a select element
* @param string $label Text label to display near the input
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
* @param int $width Bootstrap column width for the input, out of 12.
*/
public function addSelect(string $name, string $value = "", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4) {
$this->addInput($name, $value, "select", $required, $id, $options, $label, $icon, $width);
}
/**
* Add a button to the form.
*
* @param string $text Text string to show on the button.
* @param string $icon FontAwesome icon to show next to the text.
* @param string $href If not null, the button will actually be a hyperlink.
* @param string $type Usually "button" or "submit". Ignored if $href is set.
* @param string $id The element ID.
* @param string $name The element name for the button.
* @param string $value The form value for the button. Ignored if $name is null.
* @param string $class The CSS classes for the button, if a standard success-colored one isn't right.
*/
public function addButton(string $text, string $icon = "", string $href = null, string $type = "button", string $id = null, string $name = null, string $value = "", string $class = "btn btn-success") {
$button = [
"text" => $text,
"icon" => $icon,
"class" => $class,
"type" => $type,
"id" => $id,
"href" => $href,
"name" => $name,
"value" => $value
];
$this->buttons[] = $button;
}
/**
* Add a hidden input.
* @param string $name
* @param string $value
*/
public function addHiddenInput(string $name, string $value) {
$this->hiddenitems[$name] = $value;
}
/**
* Generate the form HTML.
* @param bool $echo If false, returns HTML string instead of outputting it.
*/
public function generate(bool $echo = true) {
$html = <<<HTMLTOP
<form action="$this->action" method="$this->method" id="$this->id">
<div class="card">
<h3 class="card-header d-flex">
<div>
<i class="$this->icon"></i> $this->title
</div>
</h3>
<div class="card-body">
<div class="row">
HTMLTOP;
foreach ($this->items as $item) {
$required = $item["required"] ? "required" : "";
$id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
$pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
if (empty($item['type'])) {
$item['type'] = "text";
}
$itemhtml = "";
$itemlabel = "";
if ($item['type'] == "textarea") {
$itemlabel = "<label class=\"mb-0\"><i class=\"$item[icon]\"></i> $item[label]:</label>";
} else if ($item['type'] != "checkbox") {
$itemlabel = "<label class=\"mb-0\">$item[label]:</label>";
}
$strippedlabel = strip_tags($item['label']);
$itemhtml .= <<<ITEMTOP
\n\n <div class="col-12 col-md-$item[width]">
<div class="form-group mb-3">
$itemlabel
ITEMTOP;
$inputgrouptop = <<<INPUTG
\n <div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="$item[icon]"></i></span>
</div>
INPUTG;
switch ($item['type']) {
case "select":
$itemhtml .= $inputgrouptop;
$itemhtml .= <<<SELECT
\n <select class="form-control" name="$item[name]" aria-label="$strippedlabel" $required>
SELECT;
foreach ($item['options'] as $value => $label) {
$selected = "";
if (!empty($item['value']) && $value == $item['value']) {
$selected = " selected";
}
$itemhtml .= "\n <option value=\"$value\"$selected>$label</option>";
}
$itemhtml .= "\n </select>";
break;
case "checkbox":
$itemhtml .= $inputgrouptop;
$itemhtml .= <<<CHECKBOX
\n <div class="form-group form-check">
<input type="checkbox" name="$item[name]" $id class="form-check-input" value="$item[value]" $required aria-label="$strippedlabel">
<label class="form-check-label">$item[label]</label>
</div>
CHECKBOX;
break;
case "textarea":
$val = htmlentities($item['value']);
$itemhtml .= <<<TEXTAREA
\n <textarea class="form-control" id="info" name="$item[name]" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $required>$val</textarea>
TEXTAREA;
break;
default:
$itemhtml .= $inputgrouptop;
$itemhtml .= <<<INPUT
\n <input type="$item[type]" name="$item[name]" $id class="form-control" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $pattern value="$item[value]" $required />
INPUT;
break;
}
if (!empty($item["error"])) {
$itemhtml .= <<<ERROR
\n <div class="invalid-feedback">
$item[error]
</div>
ERROR;
}
if ($item["type"] != "textarea") {
$itemhtml .= "\n </div>";
}
$itemhtml .= <<<ITEMBOTTOM
\n </div>
</div>\n
ITEMBOTTOM;
$html .= $itemhtml;
}
$html .= <<<HTMLBOTTOM
</div>
</div>
HTMLBOTTOM;
if (!empty($this->buttons)) {
$html .= "\n <div class=\"card-footer d-flex\">";
foreach ($this->buttons as $btn) {
$btnhtml = "";
$inner = "<i class=\"$btn[icon]\"></i> $btn[text]";
$id = empty($btn['id']) ? "" : "id=\"$btn[id]\"";
if (!empty($btn['href'])) {
$btnhtml = "<a href=\"$btn[href]\" class=\"$btn[class]\" $id>$inner</a>";
} else {
$name = empty($btn['name']) ? "" : "name=\"$btn[name]\"";
$value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : "";
$btnhtml = "<button type=\"$btn[type]\" class=\"$btn[class]\" $id $name $value>$inner</button>";
}
$html .= "\n $btnhtml";
}
$html .= "\n </div>";
}
$html .= "\n </div>";
foreach ($this->hiddenitems as $name => $value) {
$value = htmlentities($value);
$html .= "\n <input type=\"hidden\" name=\"$name\" value=\"$value\" />";
}
$html .= "\n</form>\n";
if ($echo) {
echo $html;
}
return $html;
}
}

135
lib/IPUtils.lib.php Normal file
View File

@ -0,0 +1,135 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
class IPUtils {
/**
* Check if a given ipv4 address is in a given cidr
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return boolean true if the ip is in this range / false if not.
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
*/
public static function ip4_in_cidr($ip, $cidr) {
if (strpos($cidr, '/') == false) {
$cidr .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
list( $cidr, $netmask ) = explode('/', $cidr, 2);
$range_decimal = ip2long($cidr);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
}
/**
* Check if a given ipv6 address is in a given cidr
* @param string $ip IP to check in IPV6 format
* @param string $cidr CIDR netmask
* @return boolean true if the IP is in this range, false otherwise.
* @author MW. <https://stackoverflow.com/a/7952169>
*/
public static function ip6_in_cidr($ip, $cidr) {
$address = inet_pton($ip);
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
$subnetMask = explode("/", $cidr)[1];
$addr = str_repeat("f", $subnetMask / 4);
switch ($subnetMask % 4) {
case 0:
break;
case 1:
$addr .= "8";
break;
case 2:
$addr .= "c";
break;
case 3:
$addr .= "e";
break;
}
$addr = str_pad($addr, 32, '0');
$addr = pack("H*", $addr);
$binMask = $addr;
return ($address & $binMask) == $subnetAddress;
}
/**
* Check if the REMOTE_ADDR is on Cloudflare's network.
* @return boolean true if it is, otherwise false
*/
public static function validateCloudflare() {
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// Using IPv6
$cloudflare_ips_v6 = [
"2400:cb00::/32",
"2405:8100::/32",
"2405:b500::/32",
"2606:4700::/32",
"2803:f800::/32",
"2c0f:f248::/32",
"2a06:98c0::/29"
];
$valid = false;
foreach ($cloudflare_ips_v6 as $cidr) {
if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
} else {
// Using IPv4
$cloudflare_ips_v4 = [
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"104.16.0.0/12",
"108.162.192.0/18",
"131.0.72.0/22",
"141.101.64.0/18",
"162.158.0.0/15",
"172.64.0.0/13",
"173.245.48.0/20",
"188.114.96.0/20",
"190.93.240.0/20",
"197.234.240.0/22",
"198.41.128.0/17"
];
$valid = false;
foreach ($cloudflare_ips_v4 as $cidr) {
if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
}
return $valid;
}
/**
* Makes a good guess at the client's real IP address.
*
* @return string Client IP or `0.0.0.0` if we can't find anything
*/
public static function getClientIP() {
// If CloudFlare is in the mix, we should use it.
// Check if the request is actually from CloudFlare before trusting it.
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
if (validateCloudflare()) {
return $_SERVER["HTTP_CF_CONNECTING_IP"];
}
}
if (isset($_SERVER["REMOTE_ADDR"])) {
return $_SERVER["REMOTE_ADDR"];
}
return "0.0.0.0"; // This will not happen unless we aren't a web server
}
}

80
lib/Login.lib.php Normal file
View File

@ -0,0 +1,80 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Login {
const BAD_USERPASS = 1;
const BAD_2FA = 2;
const ACCOUNT_DISABLED = 3;
const LOGIN_OK = 4;
public static function auth(string $username, string $password, string $twofa = ""): int {
global $database;
$username = strtolower($username);
$user = User::byUsername($username);
if (!$user->exists()) {
return Login::BAD_USERPASS;
}
if (!$user->checkPassword($password)) {
return Login::BAD_USERPASS;
}
if ($user->has2fa()) {
if (!$user->check2fa($twofa)) {
return Login::BAD_2FA;
}
}
switch ($user->getStatus()->get()) {
case AccountStatus::TERMINATED:
return Login::BAD_USERPASS;
case AccountStatus::LOCKED_OR_DISABLED:
return Login::ACCOUNT_DISABLED;
case AccountStatus::NORMAL:
default:
return Login::LOGIN_OK;
}
return Login::LOGIN_OK;
}
/**
* Check the login server API for sanity
* @return boolean true if OK, else false
*/
public static function checkLoginServer() {
try {
$resp = AccountHubApi::get("ping");
if ($resp['status'] == "OK") {
return true;
} else {
return false;
}
} catch (Exception $e) {
return false;
}
}
/**
* Checks if the given AccountHub API key is valid by attempting to
* access the API with it.
* @param String $key The API key to check
* @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
*/
function checkAPIKey($key) {
try {
$resp = AccountHubApi::get("ping", null, true);
return false;
} catch (Exception $e) {
return false;
}
}
}

53
lib/Notifications.lib.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Notifications {
/**
* Add a new notification.
* @global $database
* @param User $user
* @param string $title
* @param string $content
* @param string $timestamp If left empty, the current date and time will be used.
* @param string $url
* @param bool $sensitive If true, the notification is marked as containing sensitive content, and the $content might be hidden on lockscreens and other non-secure places.
* @return int The newly-created notification ID.
* @throws Exception
*/
public static function add(User $user, string $title, string $content, string $timestamp = "", string $url = "", bool $sensitive = false): int {
global $Strings;
if ($user->exists()) {
if (empty($title) || empty($content)) {
throw new Exception($Strings->get("invalid parameters", false));
}
$timestamp = date("Y-m-d H:i:s");
if (!empty($timestamp)) {
$timestamp = date("Y-m-d H:i:s", strtotime($timestamp));
}
$resp = AccountHubApi::get("addnotification", [
'uid' => $user->getUID(),
'title' => $title,
'content' => $content,
'timestamp' => $timestamp,
'url' => $url,
'sensitive' => $sensitive
]
);
if ($resp['status'] == "OK") {
return $resp['id'] * 1;
} else {
return false;
}
}
throw new Exception($Strings->get("user does not exist", false));
}
}

19
lib/Session.lib.php Normal file
View File

@ -0,0 +1,19 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Session {
public static function start(User $user) {
$_SESSION['username'] = $user->getUsername();
$_SESSION['uid'] = $user->getUID();
$_SESSION['email'] = $user->getEmail();
$_SESSION['realname'] = $user->getName();
$_SESSION['loggedin'] = true;
}
}

122
lib/Strings.lib.php Normal file
View File

@ -0,0 +1,122 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* Provides translated language strings.
*/
class Strings {
private $language = "en";
private $strings = [];
public function __construct($language = "en") {
if (!preg_match("/[a-zA-Z\_\-]+/", $language)) {
throw new Exception("Invalid language code $language");
}
$this->load("en");
if ($language == "en") {
return;
}
if (file_exists(__DIR__ . "/../langs/$language/")) {
$this->language = $language;
$this->load($language);
} else {
trigger_error("Language $language could not be found.", E_USER_WARNING);
}
}
/**
* Load all JSON files for the specified language.
* @param string $language
*/
private function load(string $language) {
$files = glob(__DIR__ . "/../langs/$language/*.json");
foreach ($files as $file) {
$strings = json_decode(file_get_contents($file), true);
foreach ($strings as $key => $val) {
if (array_key_exists($key, $this->strings)) {
trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING);
}
$this->strings[$key] = $val;
}
}
}
/**
* Add language strings dynamically.
* @param array $strings ["key" => "value", ...]
*/
public function addStrings(array $strings) {
foreach ($strings as $key => $val) {
$this->strings[$key] = $val;
}
}
/**
* I18N string getter. If the key isn't found, it outputs the key itself.
* @param string $key
* @param bool $echo True to echo the result, false to return it. Default is true.
* @return string
*/
public function get(string $key, bool $echo = true): string {
$str = $key;
if (array_key_exists($key, $this->strings)) {
$str = $this->strings[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
}
if ($echo) {
echo $str;
}
return $str;
}
/**
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
* @param string $key
* @param array $replace key-value array of replacements.
* If the string value is "hello {abc}" and you give ["abc" => "123"], the
* result will be "hello 123".
* @param bool $echo True to echo the result, false to return it. Default is true.
* @return string
*/
public function build(string $key, array $replace, bool $echo = true): string {
$str = $key;
if (array_key_exists($key, $this->strings)) {
$str = $this->strings[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
}
foreach ($replace as $find => $repl) {
$str = str_replace("{" . $find . "}", $repl, $str);
}
if ($echo) {
echo $str;
}
return $str;
}
/**
* Builds and returns a JSON key:value string for the supplied array of keys.
* @param array $keys ["key1", "key2", ...]
*/
public function getJSON(array $keys): string {
$strings = [];
foreach ($keys as $k) {
$strings[$k] = $this->get($k, false);
}
return json_encode($strings);
}
}

221
lib/User.lib.php Normal file
View File

@ -0,0 +1,221 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class User {
private $uid = null;
private $username;
private $email;
private $realname;
private $has2fa = false;
private $exists = false;
public function __construct(int $uid, string $username = "") {
// Check if user exists
$resp = AccountHubApi::get("userexists", ["uid" => $uid]);
if ($resp['status'] == "OK" && $resp['exists'] === true) {
$this->exists = true;
} else {
$this->uid = $uid;
$this->username = $username;
$this->exists = false;
}
if ($this->exists) {
// Get user info
$resp = AccountHubApi::get("userinfo", ["uid" => $uid]);
if ($resp['status'] == "OK") {
$this->uid = $resp['data']['uid'] * 1;
$this->username = $resp['data']['username'];
$this->email = $resp['data']['email'];
$this->realname = $resp['data']['name'];
} else {
sendError("Login server error: " . $resp['msg']);
}
}
}
public static function byUsername(string $username): User {
$resp = AccountHubApi::get("userinfo", ["username" => $username]);
if (!isset($resp['status'])) {
sendError("Login server error: " . $resp);
}
if ($resp['status'] == "OK") {
return new self($resp['data']['uid'] * 1);
} else {
return new self(-1, $username);
}
}
public function exists(): bool {
return $this->exists;
}
public function has2fa(): bool {
if (!$this->exists) {
return false;
}
$resp = AccountHubApi::get("hastotp", ['username' => $this->username]);
if ($resp['status'] == "OK") {
return $resp['otp'] == true;
} else {
return false;
}
}
function getUsername() {
return $this->username;
}
function getUID() {
return $this->uid;
}
function getEmail() {
return $this->email;
}
function getName() {
return $this->realname;
}
/**
* Check the given plaintext password against the stored hash.
* @param string $password
* @param bool $apppass Set to true to enforce app passwords when 2fa is on.
* @return bool
*/
function checkPassword(string $password, bool $apppass = false): bool {
$resp = AccountHubApi::get("auth", ['username' => $this->username, 'password' => $password, 'apppass' => ($apppass ? "1" : "0")]);
if ($resp['status'] == "OK") {
return true;
} else {
return false;
}
}
function check2fa(string $code): bool {
if (!$this->has2fa) {
return true;
}
$resp = AccountHubApi::get("verifytotp", ['username' => $this->username, 'code' => $code]);
if ($resp['status'] == "OK") {
return $resp['valid'];
} else {
return false;
}
}
/**
* Check if the given username has the given permission (or admin access)
* @global $database $database
* @param string $code
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
*/
function hasPermission(string $code): bool {
$resp = AccountHubApi::get("permission", ['username' => $this->username, 'code' => $code]);
if ($resp['status'] == "OK") {
return $resp['has_permission'];
} else {
return false;
}
}
/**
* Get the account status.
* @return \AccountStatus
*/
function getStatus(): AccountStatus {
$resp = AccountHubApi::get("acctstatus", ['username' => $this->username]);
if ($resp['status'] == "OK") {
return AccountStatus::fromString($resp['account']);
} else {
return null;
}
}
function sendAlertEmail(string $appname = null) {
global $SETTINGS;
if (is_null($appname)) {
$appname = $SETTINGS['site_title'];
}
$resp = AccountHubApi::get("alertemail", ['username' => $this->username, 'appname' => $SETTINGS['site_title']]);
if ($resp['status'] == "OK") {
return true;
} else {
return $resp['msg'];
}
}
}
class AccountStatus {
const NORMAL = 1;
const LOCKED_OR_DISABLED = 2;
const CHANGE_PASSWORD = 3;
const TERMINATED = 4;
const ALERT_ON_ACCESS = 5;
private $status;
public function __construct(int $status) {
$this->status = $status;
}
public static function fromString(string $status): AccountStatus {
switch ($status) {
case "NORMAL":
return new self(self::NORMAL);
case "LOCKED_OR_DISABLED":
return new self(self::LOCKED_OR_DISABLED);
case "CHANGE_PASSWORD":
return new self(self::CHANGE_PASSWORD);
case "TERMINATED":
return new self(self::TERMINATED);
case "ALERT_ON_ACCESS":
return new self(self::ALERT_ON_ACCESS);
default:
return new self(0);
}
}
/**
* Get the account status/state as an integer.
* @return int
*/
public function get(): int {
return $this->status;
}
/**
* Get the account status/state as a string representation.
* @return string
*/
public function getString(): string {
switch ($this->status) {
case self::NORMAL:
return "NORMAL";
case self::LOCKED_OR_DISABLED:
return "LOCKED_OR_DISABLED";
case self::CHANGE_PASSWORD:
return "CHANGE_PASSWORD";
case self::TERMINATED:
return "TERMINATED";
case self::ALERT_ON_ACCESS:
return "ALERT_ON_ACCESS";
default:
return "OTHER_" . $this->status;
}
}
}

File diff suppressed because it is too large Load Diff

4552
lib/crawler-user-agents.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,10 @@ dieifnotloggedin();
include_once __DIR__ . "/../lib/mimetypes.php";
$base = FILE_UPLOAD_PATH;
$base = $SETTINGS["file_upload_path"];
$folder = "";
if (isset($VARS['path']) && file_exists($base . $VARS['path']) && strpos(realpath($base . $VARS['path']), FILE_UPLOAD_PATH) === 0) {
if (isset($VARS['path']) && file_exists($base . $VARS['path']) && strpos(realpath($base . $VARS['path']), $SETTINGS["file_upload_path"]) === 0) {
$folder = $VARS['path'];
}
@ -23,7 +23,7 @@ if (isset($VARS['type']) && $VARS['type'] != "") {
$type = explode("|", $VARS['type']);
}
$enableunsplash = ENABLE_UNSPLASH;
$enableunsplash = $SETTINGS["unsplash"]["enable"];
if (count($type) > 0 && !in_array("image", $type)) {
$enableunsplash = false;
}
@ -35,12 +35,12 @@ if ($enableunsplash) {
<ul class="nav nav-tabs" id="fileBrowserTabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="uploadedFilesTabBtn" data-toggle="tab" href="#uploadedFilesTab">
<i class="fas fa-folder-open"></i> <?php lang('uploaded files'); ?>
<i class="fas fa-folder-open"></i> <?php $Strings->get('uploaded files'); ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="unsplashTabBtn" data-toggle="tab" href="#unsplashTab">
<i class="fas fa-image"></i> <?php lang('stock photos'); ?>
<i class="fas fa-image"></i> <?php $Strings->get('stock photos'); ?>
</a>
</li>
</ul>
@ -60,20 +60,20 @@ if ($enableunsplash) {
<div class="card">
<div class="card-body">
<div class="input-group">
<input type="text" class="form-control" id="unsplashSearch" placeholder="<?php lang("search images"); ?>" />
<input type="text" class="form-control" id="unsplashSearch" placeholder="<?php $Strings->get("search images"); ?>" />
<div class="input-group-append">
<div class="btn btn-primary" id="unsplashSearchBtn">
<i class="fas fa-search"></i> <?php lang("search"); ?>
<i class="fas fa-search"></i> <?php $Strings->get("search"); ?>
</div>
</div>
</div>
<span id="unsplashResults"></span> <span>via <a href="https://unsplash.com/?utm_source=<?php echo urlencode(UNSPLASH_UTMSOURCE); ?>&utm_medium=referral">Unsplash</a></span>
<span id="unsplashResults"></span> <span>via <a href="https://unsplash.com/?utm_source=<?php echo urlencode($SETTINGS["unsplash"]["utmsource"]); ?>&utm_medium=referral">Unsplash</a></span>
</div>
<div id="unsplashPhotoBin" class="px-2 pr-3">
</div>
<div class="card-body">
<button type="button" class="btn btn-primary btn-block" id="unsplashLoadMoreBtn">
<?php lang("load more"); ?>
<?php $Strings->get("load more"); ?>
</button>
</div>
</div>

View File

@ -9,10 +9,10 @@ dieifnotloggedin();
include_once __DIR__ . "/../lib/mimetypes.php";
$base = FILE_UPLOAD_PATH;
$base = $SETTINGS["file_upload_path"];
$folder = "";
if (isset($VARS['path']) && file_exists($base . $VARS['path']) && strpos(realpath($base . $VARS['path']), FILE_UPLOAD_PATH) === 0) {
if (isset($VARS['path']) && file_exists($base . $VARS['path']) && strpos(realpath($base . $VARS['path']), $SETTINGS["file_upload_path"]) === 0) {
$folder = $VARS['path'];
}
@ -23,7 +23,7 @@ if (isset($VARS['type']) && $VARS['type'] != "") {
$type = explode("|", $VARS['type']);
}
$enableunsplash = ENABLE_UNSPLASH;
$enableunsplash = $SETTINGS["unsplash"]["enable"];
if (count($type) > 0 && !in_array("image", $type)) {
$enableunsplash = false;
}
@ -129,7 +129,7 @@ $fullpath = $base . $folder;
<i class="far fa-folder-open fa-5x fa-fw"></i>
</p>
<p class="h5 text-muted">
<?php lang("nothing here"); ?>
<?php $Strings->get("nothing here"); ?>
</p>
</div>
<?php

View File

@ -13,9 +13,9 @@ dieifnotloggedin();
header('Content-Type: application/json');
Crew\Unsplash\HttpClient::init([
'applicationId' => UNSPLASH_ACCESSKEY,
'secret' => UNSPLASH_SECRETKEY,
'utmSource' => UNSPLASH_UTMSOURCE
'applicationId' => $SETTINGS["unsplash"]["accesskey"],
'secret' => $SETTINGS["unsplash"]["secretkey"],
'utmSource' => $SETTINGS["unsplash"]["utmsource"]
]);
$page = 1;
@ -34,12 +34,11 @@ if (isset($_GET['query']) && $_GET['query'] != "") {
$images = Crew\Unsplash\Photo::all($page, $per_page, 'popular');
}
$images->
$htmlout = "";
if (count($images) == 0) {
$htmlout = "<span>" . lang("no results", false) . "</span>";
$htmlout = "<div class=\"card text-center\"><div class=\"card-body\"><i class=\"fas fa-search-minus\"></i> " . $Strings->get("no results", false) . "</div></div>";
}
$htmlout .= '<div class="card-columns">';
@ -70,7 +69,7 @@ $jsonout = [
];
if (!is_null($results)) {
$jsonout['total'] = lang2("x results", ["results" => $results->getTotal()], false);
$jsonout['total'] = $Strings->build("x results", ["results" => $results->getTotal()], false);
$jsonout['pages'] = $results->getTotalPages();
}

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ if (!$database->has("settings", ["AND" => ["siteid" => getsiteid(), "key" => "an
throw new Exception("Do-Not-Track header detected, skipping analytics");
}
$bots = json_decode(file_get_contents(__DIR__ . "/bots.json"), true);
$bots = json_decode(file_get_contents(__DIR__ . "/crawler-user-agents.json"), true);
foreach ($bots as $bot) {
if (preg_match('/' . $bot['pattern'] . '/', $_SERVER['HTTP_USER_AGENT'])) {
throw new Exception("Bot/crawler detected, skipping analytics");
@ -78,7 +78,7 @@ if (!$database->has("settings", ["AND" => ["siteid" => getsiteid(), "key" => "an
// Lookup IP address
//
$reader = new Reader(GEOIP_DB);
$reader = new Reader($SETTINGS["geoip_db"]);
$record = $reader->city($clientip);
@ -108,12 +108,12 @@ if (!$database->has("settings", ["AND" => ["siteid" => getsiteid(), "key" => "an
"time" => $time
]);
} catch (GeoIp2\Exception\AddressNotFoundException $e) {
if (DEBUG) {
if ($SETTINGS["debug"]) {
echo "<!-- The client IP was not found in the GeoIP database. -->";
}
} catch (Exception $e) {
// Silently fail so the rest of the site still works
if (DEBUG) {
if ($SETTINGS["debug"]) {
echo "<!-- Analytics error: " . $e->getMessage() . " -->";
}
}

View File

@ -1,131 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Check if a given ipv4 address is in a given cidr
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return boolean true if the ip is in this range / false if not.
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
*/
function ip4_in_cidr($ip, $cidr) {
if (strpos($cidr, '/') == false) {
$cidr .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
list( $cidr, $netmask ) = explode('/', $cidr, 2);
$range_decimal = ip2long($cidr);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
}
/**
* Check if a given ipv6 address is in a given cidr
* @param string $ip IP to check in IPV6 format
* @param string $cidr CIDR netmask
* @return boolean true if the IP is in this range, false otherwise.
* @author MW. <https://stackoverflow.com/a/7952169>
*/
function ip6_in_cidr($ip, $cidr) {
$address = inet_pton($ip);
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
$subnetMask = explode("/", $cidr)[1];
$addr = str_repeat("f", $subnetMask / 4);
switch ($subnetMask % 4) {
case 0:
break;
case 1:
$addr .= "8";
break;
case 2:
$addr .= "c";
break;
case 3:
$addr .= "e";
break;
}
$addr = str_pad($addr, 32, '0');
$addr = pack("H*", $addr);
$binMask = $addr;
return ($address & $binMask) == $subnetAddress;
}
/**
* Check if the REMOTE_ADDR is on Cloudflare's network.
* @return boolean true if it is, otherwise false
*/
function validateCloudflare() {
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// Using IPv6
$cloudflare_ips_v6 = [
"2400:cb00::/32",
"2405:8100::/32",
"2405:b500::/32",
"2606:4700::/32",
"2803:f800::/32",
"2c0f:f248::/32",
"2a06:98c0::/29"
];
$valid = false;
foreach ($cloudflare_ips_v6 as $cidr) {
if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
} else {
// Using IPv4
$cloudflare_ips_v4 = [
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"104.16.0.0/12",
"108.162.192.0/18",
"131.0.72.0/22",
"141.101.64.0/18",
"162.158.0.0/15",
"172.64.0.0/13",
"173.245.48.0/20",
"188.114.96.0/20",
"190.93.240.0/20",
"197.234.240.0/22",
"198.41.128.0/17"
];
$valid = false;
foreach ($cloudflare_ips_v4 as $cidr) {
if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
}
return $valid;
}
/**
* Makes a good guess at the client's real IP address.
*
* @return string Client IP or `0.0.0.0` if we can't find anything
*/
function getClientIP() {
// If CloudFlare is in the mix, we should use it.
// Check if the request is actually from CloudFlare before trusting it.
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
if (validateCloudflare()) {
return $_SERVER["HTTP_CF_CONNECTING_IP"];
}
}
if (isset($_SERVER["REMOTE_ADDR"])) {
return $_SERVER["REMOTE_ADDR"];
}
return "0.0.0.0"; // This will not happen unless we aren't a web server
}

View File

@ -1,402 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Authentication and account functions. Connects to an AccountHub instance.
*/
/**
* Check the login server API for sanity
* @return boolean true if OK, else false
*/
function checkLoginServer() {
try {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "ping"
]
]);
if ($response->getStatusCode() != 200) {
return false;
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
return false;
}
} catch (Exception $e) {
return false;
}
}
/**
* Checks if the given AccountHub API key is valid by attempting to
* access the API with it.
* @param String $key The API key to check
* @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
*/
function checkAPIKey($key) {
try {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => $key,
'action' => "ping"
]
]);
if ($response->getStatusCode() === 200) {
return true;
}
return false;
} catch (Exception $e) {
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
// Account handling //
////////////////////////////////////////////////////////////////////////////////
/**
* Checks the given credentials against the API.
* @param string $username
* @param string $password
* @return boolean True if OK, else false
*/
function authenticate_user($username, $password, &$errmsg) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "auth",
'username' => $username,
'password' => $password
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
$errmsg = $resp['msg'];
return false;
}
}
/**
* Check if a username exists.
* @param String $username
*/
function user_exists($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userexists",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK" && $resp['exists'] === true) {
return true;
} else {
return false;
}
}
/**
* Check if a UID exists.
* @param String $uid
*/
function uid_exists($uid) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userexists",
'uid' => $uid
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK" && $resp['exists'] === true) {
return true;
} else {
return false;
}
}
/**
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
* CHANGE_PASSWORD, or ALERT_ON_ACCESS
* @param string $username
* @return string
*/
function get_account_status($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "acctstatus",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['account'];
} else {
return false;
}
}
/**
* Check if the given username has the given permission (or admin access)
* @param string $username
* @param string $permcode
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
*/
function account_has_permission($username, $permcode) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "permission",
'username' => $username,
'code' => $permcode
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['has_permission'];
} else {
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
// Login handling //
////////////////////////////////////////////////////////////////////////////////
/**
* Setup $_SESSION values with user data and set loggedin flag to true
* @param string $username
*/
function doLoginUser($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userinfo",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
$userinfo = $resp['data'];
session_regenerate_id(true);
$newSession = session_id();
session_write_close();
session_id($newSession);
session_start();
$_SESSION['username'] = $username;
$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['email'] = $userinfo['email'];
$_SESSION['realname'] = $userinfo['name'];
$_SESSION['loggedin'] = true;
return true;
} else {
return false;
}
}
function sendLoginAlertEmail($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "alertemail",
'username' => $username,
'appname' => SITE_TITLE
]
]);
if ($response->getStatusCode() > 299) {
return "An unknown error occurred.";
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
return $resp['msg'];
}
}
function simLogin($username, $password) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "login",
'username' => $username,
'password' => $password
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
return $resp['msg'];
}
}
function verifyCaptcheck($session, $answer, $url) {
$data = [
'session_id' => $session,
'answer_id' => $answer,
'action' => "verify"
];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
$resp = json_decode($result, TRUE);
if (!$resp['result']) {
return false;
} else {
return true;
}
}
////////////////////////////////////////////////////////////////////////////////
// 2-factor authentication //
////////////////////////////////////////////////////////////////////////////////
/**
* Check if a user has TOTP setup
* @param string $username
* @return boolean true if TOTP secret exists, else false
*/
function userHasTOTP($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "hastotp",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['otp'];
} else {
return false;
}
}
/**
* Verify a TOTP multiauth code
* @global $database
* @param string $username
* @param int $code
* @return boolean true if it's legit, else false
*/
function verifyTOTP($username, $code) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "verifytotp",
'username' => $username,
'code' => $code
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['valid'];
} else {
return false;
}
}

View File

@ -11,7 +11,7 @@ ob_start(); // allow sending headers after content
// Settings file
require __DIR__ . '/../settings.php';
if (!DEBUG) {
if (!$SETTINGS["debug"]) {
error_reporting(0);
} else {
error_reporting(E_ALL);
@ -57,7 +57,7 @@ function sendError($error) {
. "<p>" . htmlspecialchars($error) . "</p>");
}
date_default_timezone_set(TIMEZONE);
date_default_timezone_set($SETTINGS['timezone']);
// Database settings
// Also inits database and stuff
@ -66,12 +66,12 @@ use Medoo\Medoo;
$database;
try {
$database = new Medoo([
'database_type' => DB_TYPE,
'database_name' => DB_NAME,
'server' => DB_SERVER,
'username' => DB_USER,
'password' => DB_PASS,
'charset' => DB_CHARSET
'database_type' => $SETTINGS['database']['type'],
'database_name' => $SETTINGS['database']['name'],
'server' => $SETTINGS['database']['server'],
'username' => $SETTINGS['database']['user'],
'password' => $SETTINGS['database']['password'],
'charset' => $SETTINGS['database']['charset']
]);
} catch (Exception $ex) {
//header('HTTP/1.1 500 Internal Server Error');

View File

@ -87,10 +87,11 @@ function get_page_slug($echo = true) {
* @return string
*/
function get_page_clean_url($echo = true, $slug = null) {
global $SETTINGS;
if ($slug == null) {
$slug = get_page_slug(false);
}
if (PRETTY_URLS) {
if ($SETTINGS["pretty_urls"]) {
$url = formatsiteurl(get_site_url(false)) . "$slug";
} else {
$url = formatsiteurl(get_site_url(false)) . "index.php?id=$slug";
@ -108,6 +109,7 @@ function get_page_clean_url($echo = true, $slug = null) {
* @return string
*/
function get_page_url($echo = true, $slug = null) {
global $SETTINGS;
if ($slug == null) {
$slug = get_page_slug(false);
}
@ -132,7 +134,7 @@ function get_page_url($echo = true, $slug = null) {
$siteid = "&siteid=" . preg_replace("/[^0-9]/", '', $_GET['siteid']);
}
$args = "$edit$theme$template$color$siteid";
if (PRETTY_URLS) {
if ($SETTINGS["pretty_urls"]) {
if ($args != "") {
$args = "?$args";
}
@ -214,6 +216,7 @@ function is_component_empty($name, $context = null) {
* @return array
*/
function get_complex_component($name, $context = null, $include = []) {
global $SETTINGS;
$db = getdatabase();
if ($context == null) {
$context = getpageslug();
@ -234,14 +237,14 @@ function get_complex_component($name, $context = null, $include = []) {
$filtered = [];
foreach ($include as $i) {
if (array_key_exists($i, $content)) {
if (!isset($_GET['edit']) && $i == "image" && $content[$i] == URL . "/static/img/no-image.svg") {
if (!isset($_GET['edit']) && $i == "image" && $content[$i] == $SETTINGS["url"] . "/static/img/no-image.svg") {
$filtered[$i] = "";
} else {
$filtered[$i] = $content[$i];
}
} else {
if (isset($_GET['edit']) && $i == "image") {
$filtered[$i] = URL . "/static/img/no-image.svg";
$filtered[$i] = $SETTINGS["url"] . "/static/img/no-image.svg";
} else {
$filtered[$i] = "";
}
@ -258,12 +261,13 @@ function get_complex_component($name, $context = null, $include = []) {
* @return boolean
*/
function is_complex_empty($name, $context = null) {
global $SETTINGS;
if (isset($_GET['edit'])) {
return false;
}
$comp = get_complex_component($name, $context);
foreach ($comp as $c => $v) {
if ($c == "image" && $v == URL . "/static/img/no-image.svg") {
if ($c == "image" && $v == $SETTINGS["url"] . "/static/img/no-image.svg") {
continue;
}
if (isset($v) && !empty($v)) {
@ -314,13 +318,14 @@ function get_url_or_slug($str, $echo = true) {
* @return string
*/
function get_file_url($file, $echo = true) {
global $SETTINGS;
$url = "file.php?file=$file";
$base = FILE_UPLOAD_PATH;
$base = $SETTINGS["file_upload_path"];
$filepath = $base . $file;
if (!file_exists($filepath) || is_dir($filepath)) {
$url = $file;
} else {
if (strpos(realpath($filepath), FILE_UPLOAD_PATH) !== 0) {
if (strpos(realpath($filepath), $SETTINGS["file_upload_path"]) !== 0) {
$url = $file;
}
}
@ -382,10 +387,11 @@ function get_setting($key, $echo = false) {
* @return string
*/
function get_theme_url($echo = true) {
global $SETTINGS;
$db = getdatabase();
$site = $db->get('sites', ["sitename", "url", "theme"], ["siteid" => getsiteid()]);
if (isset($_GET['edit']) || isset($_GET['in_sw'])) {
$url = URL . "/public/themes/" . SITE_THEME;
$url = $SETTINGS["url"] . "/public/themes/" . SITE_THEME;
} else {
$url = formatsiteurl($site["url"]) . "themes/" . SITE_THEME;
}

View File

@ -1,127 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Get user info for the given username.
* @param int $u username
* @return [string] Array of [uid, username, name]
*/
function getUserByUsername($u) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userinfo",
'username' => $u
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['data'];
} else {
// this shouldn't happen, but in case it does just fake it.
return ["name" => $u, "username" => $u, "uid" => $u];
}
}
/**
* Get user info for the given UID.
* @param int $u user ID
* @return [string] Array of [uid, username, name]
*/
function getUserByID($u) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userinfo",
'uid' => $u
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['data'];
} else {
// this shouldn't happen, but in case it does just fake it.
return ["name" => $u, "username" => $u, "uid" => $u];
}
}
/**
* Check if the first UID is a manager of the second UID.
* @param int $m Manager UID
* @param int $e Employee UID
* @return boolean
*/
function isManagerOf($m, $e) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "ismanagerof",
'manager' => $m,
'employee' => $e,
'uid' => 1
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['managerof'] === true;
} else {
// this shouldn't happen, but in case it does just fake it.
return false;
}
}
/**
* Get an array of UIDs the given UID is a manager of.
* @param int $manageruid The UID of the manager to find employees for.
* @return [int]
*/
function getManagedUIDs($manageruid) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "getmanaged",
'uid' => $manageruid
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['employees'];
} else {
return [];
}
}

View File

@ -8,14 +8,8 @@
* Mobile app API
*/
// The name of the permission needed to log in.
// Set to null if you don't need it.
$access_permission = null;
require __DIR__ . "/../required.php";
require __DIR__ . "/../lib/login.php";
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
@ -25,21 +19,7 @@ if ($VARS['action'] == "ping") {
}
function mobile_enabled() {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "mobileenabled"
]
]);
if ($response->getStatusCode() > 299) {
return false;
}
$resp = json_decode($response->getBody(), TRUE);
$resp = AccountHubApi::get("mobileenabled");
if ($resp['status'] == "OK" && $resp['mobile'] === TRUE) {
return true;
} else {
@ -48,36 +28,25 @@ function mobile_enabled() {
}
function mobile_valid($username, $code) {
$client = new GuzzleHttp\Client();
try {
$resp = AccountHubApi::get("mobilevalid", ["code" => $code, "username" => $username], true);
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
"code" => $code,
"username" => $username,
'action' => "mobilevalid"
]
]);
if ($response->getStatusCode() > 299) {
return false;
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK" && $resp['valid'] === TRUE) {
return true;
} else {
return false;
}
} catch (Exception $ex) {
return false;
}
}
if (mobile_enabled() !== TRUE) {
exit(json_encode(["status" => "ERROR", "msg" => lang("mobile login disabled", false)]));
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
}
// Make sure we have a username and access key
if (is_empty($VARS['username']) || is_empty($VARS['key'])) {
if (empty($VARS['username']) || empty($VARS['key'])) {
http_response_code(401);
die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
}
@ -93,20 +62,22 @@ if (!mobile_valid($VARS['username'], $VARS['key'])) {
switch ($VARS['action']) {
case "start_session":
// Do a web login.
if (user_exists($VARS['username'])) {
if (get_account_status($VARS['username']) == "NORMAL") {
if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
if (is_null($access_permission) || account_has_permission($VARS['username'], $access_permission)) {
doLoginUser($VARS['username'], $VARS['password']);
$user = User::byUsername($VARS['username']);
if ($user->exists()) {
if ($user->getStatus()->getString() == "NORMAL") {
if ($user->checkPassword($VARS['password'])) {
foreach ($SETTINGS['permissions'] as $perm) {
if (!$user->hasPermission($perm)) {
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("no permission", false)]));
}
}
Session::start($user);
$_SESSION['mobile'] = true;
exit(json_encode(["status" => "OK"]));
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("no admin permission", false)]));
}
}
}
}
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
default:
http_response_code(404);
die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));

29
nbproject/fa5tophp.php Normal file
View File

@ -0,0 +1,29 @@
<?php
// Script to convert icons.json in the FontAwesome download into a PHP array for SiteWriter
$json = file_get_contents(__DIR__ . "/icons.json");
$icons = json_decode($json, true);
$output = [];
foreach ($icons as $icon => $data) {
$meta = [];
$meta["label"] = $data["label"];
$meta["search"] = $data["search"]["terms"];
foreach ($data["styles"] as $s) {
$class = "fa";
switch ($s) {
case "solid":
$class = "fas";
break;
case "regular":
$class = "far";
break;
case "brands":
$class = "fab";
break;
}
$output["$class fa-$icon"] = $meta;
}
}
var_export($output);

51733
nbproject/icons.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,6 @@
?>
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
<div class="alert alert-warning"><b><?php lang("404 error");?></b><br /> <?php lang("page not found"); ?></div>
<div class="alert alert-warning"><b><?php $Strings->get("404 error");?></b><br /> <?php $Strings->get("page not found"); ?></div>
</div>
</div>

View File

@ -7,8 +7,9 @@ require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
require_once __DIR__ . "/../lib/login.php";
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_ANALYTICS")) {
$user = new User($_SESSION['uid']);
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_ANALYTICS")) {
if ($_GET['msg'] != "no_permission") {
header("Location: app.php?page=analytics&msg=no_permission");
}
@ -17,18 +18,18 @@ if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has
$select_filter = [];
if (isset($VARS['siteid']) && !is_empty($VARS['siteid'])) {
if (!empty($VARS['siteid'])) {
if ($database->has('sites', ['siteid' => $VARS['siteid']])) {
$select_filter["analytics.siteid"] = $VARS['siteid'];
}
}
if (isset($VARS['after']) && !is_empty($VARS['after'])) {
if (!empty($VARS['after'])) {
if (strtotime($VARS['after']) !== FALSE) {
$select_filter["time[>]"] = date("Y-m-d H:i:s", strtotime($VARS['after']));
}
}
if (isset($VARS['before']) && !is_empty($VARS['before'])) {
if (!empty($VARS['before'])) {
if (strtotime($VARS['before']) !== FALSE) {
$select_filter["time[<]"] = date("Y-m-d H:i:s", strtotime($VARS['before']));
}
@ -140,13 +141,13 @@ foreach ($states as $id => $count) {
<div class="card p-2">
<form class="form-inline" action="app.php" method="GET">
<button type="submit" class="btn btn-primary"><i class="fas fa-sync"></i></button>
<label for="siteid_select" class="sr-only"><?php lang("filter by site") ?></label>
<label for="siteid_select" class="sr-only"><?php $Strings->get("filter by site") ?></label>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-sitemap"></i></span>
</div>
<select name="siteid" class="form-control pr-4" id="siteid_select">
<option value=""><?php lang("all sites"); ?></option>
<option value=""><?php $Strings->get("all sites"); ?></option>
<?php
$sites = $database->select("sites", ["siteid", "sitename"]);
foreach ($sites as $s) {
@ -164,22 +165,22 @@ foreach ($states as $id => $count) {
<span class="vertline d-none d-lg-inline"></span>
<label for="date_after" class="sr-only"><?php lang("filter after date") ?></label>
<label for="date_before" class="sr-only"><?php lang("filter before date") ?></label>
<label for="date_after" class="sr-only"><?php $Strings->get("filter after date") ?></label>
<label for="date_before" class="sr-only"><?php $Strings->get("filter before date") ?></label>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-calendar"></i></span>
</div>
<input type="text" id="date_after" name="after" value="<?php echo htmlspecialchars($VARS['after']); ?>" class="form-control" placeholder="<?php lang("start date"); ?>" data-toggle="datetimepicker" data-target="#date_after" />
<input type="text" id="date_after" name="after" value="<?php echo htmlspecialchars($VARS['after']); ?>" class="form-control" placeholder="<?php $Strings->get("start date"); ?>" data-toggle="datetimepicker" data-target="#date_after" />
<div class="input-group-prepend input-group-append">
<span class="input-group-text"><i class="fas fa-caret-right"></i></span>
</div>
<input type="text" id="date_before" name="before" value="<?php echo htmlspecialchars($VARS['before']); ?>" class="form-control" placeholder="<?php lang("end date"); ?>" data-toggle="datetimepicker" data-target="#date_before" />
<input type="text" id="date_before" name="before" value="<?php echo htmlspecialchars($VARS['before']); ?>" class="form-control" placeholder="<?php $Strings->get("end date"); ?>" data-toggle="datetimepicker" data-target="#date_before" />
</div>
<input type="hidden" name="page" value="analytics" />
<button type="submit" class="btn btn-secondary"><i class="fas fa-filter"></i> <?php lang("filter"); ?></button>
<button type="submit" class="btn btn-secondary"><i class="fas fa-filter"></i> <?php $Strings->get("filter"); ?></button>
</form>
</div>
@ -191,14 +192,14 @@ if (count($records) > 0) {
<!-- Overview -->
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title"><?php lang("overview"); ?></h4>
<h4 class="card-title"><?php $Strings->get("overview"); ?></h4>
<?php
$ratio = round($pageviews / $visits, 1);
?>
<h5>
<i class="fas fa-users fa-fw"></i> <?php echo $visits; ?> <?php lang("visits") ?> <br />
<i class="fas fa-eye fa-fw"></i> <?php echo $pageviews; ?> <?php lang("page views") ?> <br />
<i class="fas fa-percent fa-fw"></i> <?php echo $ratio; ?> <?php lang("views per visit") ?>
<i class="fas fa-users fa-fw"></i> <?php echo $visits; ?> <?php $Strings->get("visits") ?> <br />
<i class="fas fa-eye fa-fw"></i> <?php echo $pageviews; ?> <?php $Strings->get("page views") ?> <br />
<i class="fas fa-percent fa-fw"></i> <?php echo $ratio; ?> <?php $Strings->get("views per visit") ?>
</h5>
</div>
</div>
@ -206,7 +207,7 @@ if (count($records) > 0) {
<!-- Visits Over Time -->
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title"><?php lang("visits over time"); ?></h4>
<h4 class="card-title"><?php $Strings->get("visits over time"); ?></h4>
<script nonce="<?php echo $SECURE_NONCE; ?>">
var visitsOverTimeData = [
<?php foreach ($visitsovertime as $d => $c) { ?>
@ -226,7 +227,7 @@ if (count($records) > 0) {
<!-- Views Over Time -->
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title"><?php lang("page views over time"); ?></h4>
<h4 class="card-title"><?php $Strings->get("page views over time"); ?></h4>
<script nonce="<?php echo $SECURE_NONCE; ?>">
var viewsOverTimeData = [
<?php foreach ($viewsovertime as $d => $c) { ?>
@ -246,7 +247,7 @@ if (count($records) > 0) {
<!-- Visitor Map -->
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title"><?php lang("visitor map"); ?></h4>
<h4 class="card-title"><?php $Strings->get("visitor map"); ?></h4>
<script nonce="<?php echo $SECURE_NONCE; ?>">
visitorMap_Countries = <?php echo json_encode($countrymapdata); ?>;
visitorMap_States = <?php echo json_encode($statemapdata); ?>;
@ -259,7 +260,7 @@ if (count($records) > 0) {
<!-- Page Ranking -->
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title"><?php lang("page ranking"); ?></h4>
<h4 class="card-title"><?php $Strings->get("page ranking"); ?></h4>
</div>
<div class="list-group">
<?php
@ -272,7 +273,7 @@ if (count($records) > 0) {
<span><i class="fas fa-sitemap fa-fw"></i> <?php echo $p["sitename"]; ?></span>
</div>
<div class="col-6">
<span><i class="fas fa-eye fa-fw"></i> <?php lang2("x views", ["views" => $p['views']]); ?></span>
<span><i class="fas fa-eye fa-fw"></i> <?php $Strings->build("x views", ["views" => $p['views']]); ?></span>
</div>
</div>
</div>
@ -285,7 +286,7 @@ if (count($records) > 0) {
<!-- Recent Actions -->
<div class="card mb-4">
<div class="card-body">
<h4 class="card-title"><?php lang("recent actions"); ?></h4>
<h4 class="card-title"><?php $Strings->get("recent actions"); ?></h4>
</div>
<div class="list-group list-group-scrolly">
<?php
@ -329,7 +330,7 @@ if (count($records) > 0) {
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
<div class="card">
<div class="card-body text-center">
<i class="fas fa-info-circle"></i> <?php lang("no data"); ?>
<i class="fas fa-info-circle"></i> <?php $Strings->get("no data"); ?>
</div>
</div>
</div>

View File

@ -7,15 +7,16 @@ require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
require_once __DIR__ . "/../lib/login.php";
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_EDIT")) {
$user = new User($_SESSION['uid']);
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_EDIT")) {
if ($_GET['msg'] != "no_permission") {
header("Location: app.php?page=editor&msg=no_permission");
}
die();
}
if (isset($VARS['arg']) && !is_empty($VARS['arg'])) {
if (!empty($VARS['arg'])) {
// Allow action.php to do a better redirect
$VARS['siteid'] = $VARS['arg'];
if (strpos($VARS['arg'], "|") !== FALSE) {
@ -27,7 +28,7 @@ if (isset($VARS['arg']) && !is_empty($VARS['arg'])) {
}
}
if (!is_empty($VARS['siteid'])) {
if (!empty($VARS['siteid'])) {
if ($database->has('sites', ['siteid' => $VARS['siteid']])) {
$sitedata = $database->get(
'sites', [
@ -86,16 +87,16 @@ if (!is_empty($VARS['siteid'])) {
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="fileBrowseLabel"><i class="far fa-folder-open"></i> <?php lang("browse"); ?></h5>
<h5 class="modal-title" id="fileBrowseLabel"><i class="far fa-folder-open"></i> <?php $Strings->get("browse"); ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="fileBrowseModalBody">
<i class="fas fa-spin fa-circle-notch"></i> <?php lang("loading"); ?>
<i class="fas fa-spin fa-circle-notch"></i> <?php $Strings->get("loading"); ?>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php lang("cancel"); ?></button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php $Strings->get("cancel"); ?></button>
</div>
</div>
</div>
@ -105,18 +106,18 @@ if (!is_empty($VARS['siteid'])) {
<div class="modal-dialog" role="document">
<form class="modal-content" action="action.php" method="POST">
<div class="modal-header">
<h5 class="modal-title" id="pageSettingsLabel"><i class="fas fa-cog"></i> <?php lang("page settings"); ?></h5>
<h5 class="modal-title" id="pageSettingsLabel"><i class="fas fa-cog"></i> <?php $Strings->get("page settings"); ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="pageSettingsModalBody">
<div class="form-group">
<label for="pageSettingsTitle"><i class="fas fa-font"></i> <?php lang("title"); ?></label>
<label for="pageSettingsTitle"><i class="fas fa-font"></i> <?php $Strings->get("title"); ?></label>
<input type="text" id="pageSettingsTitle" name="title" class="form-control" required="required" minlength="1" maxlength="200" value="<?php echo $thispage['title']; ?>" />
</div>
<div class="form-group">
<label><i class="fas fa-paint-brush"></i> <?php lang("template"); ?></label>
<label><i class="fas fa-paint-brush"></i> <?php $Strings->get("template"); ?></label>
<select id="pageSettingsTemplate" name="template" class="form-control" required="required">
<?php
$json = file_get_contents(__DIR__ . "/../public/themes/" . $sitedata['theme'] . "/theme.json");
@ -135,11 +136,11 @@ if (!is_empty($VARS['siteid'])) {
if ($singlepage !== true) {
?>
<div class="form-group">
<label><i class="fas fa-bars"></i> <?php lang("navbar options"); ?></label>
<label><i class="fas fa-bars"></i> <?php $Strings->get("navbar options"); ?></label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="innavbar" id="innavbarCheckbox" value="1" <?php echo (is_empty($thispage['nav']) ? "" : "checked") ?> />
<input class="form-check-input" type="checkbox" name="innavbar" id="innavbarCheckbox" value="1" <?php echo (empty($thispage['nav']) ? "" : "checked") ?> />
<label class="form-check-label" for="innavbarCheckbox">
<?php lang("in navbar"); ?>
<?php $Strings->get("in navbar"); ?>
</label>
</div>
<div id="navbarSettings" class="card p-2 mt-3 d-none">
@ -149,16 +150,16 @@ if (!is_empty($VARS['siteid'])) {
</script>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="navbarTitle"><?php lang("navbar title"); ?></label>
<label class="input-group-text" for="navbarTitle"><?php $Strings->get("navbar title"); ?></label>
</div>
<input type="text" id="navbarTitle" name="navbartitle" class="form-control" required="required" minlength="1" maxlength="200" value="<?php echo (is_empty($thispage['nav']) ? $thispage['title'] : $thispage['nav']); ?>" />
<input type="text" id="navbarTitle" name="navbartitle" class="form-control" required="required" minlength="1" maxlength="200" value="<?php echo (empty($thispage['nav']) ? $thispage['title'] : $thispage['nav']); ?>" />
</div>
<label class="mb-0 pb-0"><?php lang("navbar position"); ?></label>
<label class="mb-0 pb-0"><?php $Strings->get("navbar position"); ?></label>
<div class="list-group py-3" id="navbar-order-list">
<?php
foreach ($pagedata as $page) {
if (is_empty($page['nav'])) {
if (empty($page['nav'])) {
continue;
}
?>
@ -183,8 +184,8 @@ if (!is_empty($VARS['siteid'])) {
<input type="hidden" name="pageid" value="<?php echo $thispage['pageid']; ?>" />
<input type="hidden" name="action" value="pagesettings" />
<input type="hidden" name="source" value="editor" />
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php lang("cancel"); ?></button>
<button type="submit" class="btn btn-success" id="pageSettingsModalSave"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php $Strings->get("cancel"); ?></button>
<button type="submit" class="btn btn-success" id="pageSettingsModalSave"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
</div>
</form>
</div>
@ -194,22 +195,22 @@ if (!is_empty($VARS['siteid'])) {
<div class="modal-dialog" role="document">
<form class="modal-content" action="action.php" method="POST">
<div class="modal-header">
<h5 class="modal-title" id="newPageLabel"><i class="fas fa-plus"></i> <?php lang("new page"); ?></h5>
<h5 class="modal-title" id="newPageLabel"><i class="fas fa-plus"></i> <?php $Strings->get("new page"); ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="newPageModalBody">
<div class="form-group">
<label><i class="fas fa-font"></i> <?php lang("title"); ?></label>
<label><i class="fas fa-font"></i> <?php $Strings->get("title"); ?></label>
<input type="text" id="newPageTitle" name="title" class="form-control" required="required" minlength="1" maxlength="200" />
</div>
<!--<div class="form-group">
<label><i class="fas fa-link"></i> <?php lang("page id"); ?></label>
<label><i class="fas fa-link"></i> <?php $Strings->get("page id"); ?></label>
<input type="text" id="newPageSlug" name="slug" class="form-control" placeholder="" minlength="1" maxlength="200" />
</div>-->
<div class="form-group">
<label><i class="fas fa-paint-brush"></i> <?php lang("template"); ?></label>
<label><i class="fas fa-paint-brush"></i> <?php $Strings->get("template"); ?></label>
<select id="newPageTemplate" name="template" class="form-control" required="required">
<?php
$json = file_get_contents(__DIR__ . "/../public/themes/" . $sitedata['theme'] . "/theme.json");
@ -225,8 +226,8 @@ if (!is_empty($VARS['siteid'])) {
<input type="hidden" name="siteid" value="<?php echo $sitedata['siteid']; ?>" />
<input type="hidden" name="action" value="newpage" />
<input type="hidden" name="source" value="editor" />
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php lang("cancel"); ?></button>
<button type="submit" class="btn btn-success" id="newPageModalSave"><i class="fas fa-plus"></i> <?php lang("add page"); ?></button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php $Strings->get("cancel"); ?></button>
<button type="submit" class="btn btn-success" id="newPageModalSave"><i class="fas fa-plus"></i> <?php $Strings->get("add page"); ?></button>
</div>
</form>
</div>
@ -236,40 +237,40 @@ if (!is_empty($VARS['siteid'])) {
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editLabel"><?php lang("edit component"); ?></h5>
<h5 class="modal-title" id="editLabel"><?php $Strings->get("edit component"); ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="editModalBody">
<div class="form-group d-none" id="iconEdit">
<label><i class="fas fa-paint-brush"></i> <?php lang("icon"); ?></label>
<label><i class="fas fa-paint-brush"></i> <?php $Strings->get("icon"); ?></label>
<br /> <div class="card d-inline-block mb-2">
<div class="card-body p-1">
<?php lang("current"); ?>: <span id="selectedicon"><i class="fa-fw"></i></span>
<?php $Strings->get("current"); ?>: <span id="selectedicon"><i class="fa-fw"></i></span>
</div>
</div>
<div id="iconpicker"><i class="fas fa-spin fa-circle-notch ml-2"></i> <?php lang("loading"); ?></div>
<div id="iconpicker"><i class="fas fa-spin fa-circle-notch ml-2"></i> <?php $Strings->get("loading"); ?></div>
</div>
<div class="form-group d-none" id="imageEdit" data-image="">
<label><i class="fas fa-image"></i> <?php lang("image"); ?></label>
<label><i class="fas fa-image"></i> <?php $Strings->get("image"); ?></label>
<br /> <div class="card d-inline-block mb-2">
<div class="card-body p-1">
<img id="selectedimage" class="img-responsive" />
<br />
<span class="btn btn-sm btn-outline-danger mt-1" id="removeimagebtn">
<span class="fas fa-times fa-fw"></span> <?php lang("remove image"); ?>
<span class="fas fa-times fa-fw"></span> <?php $Strings->get("remove image"); ?>
</span>
</div>
</div>
<div id="imagepicker">
<i class="fas fa-spin fa-circle-notch ml-2"></i> <?php lang("loading"); ?>
<i class="fas fa-spin fa-circle-notch ml-2"></i> <?php $Strings->get("loading"); ?>
</div>
</div>
<div class="form-group" id="linkEdit">
<label><i class="fas fa-link"></i> <?php lang("link"); ?></label>
<label><i class="fas fa-link"></i> <?php $Strings->get("link"); ?></label>
<select id="linkPage" class="form-control">
<option value=""><?php lang("select page or enter url"); ?></option>
<option value=""><?php $Strings->get("select page or enter url"); ?></option>
<?php
foreach ($pagedata as $p) {
echo "<option value=\"" . $p['slug'] . "\">" . $p['title'] . ' (' . $p['slug'] . ')' . "</option>\n";
@ -279,13 +280,13 @@ if (!is_empty($VARS['siteid'])) {
<input type="text" id="linkBox" class="form-control" placeholder="http://example.com" />
</div>
<div class="form-group" id="textEdit">
<label><i class="fas fa-font"></i> <?php lang("text"); ?></label>
<label><i class="fas fa-font"></i> <?php $Strings->get("text"); ?></label>
<input type="text" id="textBox" class="form-control" placeholder="Edit me" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php lang("cancel"); ?></button>
<button type="button" class="btn btn-success" id="editModalSave"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php $Strings->get("cancel"); ?></button>
<button type="button" class="btn btn-success" id="editModalSave"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
</div>
</div>
</div>
@ -295,24 +296,24 @@ if (!is_empty($VARS['siteid'])) {
<div class="col-12 col-sm-6">
<div class="btn-group">
<div class="btn btn-success" id="savebtn">
<i class="fas fa-save"></i> <?php lang("save"); ?>
<i class="fas fa-save"></i> <?php $Strings->get("save"); ?>
</div>
<div class="btn btn-secondary" id="pagesettingsbtn">
<i class="fas fa-cog"></i> <?php lang("settings"); ?>
<i class="fas fa-cog"></i> <?php $Strings->get("settings"); ?>
</div>
<a class="btn btn-info" id="viewbtn" target="_BLANK" href="public/index.php?id=<?php echo $slug; ?>&siteid=<?php echo $VARS['siteid']; ?>">
<i class="fas fa-eye"></i> <?php lang("view"); ?>
<i class="fas fa-eye"></i> <?php $Strings->get("view"); ?>
</a>
<?php if (!$singlepage) { ?>
<div class="btn btn-primary" id="newpagebtn">
<i class="fas fa-plus"></i> <?php lang("new"); ?>
<i class="fas fa-plus"></i> <?php $Strings->get("new"); ?>
</div>
<?php } ?>
</div>
<span class="badge badge-success d-none" id="savedBadge"><i class="fas fa-check"></i> <?php lang("saved"); ?></span>
<span class="badge badge-success d-none" id="savedBadge"><i class="fas fa-check"></i> <?php $Strings->get("saved"); ?></span>
<div id="reloadprompt" class="badge badge-info d-none">
<i class="fas fa-sync-alt"></i>
<?php lang("save needed"); ?>
<?php $Strings->get("save needed"); ?>
</div>
</div>
<?php if (!$singlepage) { ?>
@ -332,7 +333,7 @@ if (!is_empty($VARS['siteid'])) {
?>
</select>
<div class="input-group-append">
<button type="submit" class="btn btn-primary"><i class="fas fa-edit"></i> <?php lang("edit"); ?></button>
<button type="submit" class="btn btn-primary"><i class="fas fa-edit"></i> <?php $Strings->get("edit"); ?></button>
</div>
</div>
</form>

View File

@ -7,8 +7,9 @@ require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
require_once __DIR__ . "/../lib/login.php";
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_FILES") && !account_has_permission($_SESSION['username'], "SITEWRITER_EDIT")) {
$user = new User($_SESSION['uid']);
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_FILES") && !$user->hasPermission("SITEWRITER_EDIT")) {
// Note: the EDIT permission is valid here because content editors can browse files anyways
if ($_GET['msg'] != "no_permission") {
header("Location: app.php?page=files&msg=no_permission");
@ -18,10 +19,10 @@ if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has
include_once __DIR__ . "/../lib/mimetypes.php";
$base = FILE_UPLOAD_PATH;
$base = $SETTINGS["file_upload_path"];
$folder = "";
if (isset($VARS['path']) && file_exists($base . $VARS['path']) && strpos(realpath($base . $VARS['path']), FILE_UPLOAD_PATH) === 0) {
if (isset($VARS['path']) && file_exists($base . $VARS['path']) && strpos(realpath($base . $VARS['path']), $SETTINGS["file_upload_path"]) === 0) {
$folder = $VARS['path'];
}
@ -63,9 +64,9 @@ $fullpath = $base . $folder;
<input type="text" id="uploadstatus" class="form-control" readonly />
<div class="input-group-append">
<span class="btn btn-primary btn-file">
<i class="fas fa-folder-open"></i> <?php lang("browse"); ?> <input id="fileupload" type="file" name="files[]" multiple required />
<i class="fas fa-folder-open"></i> <?php $Strings->get("browse"); ?> <input id="fileupload" type="file" name="files[]" multiple required />
</span>
<button class="btn btn-success" type="submit"><i class="fas fa-cloud-upload-alt"></i> <?php lang("upload"); ?></button>
<button class="btn btn-success" type="submit"><i class="fas fa-cloud-upload-alt"></i> <?php $Strings->get("upload"); ?></button>
</div>
</div>
<input type="hidden" name="action" value="fileupload" />
@ -76,7 +77,7 @@ $fullpath = $base . $folder;
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="folder" required />
<div class="input-group-append">
<button class="btn btn-success" type="submit"><i class="fas fa-folder"></i> <?php lang("new folder"); ?></button>
<button class="btn btn-success" type="submit"><i class="fas fa-folder"></i> <?php $Strings->get("new folder"); ?></button>
</div>
</div>
<input type="hidden" name="action" value="newfolder" />
@ -133,7 +134,7 @@ $fullpath = $base . $folder;
<input type="hidden" name="action" value="filedelete" />
<input type="hidden" name="source" value="files" />
<input type="hidden" name="file" value="<?php echo "$folder/$f"; ?>" />
<button type="submit" class="btn btn-outline-danger btn-sm"><i class="fas fa-trash"></i> <?php lang("delete"); ?></button>
<button type="submit" class="btn btn-outline-danger btn-sm"><i class="fas fa-trash"></i> <?php $Strings->get("delete"); ?></button>
</form>
</div>
<?php
@ -146,7 +147,7 @@ $fullpath = $base . $folder;
<i class="far fa-folder-open fa-10x fa-fw"></i>
</p>
<p class="h4 text-muted">
<?php lang("nothing here"); ?>
<?php $Strings->get("nothing here"); ?>
</p>
</div>
<?php

26
pages/form.php Normal file
View File

@ -0,0 +1,26 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* This file demonstrates creating a form with the FormBuilder class.
*/
$form = new FormBuilder("Sample Form", "fas fa-code", "", "GET");
$form->setID("sampleform");
$form->addHiddenInput("page", "form");
$form->addInput("name", "John", "text", true, null, null, "Your name", "fas fa-user", 6, 5, 20, "John(ny)?|Steve", "Invalid name, please enter John, Johnny, or Steve.");
$form->addInput("location", "", "select", true, null, ["1" => "Here", "2" => "There"], "Location", "fas fa-map-marker");
$form->addInput("textbox", "Hello world", "textarea", true, null, null, "Text area", "fas fa-font");
$form->addInput("box", "1", "checkbox", true, null, null, "I agree to the terms of service");
$form->addButton("Submit", "fas fa-save", null, "submit", "savebtn");
$form->generate();

View File

@ -24,19 +24,19 @@ die();
?>
<div class="card bg-blue text-light">
<div class="card-body">
<h4 class="card-title"><?php lang("today") ?></h4>
<h4 class="card-title"><?php $Strings->get("today") ?></h4>
<h1><i class="fas fa-fw fa-users"></i> <?php echo $visits_today; ?> <?php
if ($visits_today == 1) {
lang("visit");
$Strings->get("visit");
} else {
lang("visits");
$Strings->get("visits");
}
?></h1>
<h1><i class="fas fa-fw fa-eye"></i> <?php echo $views_today; ?> <?php
if ($views_today == 1) {
lang("page view");
$Strings->get("page view");
} else {
lang("page views");
$Strings->get("page views");
}
?></h1>
</div>
@ -54,19 +54,19 @@ die();
?>
<div class="card bg-green text-light">
<div class="card-body">
<h4 class="card-title"><?php lang("this week") ?></h4>
<h4 class="card-title"><?php $Strings->get("this week") ?></h4>
<h1><i class="fas fa-fw fa-users"></i> <?php echo $visits_week; ?> <?php
if ($visits_week == 1) {
lang("visit");
$Strings->get("visit");
} else {
lang("visits");
$Strings->get("visits");
}
?></h1>
<h1><i class="fas fa-fw fa-eye"></i> <?php echo $views_week; ?> <?php
if ($views_week == 1) {
lang("page view");
$Strings->get("page view");
} else {
lang("page views");
$Strings->get("page views");
}
?></h1>
</div>

View File

@ -7,8 +7,9 @@ require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
require_once __DIR__ . "/../lib/login.php";
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_CONTACT")) {
$user = new User($_SESSION['uid']);
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_CONTACT")) {
if ($_GET['msg'] != "no_permission") {
header("Location: app.php?page=messages&msg=no_permission");
}
@ -19,12 +20,12 @@ if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has
<thead>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><?php lang('actions'); ?></th>
<th data-priority="1"><i class="fas fa-user d-none d-md-inline"></i> <?php lang('name'); ?></th>
<th data-priority="2"><i class="fas fa-envelope d-none d-md-inline"></i> <?php lang('message'); ?></th>
<th data-priority="3"><i class="fas fa-at d-none d-md-inline"></i> <?php lang('email'); ?></th>
<th data-priority="4"><i class="fas fa-globe d-none d-md-inline"></i> <?php lang('site'); ?></th>
<th data-priority="5"><i class="fas fa-calendar d-none d-md-inline"></i> <?php lang('date'); ?></th>
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
<th data-priority="1"><i class="fas fa-user d-none d-md-inline"></i> <?php $Strings->get('name'); ?></th>
<th data-priority="2"><i class="fas fa-envelope d-none d-md-inline"></i> <?php $Strings->get('message'); ?></th>
<th data-priority="3"><i class="fas fa-at d-none d-md-inline"></i> <?php $Strings->get('email'); ?></th>
<th data-priority="4"><i class="fas fa-globe d-none d-md-inline"></i> <?php $Strings->get('site'); ?></th>
<th data-priority="5"><i class="fas fa-calendar d-none d-md-inline"></i> <?php $Strings->get('date'); ?></th>
</tr>
</thead>
<tbody>
@ -58,20 +59,20 @@ if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has
class="btn btn-primary btn-sm"
href="<?php echo $mailto; ?>"
>
<i class="fas fa-reply"></i> <?php lang("reply"); ?>
<i class="fas fa-reply"></i> <?php $Strings->get("reply"); ?>
</a>
<span
class="btn btn-danger btn-sm deletemsgbtn"
data-message="<?php echo $m['mid']; ?>"
>
<i class="fas fa-trash"></i> <?php lang("delete"); ?>
<i class="fas fa-trash"></i> <?php $Strings->get("delete"); ?>
</span>
</td>
<td><?php echo $m['name']; ?></td>
<td><?php echo $m['message']; ?></td>
<td><?php echo $m['email']; ?></td>
<td><?php echo $m['sitename']; ?></td>
<td><?php echo date("M j Y, g:i A", strtotime($m['date'])); ?></td>
<td><span class="d-none"><?php echo strtotime($m['date']); ?></span><?php echo date("M j Y, g:i A", strtotime($m['date'])); ?></td>
</tr>
<?php
}
@ -80,12 +81,12 @@ if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has
<tfoot>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><?php lang('actions'); ?></th>
<th data-priority="1"><i class="fas fa-user d-none d-md-inline"></i> <?php lang('name'); ?></th>
<th data-priority="2"><i class="fas fa-envelope d-none d-md-inline"></i> <?php lang('message'); ?></th>
<th data-priority="3"><i class="fas fa-at d-none d-md-inline"></i> <?php lang('email'); ?></th>
<th data-priority="4"><i class="fas fa-globe d-none d-md-inline"></i> <?php lang('site'); ?></th>
<th data-priority="5"><i class="fas fa-calendar d-none d-md-inline"></i> <?php lang('date'); ?></th>
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
<th data-priority="1"><i class="fas fa-user d-none d-md-inline"></i> <?php $Strings->get('name'); ?></th>
<th data-priority="2"><i class="fas fa-envelope d-none d-md-inline"></i> <?php $Strings->get('message'); ?></th>
<th data-priority="3"><i class="fas fa-at d-none d-md-inline"></i> <?php $Strings->get('email'); ?></th>
<th data-priority="4"><i class="fas fa-globe d-none d-md-inline"></i> <?php $Strings->get('site'); ?></th>
<th data-priority="5"><i class="fas fa-calendar d-none d-md-inline"></i> <?php $Strings->get('date'); ?></th>
</tr>
</tfoot>
</table>

View File

@ -8,23 +8,24 @@ require_once __DIR__ . '/../lib/util.php';
redirectifnotloggedin();
require_once __DIR__ . "/../lib/login.php";
$user = new User($_SESSION['uid']);
$showbuttons = true;
if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has_permission($_SESSION['username'], "SITEWRITER_EDIT")) {
if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_EDIT")) {
$showbuttons = false;
}
?>
<div class="btn-group mb-2">
<a href="app.php?page=sitesettings" class="btn btn-success"><i class="fas fa-plus"></i> <?php lang("new site"); ?></a>
<a href="app.php?page=sitesettings" class="btn btn-success"><i class="fas fa-plus"></i> <?php $Strings->get("new site"); ?></a>
</div>
<table class="table table-bordered table-hover table-sm table-responsive-sm">
<thead>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><?php lang('actions'); ?></th>
<th data-priority="1"><i class="fas fa-font d-none d-md-inline"></i> <?php lang('site name'); ?></th>
<th data-priority="2"><i class="fas fa-globe d-none d-md-inline"></i> <?php lang('url'); ?></th>
<th data-priority="3" class="d-none d-sm-table-cell"><i class="fas fa-paint-brush d-none d-md-inline"></i> <?php lang('theme'); ?></th>
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
<th data-priority="1"><i class="fas fa-font d-none d-md-inline"></i> <?php $Strings->get('site name'); ?></th>
<th data-priority="2"><i class="fas fa-globe d-none d-md-inline"></i> <?php $Strings->get('url'); ?></th>
<th data-priority="3" class="d-none d-sm-table-cell"><i class="fas fa-paint-brush d-none d-md-inline"></i> <?php $Strings->get('theme'); ?></th>
</tr>
</thead>
<tbody>
@ -46,12 +47,12 @@ if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has
<?php
if ($showbuttons) {
?>
<a class="btn btn-primary btn-sm" href="app.php?page=editor&siteid=<?php echo $site['siteid']; ?>"><i class="fas fa-edit"></i> <?php lang("editor"); ?></a>
<a class="btn btn-secondary btn-sm" href="app.php?page=sitesettings&siteid=<?php echo $site['siteid']; ?>"><i class="fas fa-cog"></i> <?php lang("settings"); ?></a>
<a class="btn btn-primary btn-sm" href="app.php?page=editor&siteid=<?php echo $site['siteid']; ?>"><i class="fas fa-edit"></i> <?php $Strings->get("editor"); ?></a>
<a class="btn btn-secondary btn-sm" href="app.php?page=sitesettings&siteid=<?php echo $site['siteid']; ?>"><i class="fas fa-cog"></i> <?php $Strings->get("settings"); ?></a>
<?php
}
?>
<a class="btn btn-info btn-sm" href="<?php echo formatsiteurl($site['url']); ?>" target="_BLANK"><i class="fas fa-eye"></i> <?php lang("view"); ?></a>
<a class="btn btn-info btn-sm" href="<?php echo formatsiteurl($site['url']); ?>" target="_BLANK"><i class="fas fa-eye"></i> <?php $Strings->get("view"); ?></a>
</td>
<td><?php echo $site['sitename']; ?></td>
<td><?php echo $site['url']; ?></td>
@ -64,10 +65,10 @@ if (!account_has_permission($_SESSION['username'], "SITEWRITER") && !account_has
<tfoot>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><?php lang('actions'); ?></th>
<th data-priority="1"><i class="fas fa-font d-none d-md-inline"></i> <?php lang('site name'); ?></th>
<th data-priority="2"><i class="fas fa-globe d-none d-md-inline"></i> <?php lang('url'); ?></th>
<th data-priority="3" class="d-none d-sm-table-cell"><i class="fas fa-paint-brush d-none d-md-inline"></i> <?php lang('theme'); ?></th>
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
<th data-priority="1"><i class="fas fa-font d-none d-md-inline"></i> <?php $Strings->get('site name'); ?></th>
<th data-priority="2"><i class="fas fa-globe d-none d-md-inline"></i> <?php $Strings->get('url'); ?></th>
<th data-priority="3" class="d-none d-sm-table-cell"><i class="fas fa-paint-brush d-none d-md-inline"></i> <?php $Strings->get('theme'); ?></th>
</tr>
</tfoot>
</table>

View File

@ -7,8 +7,9 @@ require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
require_once __DIR__ . "/../lib/login.php";
if (!account_has_permission($_SESSION['username'], "SITEWRITER")) {
$user = new User($_SESSION['uid']);
if (!$user->hasPermission("SITEWRITER")) {
if ($_GET['msg'] != "no_permission") {
header("Location: app.php?page=sitesettings&msg=no_permission");
}
@ -27,7 +28,7 @@ $sitedata = [
];
$settings = [];
if (isset($VARS['siteid']) && !is_empty($VARS['siteid'])) {
if (!empty($VARS['siteid'])) {
if ($database->has('sites', ['siteid' => $VARS['siteid']])) {
$siteid = $VARS['siteid'];
$sitedata = $database->select(
@ -74,11 +75,11 @@ function getsetting($name) {
<?php
if ($editing) {
?>
<i class="fas fa-edit"></i> <?php lang2("editing site", ['site' => "<span id=\"name_title\">" . htmlspecialchars($sitedata['sitename']) . "</span>"]); ?>
<i class="fas fa-edit"></i> <?php $Strings->build("editing site", ['site' => "<span id=\"name_title\">" . htmlspecialchars($sitedata['sitename']) . "</span>"]); ?>
<?php
} else {
?>
<i class="fas fa-plus"></i> <?php lang2("adding site", ['site' => "<span id=\"name_title\">" . htmlspecialchars($sitedata['sitename']) . "</span>"]); ?>
<i class="fas fa-plus"></i> <?php $Strings->build("adding site", ['site' => "<span id=\"name_title\">" . htmlspecialchars($sitedata['sitename']) . "</span>"]); ?>
<?php
}
?>
@ -86,16 +87,16 @@ function getsetting($name) {
<div class="card-body">
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title"><i class="fas fa-info-circle"></i> <?php lang("site info"); ?></h5>
<h5 class="card-title"><i class="fas fa-info-circle"></i> <?php $Strings->get("site info"); ?></h5>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="name"><i class="fas fa-font"></i> <?php lang("title"); ?></label></span>
<span class="input-group-text"><label for="name"><i class="fas fa-font"></i> <?php $Strings->get("title"); ?></label></span>
</div>
<input type="text" class="form-control" id="name" name="name" placeholder="Foo Bar" required="required" value="<?php echo htmlspecialchars($sitedata['sitename']); ?>" />
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="url"><i class="fas fa-globe"></i> <?php lang("url"); ?></label></span>
<span class="input-group-text"><label for="url"><i class="fas fa-globe"></i> <?php $Strings->get("url"); ?></label></span>
</div>
<input type="text" class="form-control" id="url" name="url" placeholder="https://example.com" required="required" value="<?php echo htmlspecialchars($sitedata['url']); ?>" />
</div>
@ -104,7 +105,7 @@ function getsetting($name) {
<div class="card mb-4">
<div class="card-body">
<div class="form-group">
<h5 class="card-title"><label for="theme"><i class="fas fa-paint-brush"></i> <?php lang('theme'); ?></label></h5>
<h5 class="card-title"><label for="theme"><i class="fas fa-paint-brush"></i> <?php $Strings->get('theme'); ?></label></h5>
<div class="theme_bin_overflow">
<div class="theme_bin card-columns">
<?php
@ -132,18 +133,18 @@ function getsetting($name) {
<span class="d-flex">
<h4 class="mr-auto"><?php echo $info['name']; ?></h4>
<a href="public/index.php?page=index&siteid=<?php echo $siteid; ?>&theme=<?php echo $t; ?>" target="_BLANK">
<i class="fas fa-eye"></i> <?php lang("preview"); ?>
<i class="fas fa-eye"></i> <?php $Strings->get("preview"); ?>
</a>
</span>
<b><?php lang("theme type"); ?></b>:
<b><?php $Strings->get("theme type"); ?></b>:
<?php
if ($info['singlepage'] == true) {
lang("single page");
$Strings->get("single page");
} else {
lang("multiple page");
$Strings->get("multiple page");
}
?><br />
<b><?php lang("templates"); ?></b>:
<b><?php $Strings->get("templates"); ?></b>:
<?php
$temtitles = [];
foreach ($info['templates'] as $tem) {
@ -151,11 +152,11 @@ function getsetting($name) {
}
echo implode(", ", $temtitles);
?><br />
<b><?php lang("color styles"); ?></b>:
<b><?php $Strings->get("color styles"); ?></b>:
<div class="list-group colorSelector">
<?php
if (count($info['colors']) == 0) {
$info['colors'] = ["default" => ["title" => lang("default", false), "description" => ""]];
$info['colors'] = ["default" => ["title" => $Strings->get("default", false), "description" => ""]];
}
foreach ($info['colors'] as $c => $color) {
$checked = "";
@ -169,7 +170,7 @@ function getsetting($name) {
<b><?php echo $color["title"]; ?></b>
<div class="text-nowrap">
<a href="public/index.php?page=index&siteid=<?php echo $siteid; ?>&theme=<?php echo $t; ?>&color=<?php echo $c; ?>" target="_BLANK">
<i class="fas fa-eye"></i> <?php lang("preview"); ?>
<i class="fas fa-eye"></i> <?php $Strings->get("preview"); ?>
</a>
</div>
</div>
@ -194,32 +195,32 @@ function getsetting($name) {
<!-- Company/Org Info -->
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="fas fa-briefcase"></i> <?php lang("company info"); ?></h5>
<h5 class="card-title"><i class="fas fa-briefcase"></i> <?php $Strings->get("company info"); ?></h5>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="businessname"><i class="fas fa-font"></i> <?php lang("name"); ?></label></span>
<span class="input-group-text"><label for="businessname"><i class="fas fa-font"></i> <?php $Strings->get("name"); ?></label></span>
</div>
<input type="text" class="form-control" name="settings[businessname]" id="businessname" value="<?php echo getsetting("businessname"); ?>" />
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="phone"><i class="fas fa-phone"></i> <?php lang("phone"); ?></label></span>
<span class="input-group-text"><label for="phone"><i class="fas fa-phone"></i> <?php $Strings->get("phone"); ?></label></span>
</div>
<input type="text" class="form-control" name="settings[phone]" id="phone" value="<?php echo getsetting("phone"); ?>" />
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="address"><i class="fas fa-map-marker"></i> <?php lang("address"); ?></label></span>
<span class="input-group-text"><label for="address"><i class="fas fa-map-marker"></i> <?php $Strings->get("address"); ?></label></span>
</div>
<textarea class="form-control" name="settings[address]" id="address" rows="2"><?php echo getsetting("address"); ?></textarea>
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="email"><i class="fas fa-envelope"></i> <?php lang("email"); ?></label></span>
<span class="input-group-text"><label for="email"><i class="fas fa-envelope"></i> <?php $Strings->get("email"); ?></label></span>
</div>
<input type="email" class="form-control" name="settings[email]" id="email" value="<?php echo getsetting("email"); ?>" />
</div>
@ -229,26 +230,40 @@ function getsetting($name) {
<!-- Analytics -->
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title"><i class="fas fa-chart-bar"></i> <?php lang("analytics"); ?></h5>
<h5 class="card-title"><i class="fas fa-chart-bar"></i> <?php $Strings->get("analytics"); ?></h5>
<div class="form-check">
<input class="form-check-input" type="radio" name="settings[analytics]" value="" id="analytics_on" <?php echo ((isset($settings["analytics"]) && $settings["analytics"] === "off") ? "" : "checked") ?>>
<label class="form-check-label" for="analytics_on">
<?php lang("enable built-in analytics"); ?>
<?php $Strings->get("enable built-in analytics"); ?>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="settings[analytics]" value="off" id="analytics_off" <?php echo ((isset($settings["analytics"]) && $settings["analytics"] === "off") ? "checked" : "") ?>>
<label class="form-check-label" for="analytics_off">
<?php lang("disable built-in analytics"); ?>
<?php $Strings->get("disable built-in analytics"); ?>
</label>
</div>
</div>
</div>
<!-- Contact Form -->
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title"><i class="fas fa-comments"></i> <?php $Strings->get("contact form"); ?></h5>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="contactemail"><i class="fas fa-envelope"></i> Forward to:</label></span>
</div>
<input type="email" class="form-control" name="settings[contactemail]" id="contactemail" value="<?php echo getsetting("contactemail"); ?>" />
</div>
<small class="form-text"><?php $Strings->get("contact form messages will be forwarded to this email address"); ?></small>
</div>
</div>
<!-- Extra code header snippets -->
<div class="card mt-4 mb-4">
<div class="card-body">
<h5 class="card-title"><label for="extracode"><i class="fas fa-code"></i> <?php lang("extra code"); ?></label></h5>
<h5 class="card-title"><label for="extracode"><i class="fas fa-code"></i> <?php $Strings->get("extra code"); ?></label></h5>
<textarea class="form-control" name="settings[extracode]" id="extracode" placeholder="<script></script>" rows="5"><?php echo (isset($settings["extracode"]) ? $settings["extracode"] : ""); ?></textarea>
</div>
</div>
@ -258,7 +273,7 @@ function getsetting($name) {
<div class="col-12 col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="fas fa-share-square"></i> <?php lang("social links"); ?></h5>
<h5 class="card-title"><i class="fas fa-share-square"></i> <?php $Strings->get("social links"); ?></h5>
<div class="input-group">
<div class="input-group-prepend">
@ -351,7 +366,7 @@ function getsetting($name) {
<div class="col-12">
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title"><label><i class="fas fa-list"></i> <?php lang("site footer links"); ?></label></h5>
<h5 class="card-title"><label><i class="fas fa-list"></i> <?php $Strings->get("site footer links"); ?></label></h5>
<div id="footer-link-bin">
<?php
$footerset = false;
@ -373,11 +388,11 @@ function getsetting($name) {
?>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><?php lang("title"); ?>:</span>
<span class="input-group-text"><?php $Strings->get("title"); ?>:</span>
</div>
<input type="text" class="form-control" name="settings[footerlinks][<?php echo $i; ?>][title]" value="<?php echo $title; ?>" />
<div class="input-group-prepend">
<span class="input-group-text"><?php lang("link"); ?>:</span>
<span class="input-group-text"><?php $Strings->get("link"); ?>:</span>
</div>
<input type="text" class="form-control" name="settings[footerlinks][<?php echo $i; ?>][link]" value="<?php echo $url; ?>" />
</div>
@ -396,7 +411,7 @@ function getsetting($name) {
<input type="hidden" name="source" value="sites" />
<div class="card-footer d-flex">
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
</div>
</div>
</form>

View File

@ -1,345 +1 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
svg:not(:root).svg-inline--fa {
overflow: visible; }
.svg-inline--fa {
display: inline-block;
font-size: inherit;
height: 1em;
overflow: visible;
vertical-align: -.125em; }
.svg-inline--fa.fa-lg {
vertical-align: -.225em; }
.svg-inline--fa.fa-w-1 {
width: 0.0625em; }
.svg-inline--fa.fa-w-2 {
width: 0.125em; }
.svg-inline--fa.fa-w-3 {
width: 0.1875em; }
.svg-inline--fa.fa-w-4 {
width: 0.25em; }
.svg-inline--fa.fa-w-5 {
width: 0.3125em; }
.svg-inline--fa.fa-w-6 {
width: 0.375em; }
.svg-inline--fa.fa-w-7 {
width: 0.4375em; }
.svg-inline--fa.fa-w-8 {
width: 0.5em; }
.svg-inline--fa.fa-w-9 {
width: 0.5625em; }
.svg-inline--fa.fa-w-10 {
width: 0.625em; }
.svg-inline--fa.fa-w-11 {
width: 0.6875em; }
.svg-inline--fa.fa-w-12 {
width: 0.75em; }
.svg-inline--fa.fa-w-13 {
width: 0.8125em; }
.svg-inline--fa.fa-w-14 {
width: 0.875em; }
.svg-inline--fa.fa-w-15 {
width: 0.9375em; }
.svg-inline--fa.fa-w-16 {
width: 1em; }
.svg-inline--fa.fa-w-17 {
width: 1.0625em; }
.svg-inline--fa.fa-w-18 {
width: 1.125em; }
.svg-inline--fa.fa-w-19 {
width: 1.1875em; }
.svg-inline--fa.fa-w-20 {
width: 1.25em; }
.svg-inline--fa.fa-pull-left {
margin-right: .3em;
width: auto; }
.svg-inline--fa.fa-pull-right {
margin-left: .3em;
width: auto; }
.svg-inline--fa.fa-border {
height: 1.5em; }
.svg-inline--fa.fa-li {
width: 2em; }
.svg-inline--fa.fa-fw {
width: 1.25em; }
.fa-layers svg.svg-inline--fa {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0; }
.fa-layers {
display: inline-block;
height: 1em;
position: relative;
text-align: center;
vertical-align: -.125em;
width: 1em; }
.fa-layers svg.svg-inline--fa {
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-text, .fa-layers-counter {
display: inline-block;
position: absolute;
text-align: center; }
.fa-layers-text {
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-counter {
background-color: #ff253a;
border-radius: 1em;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #fff;
height: 1.5em;
line-height: 1;
max-width: 5em;
min-width: 1.5em;
overflow: hidden;
padding: .25em;
right: 0;
text-overflow: ellipsis;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-bottom-right {
bottom: 0;
right: 0;
top: auto;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: bottom right;
transform-origin: bottom right; }
.fa-layers-bottom-left {
bottom: 0;
left: 0;
right: auto;
top: auto;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: bottom left;
transform-origin: bottom left; }
.fa-layers-top-right {
right: 0;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-top-left {
left: 0;
right: auto;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top left;
transform-origin: top left; }
.fa-lg {
font-size: 1.33333em;
line-height: 0.75em;
vertical-align: -.0667em; }
.fa-xs {
font-size: .75em; }
.fa-sm {
font-size: .875em; }
.fa-1x {
font-size: 1em; }
.fa-2x {
font-size: 2em; }
.fa-3x {
font-size: 3em; }
.fa-4x {
font-size: 4em; }
.fa-5x {
font-size: 5em; }
.fa-6x {
font-size: 6em; }
.fa-7x {
font-size: 7em; }
.fa-8x {
font-size: 8em; }
.fa-9x {
font-size: 9em; }
.fa-10x {
font-size: 10em; }
.fa-fw {
text-align: center;
width: 1.25em; }
.fa-ul {
list-style-type: none;
margin-left: 2.5em;
padding-left: 0; }
.fa-ul > li {
position: relative; }
.fa-li {
left: -2em;
position: absolute;
text-align: center;
width: 2em;
line-height: inherit; }
.fa-border {
border: solid 0.08em #eee;
border-radius: .1em;
padding: .2em .25em .15em; }
.fa-pull-left {
float: left; }
.fa-pull-right {
float: right; }
.fa.fa-pull-left,
.fas.fa-pull-left,
.far.fa-pull-left,
.fal.fa-pull-left,
.fab.fa-pull-left {
margin-right: .3em; }
.fa.fa-pull-right,
.fas.fa-pull-right,
.far.fa-pull-right,
.fal.fa-pull-right,
.fab.fa-pull-right {
margin-left: .3em; }
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear; }
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8); }
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
.fa-rotate-90 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
.fa-rotate-180 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
-webkit-transform: rotate(180deg);
transform: rotate(180deg); }
.fa-rotate-270 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
.fa-flip-horizontal {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
-webkit-transform: scale(-1, 1);
transform: scale(-1, 1); }
.fa-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(1, -1);
transform: scale(1, -1); }
.fa-flip-horizontal.fa-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(-1, -1);
transform: scale(-1, -1); }
:root .fa-rotate-90,
:root .fa-rotate-180,
:root .fa-rotate-270,
:root .fa-flip-horizontal,
:root .fa-flip-vertical {
-webkit-filter: none;
filter: none; }
.fa-stack {
display: inline-block;
height: 2em;
position: relative;
width: 2em; }
.fa-stack-1x,
.fa-stack-2x {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0; }
.svg-inline--fa.fa-stack-1x {
height: 1em;
width: 1em; }
.svg-inline--fa.fa-stack-2x {
height: 2em;
width: 2em; }
.fa-inverse {
color: #fff; }
.sr-only {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px; }
.sr-only-focusable:active, .sr-only-focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto; }
.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 586 KiB

After

Width:  |  Height:  |  Size: 644 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 141 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 477 KiB

After

Width:  |  Height:  |  Size: 797 KiB

File diff suppressed because one or more lines are too long

View File

@ -4,9 +4,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
ignore_user_abort(true);
require __DIR__ . "/../lib/requiredpublic.php";
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
function output_card($content) {
?>
<!DOCTYPE html>
@ -24,7 +28,7 @@ function output_card($content) {
<?php
}
if (empty($_POST['name']) || empty($_POST['message']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
if (empty($_POST['name']) || empty($_POST['message']) || !filter_var($_POST['real_email'], FILTER_VALIDATE_EMAIL) || !empty($_POST['email'])) {
$content = <<<END
<p>Whoops! You didn't fill out the contact form properly.</p>
<p><a href="javascript:history.back()" class="btn btn-primary btn-sm">Go back</a> and try again.</p>
@ -33,10 +37,12 @@ END;
die();
}
$siteid = getsiteid();
$database->insert("messages", [
"siteid" => getsiteid(),
"siteid" => $siteid,
"name" => htmlspecialchars($_POST['name']),
"email" => htmlspecialchars($_POST['email']),
"email" => htmlspecialchars($_POST['real_email']),
"message" => htmlspecialchars($_POST['message']),
"date" => date("Y-m-d H:i:s")
]);
@ -48,3 +54,35 @@ $content = <<<END
END;
output_card($content);
ob_flush();
flush();
if ($database->has('settings', ["AND" => ['siteid' => $siteid, 'key' => 'contactemail']])) {
$emailto = $database->get('settings', "value", ["AND" => ['siteid' => $siteid, 'key' => 'contactemail']]);
// Setup mailer
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = $SETTINGS["email"]["host"];
$mail->SMTPAuth = $SETTINGS["email"]["auth"];
if ($SETTINGS["email"]["auth"]) {
$mail->Username = $SETTINGS["email"]["user"];
$mail->Password = $SETTINGS["email"]["password"];
}
if ($SETTINGS["email"]["secure"] != "none") {
$mail->SMTPSecure = $SETTINGS["email"]["secure"];
}
$mail->Port = $SETTINGS["email"]["port"];
$mail->isHTML(true);
$mail->setFrom($SETTINGS["email"]["fromaddress"], $SETTINGS["email"]["fromname"]);
$mail->addAddress($emailto);
$mail->addReplyTo($_POST['email'], $_POST['name']);
$mail->Subject = 'Website Contact Form Message';
$mail->Body = '<p><b>From:</b> ' . htmlspecialchars($_POST['name']) . ' &lt;<a href="mailto:' . htmlspecialchars($_POST['email']) . '">' . $_POST['email'] . '</a>&gt;</p>'
. '<p><b>Message:</b> <br>' . htmlspecialchars($_POST['message']) . '</p>';
$mail->AltBody = "From: $_POST[name] <$_POST[email]>\r\n\r\nMessage: \r\n$_POST[message]";
$mail->send();
}

View File

@ -8,11 +8,11 @@
require_once __DIR__ . "/../lib/requiredpublic.php";
$base = FILE_UPLOAD_PATH;
$base = $SETTINGS["file_upload_path"];
$filepath = "";
if ($_GET['file'] === URL . "/static/img/no-image.svg") {
if ($_GET['file'] === $SETTINGS["url"] . "/static/img/no-image.svg") {
header("Content-Type: image/svg+xml");
ob_end_flush();
@ -26,7 +26,7 @@ if (isset($_GET['file'])) {
http_response_code(404);
die("404 File Not Found");
}
if (strpos(realpath($filepath), FILE_UPLOAD_PATH) !== 0) {
if (strpos(realpath($filepath), $SETTINGS["file_upload_path"]) !== 0) {
http_response_code(404);
die("404 File Not Found");
}
@ -56,6 +56,7 @@ $ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT";
header("Expires: $ts");
header("Pragma: cache");
header("Cache-Control: max-age=$seconds_to_cache");
header("Content-Disposition: filename=\"" . pathinfo($filepath)['basename'] . "\"");
ob_end_flush();

View File

@ -23,8 +23,8 @@ if (!getsiteid()) {
<h2 class="card-title">Welcome!</h2>
<p>You're seeing this message because no website has been created yet.
<br />
Open <?php echo SITE_TITLE; ?> to make one.</p>
<p><a href="<?php echo PORTAL_URL; ?>" class="btn btn-primary">Log In</a></p>
Open <?php echo $SETTINGS["site_title"]; ?> to make one.</p>
<p><a href="<?php echo $SETTINGS["accounthub"]["home"]; ?>" class="btn btn-primary">Log In</a></p>
</div>
</div>
</div>
@ -57,15 +57,15 @@ if (isset($_GET['edit'])) {
}
?>
<style><?php echo file_get_contents(__DIR__ . "/../static/css/editor.css"); ?></style>
<script src="<?php echo URL; ?>/static/js/jquery-3.3.1.min.js"></script>
<script src="<?php echo URL; ?>/static/js/tinymce/tinymce.min.js"></script>
<script src="<?php echo $SETTINGS["url"]; ?>/static/js/jquery-3.3.1.min.js"></script>
<script src="<?php echo $SETTINGS["url"]; ?>/static/js/tinymce/tinymce.min.js"></script>
<script>
static_dir = "<?php echo URL; ?>/static";
static_dir = "<?php echo $SETTINGS["url"]; ?>/static";
page_slug = "<?php echo getpageslug(); ?>";
site_id = "<?php echo getsiteid(); ?>";
pages_list = <?php echo json_encode($allpages); ?>;
</script>
<script src="<?php echo URL; ?>/static/js/editor.js"></script>
<script src="<?php echo $SETTINGS["url"]; ?>/static/js/editor.js"></script>
<?php
}
?>

View File

@ -17,6 +17,10 @@ include __DIR__ . "/inc/header.inc.php";
</div>
<div class="container">
<form action="<?php get_site_url(); ?>contact.php" method="POST">
<span style="display: none;">
Leave this box empty
<input name="email" id="email" placeholder="Email" type="email" style="display: none;" autocomplete="off" />
</span>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label for="name">Name</label>
@ -24,7 +28,7 @@ include __DIR__ . "/inc/header.inc.php";
</div>
<div class="col-12 col-md-6 mb-3">
<label for="email">Email</label>
<input type="email" class="form-control" name="email" id="email" placeholder="you@example.com" required />
<input type="email" class="form-control" name="real_email" id="email" placeholder="you@example.com" required />
</div>
<div class="col-12">
<label for="message">Message</label>

View File

@ -10,13 +10,17 @@
<h2 class="major"><?php get_page_clean_title(); ?></h2>
<p class="sw-text" data-component="lead"><?php get_component("lead"); ?></p>
<form method="post" action="contact.php">
<span style="display: none;">
Leave this box empty
<input name="email" id="email" placeholder="Email" type="email" style="display: none;" autocomplete="off" />
</span>
<div class="field half first">
<label for="name">Name</label>
<input type="text" name="name" id="name" required />
</div>
<div class="field half">
<label for="email">Email</label>
<input type="text" name="email" id="email" required />
<input type="text" name="real_email" id="email" required />
</div>
<div class="field">
<label for="message">Message</label>

View File

@ -163,13 +163,17 @@ include __DIR__ . "/inc/head.inc.php";
<div class="split style1">
<section>
<form method="post" action="contact.php">
<span style="display: none;">
Leave this box empty
<input name="email" id="email" placeholder="Email" type="email" style="display: none;" autocomplete="off" />
</span>
<div class="field half first">
<label for="name">Name</label>
<input type="text" name="name" id="name" required />
</div>
<div class="field half">
<label for="email">Email</label>
<input type="email" name="email" id="email" required />
<input type="email" name="real_email" id="email" required />
</div>
<div class="field">
<label for="message">Message</label>

View File

@ -6,11 +6,15 @@ include __DIR__ . "/inc/header.inc.php";
<?php get_component("contact-header"); ?>
</div></h2>
<form method="post" action="contact.php">
<span style="display: none;">
Leave this box empty
<input name="email" id="email" placeholder="Email" type="email" style="display: none;" autocomplete="off" />
</span>
<div class="field">
<input type="text" name="name" id="name" placeholder="Name" />
</div>
<div class="field">
<input type="email" name="email" id="email" placeholder="Email" />
<input type="email" name="real_email" id="email" placeholder="Email" />
</div>
<div class="field">
<textarea name="message" id="message" placeholder="Message" rows="4"></textarea>

View File

@ -74,7 +74,7 @@
<a href="<?php get_url_or_slug($article['link']); ?>" class="image fit"><img src="<?php get_file_url($article['image']); ?>" alt="" /></a>
<span class="sw-complex" data-json="<?php get_escaped_json($article); ?>" data-component="<?php echo "article-$i"; ?>"></span>
<div class="sw-editable" data-component="article-text-<?php echo $i; ?>">
<p><?php get_component("article-text-$i"); ?></p>
<?php get_component("article-text-$i"); ?>
</div>
<?php
if (!empty($article['text'])) {

View File

@ -2,13 +2,17 @@
<footer id="footer">
<section>
<form method="post" action="contact.php">
<span style="display: none;">
Leave this box empty
<input name="email" id="email" placeholder="Email" type="email" style="display: none;" autocomplete="off" />
</span>
<div class="field">
<label for="name">Name</label>
<input type="text" name="name" id="name" required />
</div>
<div class="field">
<label for="email">Email</label>
<input type="text" name="email" id="email" required />
<input type="text" name="real_email" id="email" required />
</div>
<div class="field">
<label for="message">Message</label>

View File

@ -4,11 +4,15 @@
<section>
<h2>Get in touch</h2>
<form method="post" action="contact.php">
<span style="display: none;">
Leave this box empty
<input name="email" id="email" placeholder="Email" type="email" style="display: none;" autocomplete="off" />
</span>
<div class="field half first">
<input type="text" name="name" id="name" placeholder="Name" required />
</div>
<div class="field half">
<input type="email" name="email" id="email" placeholder="Email" required />
<input type="email" name="real_email" id="email" placeholder="Email" required />
</div>
<div class="field">
<textarea name="message" id="message" placeholder="Message" required ></textarea>

View File

@ -19,6 +19,10 @@
</h3>
<form action="<?php get_site_url(); ?>contact.php" method="POST">
<span style="display: none;">
Leave this box empty
<input name="email" id="email" placeholder="Email" type="email" style="display: none;" autocomplete="off" />
</span>
<div class="row">
<div class="6u 12u(medium)">
<label for="name">Name</label>
@ -26,7 +30,7 @@
</div>
<div class="6u 12u(medium)">
<label for="email">Email</label>
<input type="email" name="email" id="email" placeholder="you@example.com" required />
<input type="email" name="real_email" id="email" placeholder="you@example.com" required />
</div>
<div class="12u">
<label for="message">Message</label>

View File

@ -32,7 +32,6 @@ session_start(); // stick some cookies in it
// renew session cookie
setcookie(session_name(), session_id(), time() + $session_length, "/", false, false);
$captcha_server = (CAPTCHA_ENABLED === true ? preg_replace("/http(s)?:\/\//", "", CAPTCHA_SERVER) : "");
if ($_SESSION['mobile'] === TRUE) {
header("Content-Security-Policy: "
. "default-src 'self';"
@ -42,8 +41,8 @@ if ($_SESSION['mobile'] === TRUE) {
. "frame-src 'self'; "
. "font-src 'self'; "
. "connect-src *; "
. "style-src 'self' 'unsafe-inline' $captcha_server; "
. "script-src 'self' 'unsafe-inline' $captcha_server");
. "style-src 'self' 'unsafe-inline'; "
. "script-src 'self' 'unsafe-inline'");
} else {
header("Content-Security-Policy: "
. "default-src 'self';"
@ -53,8 +52,8 @@ if ($_SESSION['mobile'] === TRUE) {
. "frame-src 'self'; "
. "font-src 'self'; "
. "connect-src *; "
. "style-src 'self' 'unsafe-inline' $captcha_server; "
. "script-src 'self' 'nonce-$SECURE_NONCE' $captcha_server");
. "style-src 'self' 'nonce-$SECURE_NONCE'; "
. "script-src 'self' 'nonce-$SECURE_NONCE'");
}
//
@ -62,9 +61,14 @@ if ($_SESSION['mobile'] === TRUE) {
require __DIR__ . '/vendor/autoload.php';
// List of alert messages
require __DIR__ . '/lang/messages.php';
// text strings (i18n)
require __DIR__ . '/lang/' . LANGUAGE . ".php";
require __DIR__ . '/langs/messages.php';
$libs = glob(__DIR__ . "/lib/*.lib.php");
foreach ($libs as $lib) {
require_once $lib;
}
$Strings = new Strings($SETTINGS['language']);
/**
* Kill off the running process and spit out an error message
@ -88,7 +92,7 @@ function sendError($error) {
. "<p>" . htmlspecialchars($error) . "</p>");
}
date_default_timezone_set(TIMEZONE);
date_default_timezone_set($SETTINGS['timezone']);
// Database settings
// Also inits database and stuff
@ -97,12 +101,12 @@ use Medoo\Medoo;
$database;
try {
$database = new Medoo([
'database_type' => DB_TYPE,
'database_name' => DB_NAME,
'server' => DB_SERVER,
'username' => DB_USER,
'password' => DB_PASS,
'charset' => DB_CHARSET
'database_type' => $SETTINGS['database']['type'],
'database_name' => $SETTINGS['database']['name'],
'server' => $SETTINGS['database']['server'],
'username' => $SETTINGS['database']['user'],
'password' => $SETTINGS['database']['password'],
'charset' => $SETTINGS['database']['charset']
]);
} catch (Exception $ex) {
//header('HTTP/1.1 500 Internal Server Error');
@ -110,7 +114,7 @@ try {
}
if (!DEBUG) {
if (!$SETTINGS['debug']) {
error_reporting(0);
} else {
error_reporting(E_ALL);
@ -127,66 +131,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
define("GET", true);
}
/**
* Checks if a string or whatever is empty.
* @param $str The thingy to check
* @return boolean True if it's empty or whatever.
*/
function is_empty($str) {
return (is_null($str) || !isset($str) || $str == '');
}
/**
* I18N string getter. If the key doesn't exist, outputs the key itself.
* @param string $key I18N string key
* @param boolean $echo whether to echo the result or return it (default echo)
*/
function lang($key, $echo = true) {
if (array_key_exists($key, STRINGS)) {
$str = STRINGS[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING);
$str = $key;
}
if ($echo) {
echo $str;
} else {
return $str;
}
}
/**
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
* @param string $key I18N string key
* @param array $replace key-value array of replacements.
* If the string value is "hello {abc}" and you give ["abc" => "123"], the
* result will be "hello 123".
* @param boolean $echo whether to echo the result or return it (default echo)
*/
function lang2($key, $replace, $echo = true) {
if (array_key_exists($key, STRINGS)) {
$str = STRINGS[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING);
$str = $key;
}
foreach ($replace as $find => $repl) {
$str = str_replace("{" . $find . "}", $repl, $str);
}
if ($echo) {
echo $str;
} else {
return $str;
}
}
function dieifnotloggedin() {
global $SETTINGS;
if ($_SESSION['loggedin'] != true) {
sendError("Session expired. Please log out and log in again.");
}
$user = new User($_SESSION['uid']);
foreach ($SETTINGS['permissions'] as $perm) {
if (!$user->hasPermission($perm)) {
session_destroy();
die("You don't have permission to be here.");
}
}
}
/**
@ -206,41 +162,18 @@ function checkDBError($specials = []) {
}
}
/*
* http://stackoverflow.com/a/20075147
*/
if (!function_exists('base_url')) {
function base_url($atRoot = FALSE, $atCore = FALSE, $parse = FALSE) {
if (isset($_SERVER['HTTP_HOST'])) {
$http = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http';
$hostname = $_SERVER['HTTP_HOST'];
$dir = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
$core = preg_split('@/@', str_replace($_SERVER['DOCUMENT_ROOT'], '', realpath(dirname(__FILE__))), NULL, PREG_SPLIT_NO_EMPTY);
$core = $core[0];
$tmplt = $atRoot ? ($atCore ? "%s://%s/%s/" : "%s://%s/") : ($atCore ? "%s://%s/%s/" : "%s://%s%s");
$end = $atRoot ? ($atCore ? $core : $hostname) : ($atCore ? $core : $dir);
$base_url = sprintf($tmplt, $http, $hostname, $end);
} else
$base_url = 'http://localhost/';
if ($parse) {
$base_url = parse_url($base_url);
if (isset($base_url['path']))
if ($base_url['path'] == '/')
$base_url['path'] = '';
}
return $base_url;
}
}
function redirectIfNotLoggedIn() {
global $SETTINGS;
if ($_SESSION['loggedin'] !== TRUE) {
header('Location: ' . URL . '/index.php');
header('Location: ' . $SETTINGS['url'] . '/index.php');
die();
}
$user = new User($_SESSION['uid']);
foreach ($SETTINGS['permissions'] as $perm) {
if (!$user->hasPermission($perm)) {
session_destroy();
header('Location: ./index.php');
die("You don't have permission to be here.");
}
}
}

View File

@ -1,68 +1,85 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Whether to show debugging data in output.
// DO NOT SET TO TRUE IN PRODUCTION!!!
define("DEBUG", false);
// Settings for the app.
// Copy to settings.php and customize.
// Database connection settings
// See http://medoo.in/api/new for info
define("DB_TYPE", "mysql");
define("DB_NAME", "sitewriter");
define("DB_SERVER", "localhost");
define("DB_USER", "sitewriter");
define("DB_PASS", "");
define("DB_CHARSET", "utf8");
// Name of the app.
define("SITE_TITLE", "SiteWriter");
// URL of the AccountHub API endpoint
define("PORTAL_API", "http://localhost/accounthub/api.php");
// URL of the AccountHub home page
define("PORTAL_URL", "http://localhost/accounthub/home.php");
// AccountHub API Key
define("PORTAL_KEY", "123");
// For supported values, see http://php.net/manual/en/timezones.php
define("TIMEZONE", "America/Denver");
// Base URL for site links.
define('URL', '/sitewriter');
// Folder for public files
// This should not be inside the web root for security reasons.
define('FILE_UPLOAD_PATH', __DIR__ . '/public/files');
// Use pretty URLs (requires correct web server configuration)
define('PRETTY_URLS', false);
// Location of MaxMind GeoIP database
//
// I'll just leave this here:
// This product includes GeoLite2 data created by MaxMind, available from
// http://www.maxmind.com
define('GEOIP_DB', __DIR__ . "/GeoLite2-City.mmdb");
// Unsplash photo integration
define('ENABLE_UNSPLASH', false);
define('UNSPLASH_APPID', '');
define('UNSPLASH_ACCESSKEY', '');
define('UNSPLASH_SECRETKEY', '');
define('UNSPLASH_UTMSOURCE', 'SiteWriter');
// Use Captcheck on login screen
// https://captcheck.netsyms.com
define("CAPTCHA_ENABLED", FALSE);
define('CAPTCHA_SERVER', 'https://captcheck.netsyms.com');
// See lang folder for language options
define('LANGUAGE', "en_us");
define("FOOTER_TEXT", "");
define("COPYRIGHT_NAME", "Netsyms Technologies");
$SETTINGS = [
// Whether to output debugging info like PHP notices, warnings,
// and stacktraces.
// Turning this on in production is a security risk and can sometimes break
// things, such as JSON output where extra content is not expected.
"debug" => false,
// Database connection settings
// See http://medoo.in/api/new for info
"database" => [
"type" => "mysql",
"name" => "sitewriter",
"server" => "localhost",
"user" => "",
"password" => "",
"charset" => "utf8"
],
// Name of the app.
"site_title" => "SiteWriter",
// Settings for connecting to the AccountHub server.
"accounthub" => [
// URL for the API endpoint
"api" => "http://localhost/accounthub/api/",
// URL of the home page
"home" => "http://localhost/accounthub/home.php",
// API key
"key" => "123"
],
// Folder for public files
"file_upload_path" => __DIR__ . "/public/files",
// Use pretty URLs (requires correct web server configuration)
"pretty_urls" => false,
// Location of MaxMind GeoIP database
//
// I'll just leave this here:
// This product includes GeoLite2 data created by MaxMind, available from
// http://www.maxmind.com
"geoip_db" => __DIR__ . "/GeoLite2-City.mmdb",
"unsplash" => [
"enable" => false,
"appid" => "",
"accesskey" => "",
"secretkey" => "",
"utmsource" => "SiteWriter"
],
"email" => [
"host" => "",
"auth" => true,
"secure" => "tls",
"port" => 587,
"user" => "",
"password" => "",
"fromaddress" => "",
"fromname" => "SiteWriter"
],
// List of required user permissions to access this app.
"permissions" => [
],
// List of permissions required for API access. Remove to use the value of
// "permissions" instead.
"api_permissions" => [
],
// For supported values, see http://php.net/manual/en/timezones.php
"timezone" => "America/Denver",
// Language to use for localization. See langs folder to add a language.
"language" => "en",
// Shown in the footer of all the pages.
"footer_text" => "",
// Also shown in the footer, but with "Copyright <current_year>" in front.
"copyright" => "Netsyms Technologies",
// Base URL for building links relative to the location of the app.
// Only used when there's no good context for the path.
// The default is almost definitely fine.
"url" => "."
];

File diff suppressed because one or more lines are too long

View File

@ -1,345 +0,0 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
svg:not(:root).svg-inline--fa {
overflow: visible; }
.svg-inline--fa {
display: inline-block;
font-size: inherit;
height: 1em;
overflow: visible;
vertical-align: -.125em; }
.svg-inline--fa.fa-lg {
vertical-align: -.225em; }
.svg-inline--fa.fa-w-1 {
width: 0.0625em; }
.svg-inline--fa.fa-w-2 {
width: 0.125em; }
.svg-inline--fa.fa-w-3 {
width: 0.1875em; }
.svg-inline--fa.fa-w-4 {
width: 0.25em; }
.svg-inline--fa.fa-w-5 {
width: 0.3125em; }
.svg-inline--fa.fa-w-6 {
width: 0.375em; }
.svg-inline--fa.fa-w-7 {
width: 0.4375em; }
.svg-inline--fa.fa-w-8 {
width: 0.5em; }
.svg-inline--fa.fa-w-9 {
width: 0.5625em; }
.svg-inline--fa.fa-w-10 {
width: 0.625em; }
.svg-inline--fa.fa-w-11 {
width: 0.6875em; }
.svg-inline--fa.fa-w-12 {
width: 0.75em; }
.svg-inline--fa.fa-w-13 {
width: 0.8125em; }
.svg-inline--fa.fa-w-14 {
width: 0.875em; }
.svg-inline--fa.fa-w-15 {
width: 0.9375em; }
.svg-inline--fa.fa-w-16 {
width: 1em; }
.svg-inline--fa.fa-w-17 {
width: 1.0625em; }
.svg-inline--fa.fa-w-18 {
width: 1.125em; }
.svg-inline--fa.fa-w-19 {
width: 1.1875em; }
.svg-inline--fa.fa-w-20 {
width: 1.25em; }
.svg-inline--fa.fa-pull-left {
margin-right: .3em;
width: auto; }
.svg-inline--fa.fa-pull-right {
margin-left: .3em;
width: auto; }
.svg-inline--fa.fa-border {
height: 1.5em; }
.svg-inline--fa.fa-li {
width: 2em; }
.svg-inline--fa.fa-fw {
width: 1.25em; }
.fa-layers svg.svg-inline--fa {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0; }
.fa-layers {
display: inline-block;
height: 1em;
position: relative;
text-align: center;
vertical-align: -.125em;
width: 1em; }
.fa-layers svg.svg-inline--fa {
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-text, .fa-layers-counter {
display: inline-block;
position: absolute;
text-align: center; }
.fa-layers-text {
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-counter {
background-color: #ff253a;
border-radius: 1em;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #fff;
height: 1.5em;
line-height: 1;
max-width: 5em;
min-width: 1.5em;
overflow: hidden;
padding: .25em;
right: 0;
text-overflow: ellipsis;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-bottom-right {
bottom: 0;
right: 0;
top: auto;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: bottom right;
transform-origin: bottom right; }
.fa-layers-bottom-left {
bottom: 0;
left: 0;
right: auto;
top: auto;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: bottom left;
transform-origin: bottom left; }
.fa-layers-top-right {
right: 0;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-top-left {
left: 0;
right: auto;
top: 0;
-webkit-transform: scale(0.25);
transform: scale(0.25);
-webkit-transform-origin: top left;
transform-origin: top left; }
.fa-lg {
font-size: 1.33333em;
line-height: 0.75em;
vertical-align: -.0667em; }
.fa-xs {
font-size: .75em; }
.fa-sm {
font-size: .875em; }
.fa-1x {
font-size: 1em; }
.fa-2x {
font-size: 2em; }
.fa-3x {
font-size: 3em; }
.fa-4x {
font-size: 4em; }
.fa-5x {
font-size: 5em; }
.fa-6x {
font-size: 6em; }
.fa-7x {
font-size: 7em; }
.fa-8x {
font-size: 8em; }
.fa-9x {
font-size: 9em; }
.fa-10x {
font-size: 10em; }
.fa-fw {
text-align: center;
width: 1.25em; }
.fa-ul {
list-style-type: none;
margin-left: 2.5em;
padding-left: 0; }
.fa-ul > li {
position: relative; }
.fa-li {
left: -2em;
position: absolute;
text-align: center;
width: 2em;
line-height: inherit; }
.fa-border {
border: solid 0.08em #eee;
border-radius: .1em;
padding: .2em .25em .15em; }
.fa-pull-left {
float: left; }
.fa-pull-right {
float: right; }
.fa.fa-pull-left,
.fas.fa-pull-left,
.far.fa-pull-left,
.fal.fa-pull-left,
.fab.fa-pull-left {
margin-right: .3em; }
.fa.fa-pull-right,
.fas.fa-pull-right,
.far.fa-pull-right,
.fal.fa-pull-right,
.fab.fa-pull-right {
margin-left: .3em; }
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear; }
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8); }
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
.fa-rotate-90 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
.fa-rotate-180 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
-webkit-transform: rotate(180deg);
transform: rotate(180deg); }
.fa-rotate-270 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
.fa-flip-horizontal {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
-webkit-transform: scale(-1, 1);
transform: scale(-1, 1); }
.fa-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(1, -1);
transform: scale(1, -1); }
.fa-flip-horizontal.fa-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(-1, -1);
transform: scale(-1, -1); }
:root .fa-rotate-90,
:root .fa-rotate-180,
:root .fa-rotate-270,
:root .fa-flip-horizontal,
:root .fa-flip-vertical {
-webkit-filter: none;
filter: none; }
.fa-stack {
display: inline-block;
height: 2em;
position: relative;
width: 2em; }
.fa-stack-1x,
.fa-stack-2x {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0; }
.svg-inline--fa.fa-stack-1x {
height: 1em;
width: 1em; }
.svg-inline--fa.fa-stack-2x {
height: 2em;
width: 2em; }
.fa-inverse {
color: #fff; }
.sr-only {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px; }
.sr-only-focusable:active, .sr-only-focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto; }

View File

@ -1,15 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.banner-image {
max-height: 100px;
margin: 2em auto;
border: 1px solid grey;
border-radius: 15%;
}
.footer {
margin-top: 10em;
text-align: center;
}

1
static/css/svg-with-js.min.css vendored Normal file
View File

@ -0,0 +1 @@
.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}

View File

@ -3,10 +3,64 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
$(document).ready(function () {
/* Fade out alerts */
$(".alert .close").click(function (e) {
$(this).parent().fadeOut("slow");
if ($("#msg-alert-box").length) {
$("#msg-alert-box .progress").css("height", "3px");
$("#msg-alert-box .progress").css("border-radius", "0px 0px .25rem .25rem");
$("#msg-alert-box .progress-bar").css("transition", "width 0.25s linear");
var msginteractiontick = 0;
var fifty = 10;
var gone = 20;
var msgticker = setInterval(function () {
if ($("#msg-alert-box .alert:hover").length) {
msginteractiontick = 0;
} else {
msginteractiontick++;
}
if (msginteractiontick > 0) {
function setBarWidth(offset) {
$("#msg-alert-timeout-bar").css("width", (msginteractiontick + offset) / gone * 100 + "%");
}
setBarWidth(-1 + .25);
setTimeout(function () {
setBarWidth(-1 + .5);
}, 250);
setTimeout(function () {
setBarWidth(-1 + .75);
}, 500);
setTimeout(function () {
setBarWidth(0);
}, 750);
} else {
$("#msg-alert-timeout-bar").css("width", "0%");
}
if (msginteractiontick < fifty) {
$("#msg-alert-box").css("opacity", "1");
}
if (msginteractiontick == fifty) {
$("#msg-alert-box").fadeTo(1000, 0.5);
}
if (msginteractiontick >= gone) {
setTimeout(function () {
if (msginteractiontick >= gone) {
$("#msg-alert-box").fadeOut("slow");
window.clearInterval(msgticker);
}
}, 1000);
}
}, 1000 * 1);
$("#msg-alert-box").on("mouseenter", function () {
$("#msg-alert-box").css("opacity", "1");
msginteractiontick = 0;
});
$("#msg-alert-box").on("click", ".close", function (e) {
$("#msg-alert-box").fadeOut("slow");
window.clearInterval(msgticker);
});
}
});

7
static/js/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -74,9 +74,10 @@ $(document).ready(function () {
});
$(".sw-text").each(function () {
var text = $(this).text().trim();
var text = $(this).text().trim().replace(/"/g, "&quot;");
var component = $(this).data("component");
$(this).html("<input type=\"text\" data-component=\"" + component + "\" class=\"sw-text-input\" value=\"" + text + "\" placeholder=\"Click to edit\">");
$(this).closest("a").removeAttr("href"); // Issue #33
});
$(".sw-complex").each(function () {

File diff suppressed because one or more lines are too long

16
static/js/form.js Normal file
View File

@ -0,0 +1,16 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
$("#savebtn").click(function (event) {
var form = $("#sampleform");
if (form[0].checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
form.addClass("was-validated");
});