Added Twig and started templating the pages.
parent
ccd8557362
commit
2f394e530f
|
@ -3,3 +3,6 @@ www/webdata/config.php
|
||||||
www/temp/
|
www/temp/
|
||||||
www/css/archive/*
|
www/css/archive/*
|
||||||
www/js/archive/*
|
www/js/archive/*
|
||||||
|
vendor
|
||||||
|
config/config.ini
|
||||||
|
tmp/
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
const PRODUCTION = false;
|
||||||
|
const DEBUG = true;
|
||||||
|
|
||||||
|
if(DEBUG){
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Composer autoload
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
|
||||||
|
// Application and Models autoload
|
||||||
|
spl_autoload_register(function ($class_name) {
|
||||||
|
if(file_exists(__DIR__."/application/".$class_name.'.php')){
|
||||||
|
include __DIR__."/application/".$class_name.'.php';
|
||||||
|
}
|
||||||
|
elseif(file_exists(__DIR__."/models/".$class_name.'.php')) {
|
||||||
|
include __DIR__."/models/".$class_name.'.php';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Define Twig
|
||||||
|
$twigSettings = PRODUCTION?['cache' => __dir__.'/tmp/']:[];
|
||||||
|
|
||||||
|
$loader = new Twig\Loader\FilesystemLoader(__dir__ . '/templates/'.Config::get('system', 'template', 'default')."/");
|
||||||
|
$twig = new Twig\Environment($loader, $twigSettings);
|
||||||
|
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
private static $instance = null;
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
private function __construct($file = 'config/config.ini'){
|
||||||
|
$this->config = parse_ini_file($file, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(string $category, string $config, $default = false){
|
||||||
|
// Singleton method to avoid reading the same file (too) many times
|
||||||
|
if (self::$instance == null){
|
||||||
|
self::$instance = new Config();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conf = self::$instance;
|
||||||
|
|
||||||
|
if(!empty($conf->config[$category]) && !empty($conf->config[$category][$config])){
|
||||||
|
return $conf->config[$category][$config];
|
||||||
|
}
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Auth {
|
||||||
|
private static $instance = null;
|
||||||
|
private $isLoggedIn = false;
|
||||||
|
|
||||||
|
private function __construct(){
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance(): Auth {
|
||||||
|
if(self::$instance == null){
|
||||||
|
self::$instance = new Auth();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function loginWithCredentials($email, $pass, bool $stayLoggedIn = false){
|
||||||
|
$Auth = new Auth();
|
||||||
|
|
||||||
|
$err = [];
|
||||||
|
|
||||||
|
// get user from database
|
||||||
|
$getUserSQL = "SELECT pwd, user_id, user_email FROM user WHERE user_email = ? LIMIT 1;";
|
||||||
|
$getUserRes = DB::query($getUserSQL, $email);
|
||||||
|
|
||||||
|
$dbPass = "missingPassword";
|
||||||
|
$dbUserId = 0;
|
||||||
|
$dbUserName = "Unknown";
|
||||||
|
|
||||||
|
if($getUserRes->num_rows == 1){
|
||||||
|
// Verify password
|
||||||
|
list($dbPass, $dbUserId, $dbUserName) = $getUserRes->fetch_row();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(password_verify(PwdGen($pass), $dbPass)){
|
||||||
|
session_regenerate_id();
|
||||||
|
|
||||||
|
$md5agent = md5($_SERVER['HTTP_USER_AGENT']);
|
||||||
|
$userKey = GenKey();
|
||||||
|
$shaUserKey = sha1($userKey);
|
||||||
|
$expire = $stayLoggedIn?30:0; // if "stay logged in", stay logged in for 30 days.
|
||||||
|
|
||||||
|
$updateUserSQL = "INSERT INTO user_login (user_id, ckey, ctime, expire, agent) VALUES (?, ?, ?, ?, ?);";
|
||||||
|
if(DB::query($updateUserSQL, $dbUserId, $userKey, time(), $expire, $md5agent)){
|
||||||
|
$_SESSION['user_id'] = $dbUserId;
|
||||||
|
$_SESSION['user_name'] = $dbUserName;
|
||||||
|
$_SESSION['user_agent'] = $md5agent;
|
||||||
|
$_SESSION['user_key'] = $shaUserKey;
|
||||||
|
|
||||||
|
if($stayLoggedIn){
|
||||||
|
setcookie("auth", $shaUserKey.".".$dbUserId, time() + (30 * 24*60*60), "/", $_SERVER['HTTP_HOST'], isset($_SERVER['HTTPS']), true );
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$err[] = "Failed to login.\n".$db->error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$err[] = "Username and/or Password is wrong.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function loginFromAuthKey(): bool {
|
||||||
|
if(isset($_COOKIE['auth'])){
|
||||||
|
$md5agent = md5($_SERVER['HTTP_USER_AGENT']);
|
||||||
|
|
||||||
|
$cookieSplode = explode('.', $_COOKIE['auth']);
|
||||||
|
if(count($cookieSplode) == 2){
|
||||||
|
$tkey = $cookieSplode[0];
|
||||||
|
$tuid = $cookieSplode[1];
|
||||||
|
$verifyLoginCookieRes = DB::query("
|
||||||
|
SELECT ul.user_id, ul.ckey, ul.ctime, ul.expire, ul.agent, u.full_name
|
||||||
|
FROM user_login ul
|
||||||
|
INNER JOIN user u ON ul.user_id = u.user_id
|
||||||
|
WHERE ul.user_id = ?
|
||||||
|
ORDER BY ctime DESC", $tuid);
|
||||||
|
while($row = $verifyLoginCookieRes->fetch_assoc()){
|
||||||
|
if($row['expire'] == 0){ $row['expire'] = 1; }
|
||||||
|
if(
|
||||||
|
sha1($row['ckey']) == $tkey &&
|
||||||
|
$row['agent'] == $md5agent &&
|
||||||
|
($row['ctime']+($row['expire']*24*60*60) > time())
|
||||||
|
){
|
||||||
|
session_regenerate_id();
|
||||||
|
$_SESSION['user_id'] = $row['user_id'];
|
||||||
|
$_SESSION['user_name'] = $row['full_name'];
|
||||||
|
$_SESSION['user_agent'] = $md5agent;
|
||||||
|
$_SESSION['user_key'] = sha1($row['ckey']);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function logout(){}
|
||||||
|
|
||||||
|
|
||||||
|
public static function checkLogin($strict = false): bool {
|
||||||
|
$Auth = Auth::getInstance();
|
||||||
|
|
||||||
|
if(
|
||||||
|
($Auth->isLoggedIn && !$strict) ||
|
||||||
|
$Auth->checkSessionLogin() ||
|
||||||
|
$Auth->loginFromAuthKey()
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($_SESSION['user_id']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkSessionLogin(): bool {
|
||||||
|
if(isset($_SESSION['user_id'])){
|
||||||
|
$md5agent = md5($_SERVER['HTTP_USER_AGENT']);
|
||||||
|
if($md5agent == @$_SESSION['user_agent']){
|
||||||
|
$verifyLoginSessionRes = DB::query("
|
||||||
|
SELECT expire, ctime, ckey
|
||||||
|
FROM `user_login`
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND agent = ?",
|
||||||
|
$_SESSION['user_id'], $md5agent);
|
||||||
|
|
||||||
|
while ($row = $verifyLoginSessionRes->fetch_assoc()){
|
||||||
|
if(
|
||||||
|
(sha1($row['ckey']) == $_SESSION['user_key']) &&
|
||||||
|
($row['expire'] == 0 || $row['ctime']+($row['expire']*24*60*60) > time())
|
||||||
|
){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function PwdGen($pass, $returnHashed = false): string {
|
||||||
|
$pepper = Config::get('security', 'pepper', 'IAmBadAtSecurity');
|
||||||
|
$pwd_peppered = hash_hmac("sha256", $pass, $pepper);
|
||||||
|
if(!$returnHashed){
|
||||||
|
return $pwd_peppered;
|
||||||
|
}
|
||||||
|
|
||||||
|
return password_hash($pwd_peppered, PASSWORD_ARGON2ID, ['threads' => 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenKey($length = 21): string{
|
||||||
|
$password = "";
|
||||||
|
$possible = "0123456789abcdefghijkmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
|
||||||
|
while($i < $length){
|
||||||
|
$char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
|
||||||
|
|
||||||
|
if(!strstr($password, $char)){
|
||||||
|
$password .= $char;
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $password;
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class DB {
|
||||||
|
private static $instance = null;
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
private function __construct(){
|
||||||
|
$this->db = new mysqli(
|
||||||
|
Config::get('database', 'host'),
|
||||||
|
Config::get('database', "user"),
|
||||||
|
Config::get('database', "pass"),
|
||||||
|
Config::get('database', "database")
|
||||||
|
);
|
||||||
|
|
||||||
|
if($this->db->connect_error){
|
||||||
|
die("Connection failed: " . $this->db->connect_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance(): DB {
|
||||||
|
if(self::$instance == null){
|
||||||
|
self::$instance = new DB();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function query($sql, ...$params){
|
||||||
|
return self::doQuery(0, $sql, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function queryTest($sql, ...$params){
|
||||||
|
return self::doQuery(1, $sql, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function queryPreview($sql, ...$params){
|
||||||
|
return self::doQuery(2, $sql, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function doQuery($mode, $sql, ...$params){
|
||||||
|
/*
|
||||||
|
Modes:
|
||||||
|
0: Execute normally
|
||||||
|
1: Execute, but print query
|
||||||
|
2: Only print query
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check if query parameters comes as an array, or as individual arguments
|
||||||
|
if(count($params) == 1 && is_array($params[0])){
|
||||||
|
$params = $params[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create type-specification for query
|
||||||
|
$types = "";
|
||||||
|
foreach($params as &$value){
|
||||||
|
if(is_double($value)){
|
||||||
|
$types .= "d";
|
||||||
|
}
|
||||||
|
elseif(is_integer($value)){
|
||||||
|
$types .= "i";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DB::getInstance();
|
||||||
|
|
||||||
|
if($mode >= 1){
|
||||||
|
printf(str_replace('?', "'%s'", $sql)."\n", ...$params);
|
||||||
|
}
|
||||||
|
if($mode >= 2) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->db->prepare($sql);
|
||||||
|
$stmt->bind_param($types, ...$params);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $stmt->get_result();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
interface RequireAuth {}
|
||||||
|
|
||||||
|
class WebPage {
|
||||||
|
protected $httpStatus = 200;
|
||||||
|
protected $activePage;
|
||||||
|
public $status;
|
||||||
|
public $template;
|
||||||
|
public $title = "PaperBag";
|
||||||
|
public $lang = "EN";
|
||||||
|
public $navbar;
|
||||||
|
public $loggedIn = false;
|
||||||
|
public $pr = ""; // project root
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
$this->pr = Config::get('system', 'projectroot', '');
|
||||||
|
$this->loggedIn = Auth::checkLogin();
|
||||||
|
|
||||||
|
if($this instanceof RequireAuth && !$this->loggedIn) {
|
||||||
|
$returnTo = str_ireplace('index.php', '', $_SERVER['PHP_SELF']);
|
||||||
|
$_SESSION['pre-auth'] = $returnTo;
|
||||||
|
header("Location: ".$this->pr."/login.php");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->prepareNavbar();
|
||||||
|
$this->load();
|
||||||
|
|
||||||
|
if(!$this->template){
|
||||||
|
$this->template = str_replace('.php', '.html', $_SERVER['SCRIPT_NAME']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->httpStatus == 200){
|
||||||
|
global $twig;
|
||||||
|
|
||||||
|
if(DEBUG){
|
||||||
|
header("X-Template: ".$this->template);
|
||||||
|
header("X-Script-name: ".$_SERVER['SCRIPT_NAME']);
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
|
|
||||||
|
echo $twig->render($this->template, $this->vars());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
http_response_code($this->httpStatus);
|
||||||
|
print_r( $this->vars() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Twig\Error\LoaderError $e){
|
||||||
|
http_response_code(500);
|
||||||
|
if( $e->getCode() == 0){
|
||||||
|
print_r($e->getMessage());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echo $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(\Twig\Error\RuntimeError | \Twig\Error\SyntaxError | Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo $e;
|
||||||
|
print_r( $this->vars() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function load(){ throw new Exception("Incomplete implementation"); }
|
||||||
|
|
||||||
|
function vars(): array {
|
||||||
|
return (array) $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prepareNavbar(){
|
||||||
|
$this->activePage = str_replace('index.php', '', $_SERVER['REQUEST_URI']);
|
||||||
|
|
||||||
|
$this->navbar['links'] = array_merge([
|
||||||
|
["href"=>"/", "name"=>"Home", "active"=>$this->activePage=="/"?'active':''],
|
||||||
|
["href"=>"/plan/", "name"=>"Plan", "active"=>$this->activePage=="/plan/"?'active':''],
|
||||||
|
["href"=>"/review/", "name"=>"Review", "active"=>$this->activePage=="/review/"?'active':'']
|
||||||
|
], $this->navbar ?? []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebPageAuth extends WebPage {
|
||||||
|
public function __construct(){
|
||||||
|
if(Auth::checklogin()){
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"twbs/bootstrap": "5.1.3",
|
||||||
|
"twig/twig": "^3.3",
|
||||||
|
"ext-mysqli": "*"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,310 @@
|
||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "0daa3afffc94d70bd593d310cef916f8",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.25.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
|
||||||
|
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-ctype": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.23-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"ctype",
|
||||||
|
"polyfill",
|
||||||
|
"portable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-10-20T20:35:02+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-mbstring",
|
||||||
|
"version": "v1.25.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||||
|
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-mbstring": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.23-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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 the Mbstring extension",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"mbstring",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-11-30T18:21:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "twbs/bootstrap",
|
||||||
|
"version": "v5.1.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/twbs/bootstrap.git",
|
||||||
|
"reference": "1a6fdfae6be09b09eaced8f0e442ca6f7680a61e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/twbs/bootstrap/zipball/1a6fdfae6be09b09eaced8f0e442ca6f7680a61e",
|
||||||
|
"reference": "1a6fdfae6be09b09eaced8f0e442ca6f7680a61e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"twitter/bootstrap": "self.version"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mark Otto",
|
||||||
|
"email": "markdotto@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jacob Thornton",
|
||||||
|
"email": "jacobthornton@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
|
||||||
|
"homepage": "https://getbootstrap.com/",
|
||||||
|
"keywords": [
|
||||||
|
"JS",
|
||||||
|
"css",
|
||||||
|
"framework",
|
||||||
|
"front-end",
|
||||||
|
"mobile-first",
|
||||||
|
"responsive",
|
||||||
|
"sass",
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/twbs/bootstrap/issues",
|
||||||
|
"source": "https://github.com/twbs/bootstrap/tree/v5.1.3"
|
||||||
|
},
|
||||||
|
"time": "2021-10-09T06:43:19+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "twig/twig",
|
||||||
|
"version": "v3.3.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
|
"reference": "8442df056c51b706793adf80a9fd363406dd3674"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/8442df056c51b706793adf80a9fd363406dd3674",
|
||||||
|
"reference": "8442df056c51b706793adf80a9fd363406dd3674",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"psr/container": "^1.0",
|
||||||
|
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.3-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Twig\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com",
|
||||||
|
"homepage": "http://fabien.potencier.org",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Twig Team",
|
||||||
|
"role": "Contributors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armin Ronacher",
|
||||||
|
"email": "armin.ronacher@active-4.com",
|
||||||
|
"role": "Project Founder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||||
|
"homepage": "https://twig.symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"templating"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
|
"source": "https://github.com/twigphp/Twig/tree/v3.3.10"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-04-06T06:47:41+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.2.0"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace models;
|
||||||
|
|
||||||
|
abstract class Model {
|
||||||
|
|
||||||
|
|
||||||
|
// public function set($value, )
|
||||||
|
|
||||||
|
public function save(){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace models;
|
||||||
|
|
||||||
|
class Recipe extends Model {
|
||||||
|
|
||||||
|
public static function getRecipes() : array {
|
||||||
|
$db = $db ?? database();
|
||||||
|
|
||||||
|
$recipes = [];
|
||||||
|
|
||||||
|
$result = $db->query("SELECT * FROM recipe");
|
||||||
|
while($row = $result->fetch_assoc()){
|
||||||
|
$recipe = new Recipe();
|
||||||
|
$recipe->
|
||||||
|
|
||||||
|
$recipes[] = $recipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $recipes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'].'/webdata/init.php';
|
||||||
|
|
||||||
|
include $_SERVER['DOCUMENT_ROOT']."/api/v1/products.php";
|
|
@ -0,0 +1,92 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ language|default('en') }}">
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||||
|
<title>{{ title|default('PaperBag') }}</title>
|
||||||
|
|
||||||
|
<link rel='stylesheet' href='{{ pr }}/css/bootstrap.min.css' type='text/css' />
|
||||||
|
<link rel='stylesheet' href='{{ pr }}/css/index.css' type='text/css' />
|
||||||
|
|
||||||
|
<link rel='apple-touch-icon' sizes='180x180' href='{{ pr }}/apple-touch-icon.png'>
|
||||||
|
<link rel='icon' type='image/png' sizes='32x32' href='{{ pr }}/favicon-32x32.png'>
|
||||||
|
<link rel='icon' type='image/png' sizes='16x16' href='{{ pr }}/favicon-16x16.png'>
|
||||||
|
<link rel='manifest' href='{{ pr }}/site.webmanifest'>
|
||||||
|
<link rel='mask-icon' href='{{ pr }}/favicon/safari-pinned-tab.svg' color='#5bbad5'>
|
||||||
|
<meta name='msapplication-TileColor' content='#da532c'>
|
||||||
|
<meta name='theme-color' content='#ffffff'>
|
||||||
|
|
||||||
|
<script src='{{ pr }}/js/jquery-3.5.1.min.js'></script>
|
||||||
|
<script src='{{ pr }}/js/popper.min.js'></script>
|
||||||
|
<script src='{{ pr }}/js/bootstrap.min.js'></script>
|
||||||
|
<!--<script src='//cdn.jsdelivr.net/npm/marked/marked.min.js'></script>-->
|
||||||
|
<script src='{{ pr }}/js/hammer.js'></script>
|
||||||
|
<script src='{{ pr }}/js/hammer.jquery.js'></script>
|
||||||
|
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body id="{{ pagekey|default('') }}">
|
||||||
|
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="page-wrapper">
|
||||||
|
<nav class="navbar navbar-expand-sm navbar-light bg-light" style="z-index: 2;">
|
||||||
|
<div class="container-fluid" style="max-width: 900px;">
|
||||||
|
<a class="navbar-brand" href="{{ pr }}/"><img src='{{ pr }}/paperbag_small.svg' style="height: 24px;" alt='Logo' /> PaperBag</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-sm-0">
|
||||||
|
{% for link in navbar.links %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ link.active }}" aria-current="page" href="{{ link.href }}">{{ link.name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<div class="navbar-nav" id="navLogin">
|
||||||
|
{% if loggedIn %}
|
||||||
|
<a class='nav-item nav-link' href='{{ pr }}/logout.php'>Log out</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="nav-item nav-link" id='login' href='{{ pr }}/login.php'>Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% if err or msg %}
|
||||||
|
<div class='container-sm' style="max-width: 540px;">
|
||||||
|
{% for e in err %}
|
||||||
|
<div class='alert alert-danger' role='alert'>{{ e }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for m in msg %}
|
||||||
|
<div class='alert alert-success' role='alert'>{{ m }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
Any prices you find on this website is added by its users and are only a guideline.<br>
|
||||||
|
<strong>PaperBag</strong> is a <a href="//svagard.no/projects/paperbag" target="_blank" rel="nofollow">Svagård</a> project<br>
|
||||||
|
|
||||||
|
{#
|
||||||
|
<!--<a href="https://useg.it/eirik/GroceryAssist" target="_blank" rel="nofollow">View source</a> |
|
||||||
|
<a href="/cookies" rel="nofollow">Cookies</a> |
|
||||||
|
<a href="/tos" rel="nofollow">Terms of Use</a> |
|
||||||
|
<a href="/privacy" rel="nofollow">Privacy Policy</a>-->
|
||||||
|
#}
|
||||||
|
{% block footer %}{% endblock %}
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,40 @@
|
||||||
|
{% extends "assets/base.html.twig" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="headline text-center"><img src='paperbag_small.svg' style="width: 4rem;" alt='Logo' /> PaperBag</h1>
|
||||||
|
<div class="container-sm" style="padding-top: 5px;">
|
||||||
|
|
||||||
|
<div class="card-group" style="text-align: center;">
|
||||||
|
|
||||||
|
<div class="card h-100" style="">
|
||||||
|
<a href='./plan'>
|
||||||
|
<!-- <img src="#" class="card-img-top" alt="Plan">-->
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Plan shopping</h5>
|
||||||
|
<p class="card-text"></p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card h-100" style="">
|
||||||
|
<!-- <img src="#" class="card-img-top" alt="Review">-->
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Review</h5>
|
||||||
|
<p class="card-text">Coming soon</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-group" style="text-align: center;">
|
||||||
|
<div class="card" style="">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Recent changes</h5>
|
||||||
|
<pre class="card-text" style="text-align: left; width: min-content; max-width: 100%; margin: auto; max-height: 150px; overflow-y: auto; padding: 0 15px;">{{ changes }}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">For more details see the <a href="https://useg.it/eirik/PaperBag/commits/branch/master">commits</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends "assets/base.html.twig" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class='container-sm' style="max-width: 540px;">
|
||||||
|
|
||||||
|
<h1 class="headline text-center">Login</h1>
|
||||||
|
|
||||||
|
<form action="login.php" method="POST">
|
||||||
|
<div class="form-group row mb-1">
|
||||||
|
<label for="loginEmail" class="col-sm-2 col-form-label">Email</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="email" class="form-control" name="loginEmail" id="loginEmail">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="loginPwd" class="col-sm-2 col-form-label">Password</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="password" class="form-control" name="loginPwd" id="loginPwd">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-check" style="margin: 3px 0;">
|
||||||
|
<input class="form-check-input" type="checkbox" id="gridCheck">
|
||||||
|
<label class="form-check-label" for="gridCheck">
|
||||||
|
I agree to save a temporary cookie in my browser for the logged in functions to work.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check" style="margin: 3px 0;">
|
||||||
|
<input class="form-check-input" type="checkbox" id="rememberCheck" name="stayLoggedIn">
|
||||||
|
<label class="form-check-label" for="rememberCheck">
|
||||||
|
Remember me (for 30 days)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mb-3 w-100">Login</button>
|
||||||
|
</form>
|
||||||
|
<p>Do you not have an account? <a href="register.php">Register</a> </p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,82 @@
|
||||||
|
{% extends "assets/base.html.twig" %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<!-- jsDelivr :: Sortable :: Latest (https://www.jsdelivr.com/package/npm/sortablejs) -->
|
||||||
|
<script src="/js/sortable/Sortable.min.js"></script> <!-- CDN: https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js -->
|
||||||
|
<script src="/js/sortable/jquery-sortable.js"></script> <!-- CDN: https://cdn.jsdelivr.net/npm/jquery-sortablejs@latest/jquery-sortable.js -->
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="headline text-center">PaperBag</h1>
|
||||||
|
<div class="container" style="padding-top: 5px; padding-bottom: 15px; text-align: center;">
|
||||||
|
|
||||||
|
<div id="spaceSelectWrapper" style="max-width: 900px; margin: auto;">
|
||||||
|
<div class="input-group" style="width: 200px;">
|
||||||
|
<label class="input-group-text" for="spaceSelect">Space:</label>
|
||||||
|
<select class="form-select" id='spaceSelect' aria-label="Select space">
|
||||||
|
<option>...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-columns" id="stores" style="text-align: center;">
|
||||||
|
<noscript>
|
||||||
|
<hr>
|
||||||
|
<h5>Please enable javascript for this page to work</h5>
|
||||||
|
<hr>
|
||||||
|
</noscript>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='totalPriceWrapper'>Space subtotal: <span id="totalPrice" class="priceWrapper price">00.00</span></div>
|
||||||
|
<br>
|
||||||
|
<button class="btn btn-primary mb-2" id="addStore">Add store</button><br>
|
||||||
|
<button class="btn btn-secondary" id="refreshAll">Refresh all</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src='htmlElements.js'></script>
|
||||||
|
<script src='plan.js'></script>
|
||||||
|
<script src='recipe.js'></script>
|
||||||
|
<script src='product.js'></script>
|
||||||
|
|
||||||
|
<div class="modal" tabindex="-1" id="addStoreModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add store</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body align-content-center">
|
||||||
|
<button class="btn btn-primary" id="addEmptyStore">Empty store</button>
|
||||||
|
<hr>
|
||||||
|
<div class="accordion mb-2" id="addStoreRecipe"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let addStoreModal = new bootstrap.Modal( document.getElementById('addStoreModal') );
|
||||||
|
|
||||||
|
$("#addStore").on('click', ev=>{
|
||||||
|
ev.stopPropagation();
|
||||||
|
addStoreModal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#addStoreModal").on('show.bs.modal', ev=> {
|
||||||
|
$("#addStoreRecipe").html('');
|
||||||
|
|
||||||
|
$.getJSON('/api/v1/recipe', {}, resp => {
|
||||||
|
let recipes = new Recipe(resp);
|
||||||
|
recipes.getAccordionHtml('#addStoreRecipe');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#addEmptyStore").on('click', ev => {
|
||||||
|
stores.push(new Store());
|
||||||
|
addStoreModal.hide();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,17 @@
|
||||||
<?php require 'webdata/init.php'; ?><!DOCTYPE html>
|
<?php
|
||||||
<html lang="en">
|
require_once '../Router.php';
|
||||||
<head>
|
|
||||||
<?=getHtmlHeaders();?>
|
|
||||||
<title>PaperBag - Plan & Execute Your Shopping</title>
|
|
||||||
</head>
|
|
||||||
<body id="home">
|
|
||||||
<div id="page-container">
|
|
||||||
<div id="page-wrapper">
|
|
||||||
|
|
||||||
<?php include 'webdata/navbar.php'; ?>
|
class HomePage extends WebPage {
|
||||||
|
public $pagekey = "home";
|
||||||
|
public $title = "PaperBag - Plan & Execute Your Shopping";
|
||||||
|
|
||||||
<h1 class="headline text-center"><img src='paperbag_small.svg' style="height: 4rem;" alt='Logo' /> PaperBag</h1>
|
function load(){
|
||||||
<div class="container-sm" style="padding-top: 5px;">
|
|
||||||
|
|
||||||
<div class="card-group" style="text-align: center;">
|
if (file_exists('changes.txt')) {
|
||||||
|
$this->changes = file_get_contents('changes.txt');
|
||||||
|
}
|
||||||
|
|
||||||
<div class="card h-100" style="">
|
}
|
||||||
<a href='./plan'>
|
}
|
||||||
<!-- <img src="#" class="card-img-top" alt="Plan">-->
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Plan shopping</h5>
|
|
||||||
<p class="card-text"></p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card h-100" style="">
|
$a = new HomePage();
|
||||||
<!-- <img src="#" class="card-img-top" alt="Review">-->
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Review</h5>
|
|
||||||
<p class="card-text">Coming soon</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<?php if(file_exists('changes.txt')){
|
|
||||||
$changes = file_get_contents('changes.txt');
|
|
||||||
?>
|
|
||||||
<div class="card-group" style="text-align: center;">
|
|
||||||
<div class="card" style="">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Recent changes</h5>
|
|
||||||
<pre class="card-text" style="text-align: left; width: min-content; max-width: 100%; margin: auto; max-height: 150px; overflow-y: auto; padding: 0 15px;"><?=$changes;?></pre>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">For more details see the <a href="https://useg.it/eirik/PaperBag/commits/branch/master">commits</a></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php include 'webdata/footer.html'; ?>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
148
www/login.php
148
www/login.php
|
@ -1,7 +1,55 @@
|
||||||
<?php
|
<?php
|
||||||
require 'webdata/init.php';
|
|
||||||
|
|
||||||
$returnToPage = "./";
|
require_once '../Router.php';
|
||||||
|
|
||||||
|
class LoginPage extends WebPage {
|
||||||
|
public $pagekey = "login";
|
||||||
|
public $title = "PaperBag - Plan & Execute Your Shopping";
|
||||||
|
|
||||||
|
function load(){
|
||||||
|
$returnToPage = $_POST['referrerPage'] ?? $_SESSION['pre-auth'] ?? explode($_SERVER['HTTP_HOST'], $_SERVER['HTTP_REFERER'])[1];
|
||||||
|
|
||||||
|
// TODO: check if returntopage is an auth-changing page (e.g: login / register, and if so don't redirect there!
|
||||||
|
|
||||||
|
$this->msg[] = $returnToPage;
|
||||||
|
|
||||||
|
if(Auth::checkLogin(true)){
|
||||||
|
header("Location: ".$returnToPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($_GET['thank'])){
|
||||||
|
$this->msg[] = "Thank you for registering. Please log in to continue!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($_POST) && !empty($_POST)){
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
foreach($_POST as $key => $value){
|
||||||
|
if(($data[$key] = filter($value)) === false){
|
||||||
|
print_r($value);
|
||||||
|
echo "Failed to sanitize: `".$key."`: ".$value." \t-\t type: ".gettype($value)."\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stayLoggedIn = isset($_POST['stayLoggedIn']);
|
||||||
|
$err = Auth::loginWithCredentials($data['loginEmail'], $data['loginPwd'], $stayLoggedIn);
|
||||||
|
if($err === true){
|
||||||
|
header("Location: ".$returnToPage);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$a = new LoginPage();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//require 'webdata/init.php';
|
||||||
|
|
||||||
|
/*$returnToPage = "./";
|
||||||
if(isset($_GET['return'])){
|
if(isset($_GET['return'])){
|
||||||
$returnToPage = $_GET['return'];
|
$returnToPage = $_GET['return'];
|
||||||
}
|
}
|
||||||
|
@ -10,103 +58,17 @@ elseif(isset($_POST['referrerPage'])){
|
||||||
}
|
}
|
||||||
elseif(isset($_SERVER['HTTP_REFERER'])){
|
elseif(isset($_SERVER['HTTP_REFERER'])){
|
||||||
$returnToPage = explode($_SERVER['HTTP_HOST'], $_SERVER['HTTP_REFERER'])[1];
|
$returnToPage = explode($_SERVER['HTTP_HOST'], $_SERVER['HTTP_REFERER'])[1];
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
if(stristr($returnToPage, "login.php") || stristr($returnToPage, "register.php")){
|
/*if(stristr($returnToPage, "login.php") || stristr($returnToPage, "register.php")){
|
||||||
$returnToPage = "./";
|
$returnToPage = "./";
|
||||||
} else {
|
} else {
|
||||||
$returnToPage = str_ireplace('index.php', '', $returnToPage);
|
$returnToPage = str_ireplace('index.php', '', $returnToPage);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
if( checkLogin() ){
|
/*if( checkLogin() ){
|
||||||
header("Location: ".$returnToPage);
|
header("Location: ".$returnToPage);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
$msg = [];
|
|
||||||
if(isset($_GET['thank'])){
|
|
||||||
$msg[] = "Thank you for registering. Please log in to continue!";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isset($_POST) && !empty($_POST)){
|
|
||||||
$db = database();
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
foreach($_POST as $key => $value){
|
|
||||||
if(($data[$key] = filter($value)) === false){
|
|
||||||
print_r($value);
|
|
||||||
echo "Failed to sanitize: `".$key."`: ".$value." \t-\t type: ".gettype($value)."\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$stayLoggedIn = isset($_POST['stayLoggedIn']);
|
|
||||||
$err = loginUser($data['loginEmail'], $data['loginPwd'], $stayLoggedIn);
|
|
||||||
if($err === true){
|
|
||||||
header("Location: ".$returnToPage);
|
|
||||||
die();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
?><!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<?=getHtmlHeaders();?>
|
|
||||||
<title>Login - PaperBag</title>
|
|
||||||
</head>
|
|
||||||
<body id='plan'>
|
|
||||||
<div id="page-container">
|
|
||||||
<div id="page-wrapper">
|
|
||||||
<?php include 'webdata/navbar.php'; ?>
|
|
||||||
|
|
||||||
<div class='container-sm' style="max-width: 540px;">
|
|
||||||
|
|
||||||
<h1 class="headline text-center">Login</h1>
|
|
||||||
|
|
||||||
<?php if(!empty($err)){
|
|
||||||
foreach($err as $e){
|
|
||||||
echo "<div class='alert alert-danger' role='alert'>$e</div>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!empty($msg)){
|
|
||||||
foreach($msg as $m){
|
|
||||||
echo "<div class='alert alert-success' role='alert'>$m</div>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|
||||||
<form action="login.php" method="POST">
|
|
||||||
<input type="hidden" name="referrerPage" value="<?=$returnToPage;?>">
|
|
||||||
<div class="form-group row mb-1">
|
|
||||||
<label for="loginEmail" class="col-sm-2 col-form-label">Email</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="email" class="form-control" name="loginEmail" id="loginEmail">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="loginPwd" class="col-sm-2 col-form-label">Password</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="password" class="form-control" name="loginPwd" id="loginPwd">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-check" style="margin: 3px 0;">
|
|
||||||
<input class="form-check-input" type="checkbox" id="gridCheck">
|
|
||||||
<label class="form-check-label" for="gridCheck">
|
|
||||||
I agree to save a temporary cookie in my browser for the logged in functions to work.
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check" style="margin: 3px 0;">
|
|
||||||
<input class="form-check-input" type="checkbox" id="rememberCheck" name="stayLoggedIn">
|
|
||||||
<label class="form-check-label" for="rememberCheck">
|
|
||||||
Remember me (for 30 days)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary mb-3 w-100">Login</button>
|
|
||||||
</form>
|
|
||||||
<p>Do you not have an account? <a href="register.php">Register</a> </p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php include 'webdata/footer.html'; ?>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,88 +1,12 @@
|
||||||
<?php $rPath = "../"; require $rPath.'webdata/init.php'; requireLogin(); ?><!DOCTYPE html>
|
<?php
|
||||||
<html lang="en">
|
require_once '../../Router.php';
|
||||||
<head>
|
|
||||||
<?=getHtmlHeaders($rPath);?>
|
|
||||||
<title>Plan - PaperBag - Plan Your Shopping</title>
|
|
||||||
|
|
||||||
<!-- jsDelivr :: Sortable :: Latest (https://www.jsdelivr.com/package/npm/sortablejs) -->
|
class PlanPage extends WebPage implements RequireAuth {
|
||||||
<script src="/js/sortable/Sortable.min.js"></script> <!-- CDN: https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js -->
|
public $pagekey = "plan";
|
||||||
<script src="/js/sortable/jquery-sortable.js"></script> <!-- CDN: https://cdn.jsdelivr.net/npm/jquery-sortablejs@latest/jquery-sortable.js -->
|
public $title = "Plan - PaperBag - Plan & Execute Your Shopping";
|
||||||
</head>
|
|
||||||
<body id='plan'>
|
|
||||||
<div id="page-container">
|
|
||||||
<div id="page-wrapper">
|
|
||||||
<?php include $rPath.'webdata/navbar.php'; ?>
|
|
||||||
<h1 class="headline text-center">PaperBag</h1>
|
|
||||||
<div class="container" style="padding-top: 5px; padding-bottom: 15px; text-align: center;">
|
|
||||||
|
|
||||||
<div id="spaceSelectWrapper" style="max-width: 900px; margin: auto;">
|
function load(){
|
||||||
<div class="input-group" style="width: 200px;">
|
}
|
||||||
<label class="input-group-text" for="spaceSelect">Space:</label>
|
}
|
||||||
<select class="form-select" id='spaceSelect' aria-label="Select space">
|
|
||||||
<option>...</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-columns" id="stores" style="text-align: center;">
|
$a = new PlanPage();
|
||||||
<hr>
|
|
||||||
<h5>Please enable javascript for this page to work</h5>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id='totalPriceWrapper'>Space subtotal: <span id="totalPrice" class="priceWrapper price">00.00</span></div>
|
|
||||||
<br>
|
|
||||||
<button class="btn btn-primary mb-2" id="addStore">Add store</button><br>
|
|
||||||
<button class="btn btn-secondary" id="refreshAll">Refresh all</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src='htmlElements.js'></script>
|
|
||||||
<script src='plan.js'></script>
|
|
||||||
<script src='recipe.js'></script>
|
|
||||||
<script src='product.js'></script>
|
|
||||||
<!-- <script src='draggingClass.js'></script>-->
|
|
||||||
</div>
|
|
||||||
<?php include $rPath.'webdata/footer.html'; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal" tabindex="-1" id="addStoreModal">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Add store</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body align-content-center">
|
|
||||||
<button class="btn btn-primary" id="addEmptyStore">Empty store</button>
|
|
||||||
<hr>
|
|
||||||
<div class="accordion mb-2" id="addStoreRecipe"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
let addStoreModal = new bootstrap.Modal( document.getElementById('addStoreModal') );
|
|
||||||
|
|
||||||
$("#addStore").on('click', ev=>{
|
|
||||||
ev.stopPropagation();
|
|
||||||
addStoreModal.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#addStoreModal").on('show.bs.modal', ev=> {
|
|
||||||
$("#addStoreRecipe").html('');
|
|
||||||
|
|
||||||
$.getJSON('/api/v1/recipe', {}, resp => {
|
|
||||||
let recipes = new Recipe(resp);
|
|
||||||
recipes.getAccordionHtml('#addStoreRecipe');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#addEmptyStore").on('click', ev => {
|
|
||||||
stores.push(new Store());
|
|
||||||
addStoreModal.hide();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -186,7 +186,7 @@ function logoutUser($everywhere = false){
|
||||||
session_destroy();
|
session_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
function PwdGen($pass, $returnHashed = false): string {
|
/*function PwdGen($pass, $returnHashed = false): string {
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$pepper = $config['general']['pepper'] ?? 'IAmBadAtSecurity';
|
$pepper = $config['general']['pepper'] ?? 'IAmBadAtSecurity';
|
||||||
|
@ -196,7 +196,7 @@ function PwdGen($pass, $returnHashed = false): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
return password_hash($pwd_peppered, PASSWORD_ARGON2ID, ['threads' => 2]);
|
return password_hash($pwd_peppered, PASSWORD_ARGON2ID, ['threads' => 2]);
|
||||||
}
|
}* /
|
||||||
|
|
||||||
function GenKey($length = 21): string{
|
function GenKey($length = 21): string{
|
||||||
$password = "";
|
$password = "";
|
||||||
|
@ -215,7 +215,7 @@ function GenKey($length = 21): string{
|
||||||
}
|
}
|
||||||
|
|
||||||
return $password;
|
return $password;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
function checkLogin(): bool {
|
function checkLogin(): bool {
|
||||||
global $db, $_SESSION, $_COOKIE;
|
global $db, $_SESSION, $_COOKIE;
|
||||||
|
|
Loading…
Reference in New Issue