Object-oriented authentication, and WIP translations
parent
b5fb90d290
commit
c33f8004e6
29
Router.php
29
Router.php
|
@ -16,11 +16,14 @@ 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';
|
||||
if (file_exists(__DIR__ . "/application/" . $class_name . '.php')) {
|
||||
include __DIR__ . "/application/" . $class_name . '.php';
|
||||
// echo "Load class " . __DIR__ . "/application/" . $class_name . '.php' . "<br>\n";
|
||||
}
|
||||
elseif(file_exists(__DIR__."/models/".$class_name.'.php')) {
|
||||
include __DIR__."/models/".$class_name.'.php';
|
||||
elseif(file_exists(__DIR__ . "/models/" . $class_name . ".php")) {
|
||||
include_once __DIR__."/models/Model.php";
|
||||
include_once __DIR__ . "/models/" . $class_name . ".php";
|
||||
// echo "Load class ".__DIR__."/models/".$class_name.'.php'."<br>\n";
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -31,6 +34,12 @@ $twigSettings = PRODUCTION?['cache' => __dir__.'/tmp/']:[];
|
|||
$loader = new Twig\Loader\FilesystemLoader(__dir__ . '/templates/'.Config::get('system', 'template', 'default')."/");
|
||||
$twig = new Twig\Environment($loader, $twigSettings);
|
||||
|
||||
$filter = new Twig\TwigFilter('t', function ($string) {
|
||||
return _(strip_tags($string));
|
||||
});
|
||||
|
||||
$twig->addFilter($filter);
|
||||
|
||||
|
||||
class Config {
|
||||
private static $instance = null;
|
||||
|
@ -53,4 +62,16 @@ class Config {
|
|||
}
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
function model($name){
|
||||
return;
|
||||
if(file_exists(__DIR__ . "/models/" . $name . ".php")){
|
||||
include_once __DIR__ . "/models/Model.php";
|
||||
include_once __DIR__ . "/models/" . $name . ".php";
|
||||
}
|
||||
else {
|
||||
echo "FAULT<br>\n";
|
||||
echo __DIR__ . "/models/" . $name . ".php". " doesn't exist";
|
||||
}
|
||||
}
|
|
@ -17,10 +17,21 @@ class Auth {
|
|||
}
|
||||
|
||||
public static function loginWithCredentials($email, $pass, bool $stayLoggedIn = false){
|
||||
$Auth = new Auth();
|
||||
session_destroy();
|
||||
self::$instance = null;
|
||||
$Auth = Auth::getInstance();
|
||||
|
||||
$err = [];
|
||||
|
||||
if(strlen($email) < 3){
|
||||
$err[] = __("Please enter a valid email.");
|
||||
}
|
||||
if(strlen($pass) < 3){
|
||||
$err[] = __("Please enter a valid password. Hint: The passwords need to have at least 6 characters.");
|
||||
}
|
||||
|
||||
if(!empty($err)){ return $err; }
|
||||
|
||||
// get user from database
|
||||
$getUserSQL = "SELECT pwd, user_id, user_email FROM user WHERE user_email = ? LIMIT 1;";
|
||||
$getUserRes = DB::query($getUserSQL, $email);
|
||||
|
@ -41,14 +52,16 @@ class Auth {
|
|||
$userKey = GenKey();
|
||||
$shaUserKey = sha1($userKey);
|
||||
$expire = $stayLoggedIn?30:0; // if "stay logged in", stay logged in for 30 days.
|
||||
$ctime = time();
|
||||
|
||||
$updateUserSQL = "INSERT INTO user_login (user_id, ckey, ctime, expire, agent) VALUES (?, ?, ?, ?, ?);";
|
||||
try {
|
||||
DB::query($updateUserSQL, $dbUserId, $userKey, time(), $expire, $md5agent);
|
||||
DB::query($updateUserSQL, $dbUserId, $userKey, $ctime, $expire, $md5agent);
|
||||
$_SESSION['user_id'] = $dbUserId;
|
||||
$_SESSION['user_name'] = $dbUserName;
|
||||
$_SESSION['user_agent'] = $md5agent;
|
||||
$_SESSION['user_key'] = $shaUserKey;
|
||||
$_SESSION['user_ctime'] = $ctime;
|
||||
|
||||
if($stayLoggedIn){
|
||||
setcookie("auth", $shaUserKey.".".$dbUserId, time() + (30 * 24*60*60), "/", $_SERVER['HTTP_HOST'], isset($_SERVER['HTTPS']), true );
|
||||
|
@ -56,11 +69,11 @@ class Auth {
|
|||
return true;
|
||||
}
|
||||
catch(DatabaseException $e){
|
||||
$err[] = "Failed to login.\n".$e;
|
||||
$err[] = __("Failed to login.")."\n".$e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$err[] = "Username and/or Password is wrong.";
|
||||
$err[] = __("The provided combination of username and password is not recognized in the system. Verify your details or create an account.");
|
||||
}
|
||||
|
||||
return $err;
|
||||
|
@ -102,8 +115,76 @@ class Auth {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static function logout(){}
|
||||
public static function logout($everywhere = false){
|
||||
if(Auth::checkLogin()){
|
||||
$logoutSql = "DELETE FROM user_login WHERE user_id = ?";
|
||||
$sqlparams[] = $_SESSION['user_id'];
|
||||
if(!$everywhere){
|
||||
// $logoutSql .= " AND agent = ?";
|
||||
// $sqlparams[] = md5($_SERVER['HTTP_USER_AGENT']);
|
||||
$logoutSql .= " AND ctime = ?";
|
||||
$sqlparams[] = $_SESSION['user_ctime'];
|
||||
}
|
||||
|
||||
DB::query($logoutSql, $sqlparams);
|
||||
}
|
||||
|
||||
// Delete session-cookie
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000,
|
||||
$params["path"], $params["domain"],
|
||||
$params["secure"], $params["httponly"]
|
||||
);
|
||||
|
||||
setcookie('auth', '', time() - 42000, "/", $_SERVER['HTTP_HOST'], isset($_SERVER['HTTPS']), true );
|
||||
|
||||
// Finally, destroy the session.
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
|
||||
public static function register($userEmail, $userPass, $metadata = []){
|
||||
$err = [];
|
||||
|
||||
$userName = $metadata['newName'] ?? '';
|
||||
|
||||
if(strlen($userPass) < 6){
|
||||
$err[] = __("Password is too short. Password needs to be at least 6 characters!");
|
||||
}
|
||||
else if(strlen($userPass) > 30){
|
||||
$err[] = __("Password is too long. Max length is set to 30 characters. If you believe it should be higher please contact the developers.");
|
||||
}
|
||||
|
||||
if(strlen($userEmail) > 220){
|
||||
$err[] = __("Your email-address is too long. Can you please register with another email?");
|
||||
}
|
||||
|
||||
if(strlen($userName) > 200){
|
||||
$err[] = __("Your name seems to be too long to fit this system. Maybe you can short it down somehow?");
|
||||
}
|
||||
|
||||
if(empty($err)){
|
||||
// HASH PASSWORD
|
||||
$newPass = PwdGen($userPass, true);
|
||||
|
||||
$createUserSQL = "INSERT INTO user SET user_email = ?, pwd = ?, full_name = ?;";
|
||||
try {
|
||||
DB::query($createUserSQL, $userEmail, $newPass, $userName);
|
||||
$newID = DB::insert_id();
|
||||
|
||||
if(empty($newID)){ return false; }
|
||||
|
||||
$updateUserSQL = "UPDATE user SET md5_id = ? WHERE user_id = ?;";
|
||||
DB::query($updateUserSQL, md5($newID), $newID);
|
||||
|
||||
return true;
|
||||
} catch (DatabaseException $e) {
|
||||
$err[] = __("Something went wrong").":<br>" . $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $err;
|
||||
}
|
||||
|
||||
public static function checkLogin($strict = false): bool {
|
||||
$Auth = Auth::getInstance();
|
||||
|
|
|
@ -4,16 +4,24 @@ class DB {
|
|||
private static $instance = null;
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
private function __construct(){
|
||||
$this->db = new mysqli(
|
||||
Config::get('database', 'host'),
|
||||
Config::get('database', "user"),
|
||||
Config::get('database', "pass"),
|
||||
Config::get('database', "database")
|
||||
);
|
||||
try {
|
||||
$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);
|
||||
if($this->db->connect_error){
|
||||
throw new DatabaseException("Database connection failed: ". $this->db->connect_error);
|
||||
}
|
||||
}
|
||||
catch (mysqli_sql_exception $e){
|
||||
throw new DatabaseException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,16 +60,12 @@ class DB {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param integer $mode 0: Execute normally - 1: Execute, but print query - 2: Only print query
|
||||
* @param string $sql Input SQL as string
|
||||
* @param mixed ...$params A list or an array of values for the query
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
private static function doQuery($mode, $sql, ...$params){
|
||||
/*
|
||||
Modes:
|
||||
0: Execute normally
|
||||
1: Execute, but print query
|
||||
2: Only print query
|
||||
*/
|
||||
|
||||
private static function doQuery(int $mode, string $sql, ...$params){
|
||||
// Check if query parameters comes as an array, or as individual arguments
|
||||
if(count($params) == 1 && is_array($params[0])){
|
||||
$params = $params[0];
|
||||
|
@ -69,7 +73,7 @@ class DB {
|
|||
|
||||
// Create type-specification for query
|
||||
$types = "";
|
||||
foreach($params as &$value){
|
||||
foreach($params as $value){
|
||||
if(is_double($value)){
|
||||
$types .= "d";
|
||||
}
|
||||
|
@ -90,17 +94,44 @@ class DB {
|
|||
return [];
|
||||
}
|
||||
|
||||
$stmt = $db->db->prepare($sql);
|
||||
if(
|
||||
$stmt === false ||
|
||||
$stmt->bind_param($types, ...$params) === false ||
|
||||
$stmt->execute() === false
|
||||
){
|
||||
throw new DatabaseException($db->db->error);
|
||||
try {
|
||||
$stmt = $db->db->prepare($sql);
|
||||
if(
|
||||
$stmt === false ||
|
||||
(!empty($params) && $stmt->bind_param($types, ...$params) === false) ||
|
||||
$stmt->execute() === false
|
||||
){
|
||||
throw new DatabaseException($db->db->error);
|
||||
}
|
||||
}
|
||||
catch (mysqli_sql_exception $e){
|
||||
throw new DatabaseException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
return $stmt->get_result();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public static function multiQuery(...$sql){
|
||||
$db = DB::getInstance();
|
||||
|
||||
foreach ($sql as $s){
|
||||
try {
|
||||
$db->db->multi_query($s);
|
||||
}
|
||||
catch (mysqli_sql_exception $e){
|
||||
throw new DatabaseException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function insert_id(){
|
||||
$db = DB::getInstance();
|
||||
|
||||
return $db->db->insert_id;
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseException extends Exception {
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
class Translator {
|
||||
public function __construct(){
|
||||
$this->language = "nb";
|
||||
$this->country = "NO";
|
||||
$this->charset = "utf8";
|
||||
|
||||
$this->locale = $this->language."_".$this->country.".".$this->charset;
|
||||
|
||||
if (defined('LC_MESSAGES')) {
|
||||
setlocale(LC_MESSAGES, $this->locale);
|
||||
bindtextdomain("domain1", __dir__."/../locale/");
|
||||
} else {
|
||||
echo "IS IN THE ELSE - PART";
|
||||
}
|
||||
|
||||
textdomain("domain1");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
use Gettext\Loader\PoLoader;
|
||||
|
||||
class Translator {
|
||||
private static $instance = null;
|
||||
private $translations;
|
||||
private $language;
|
||||
private $country;
|
||||
private $charset;
|
||||
private $locale;
|
||||
|
||||
private function __construct($lang = "nb", $contry = "NO", $charset = "utf8"){
|
||||
|
||||
$this->language = $lang;
|
||||
$this->country = $contry;
|
||||
$this->charset = $charset;
|
||||
$this->locale = $this->language."_".$this->country.".".$this->charset;
|
||||
|
||||
//import from a .po file:
|
||||
$poloader = new PoLoader();
|
||||
$this->translations = $poloader->loadFile('locale/'.$this->language.'.po');
|
||||
}
|
||||
|
||||
public static function getInstance(): Translator {
|
||||
if(self::$instance == null){
|
||||
self::$instance = new Translator();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
static public function translate($string, $domain = "messages"): string {
|
||||
$trans = self::getInstance();
|
||||
|
||||
return $trans->translations;
|
||||
}
|
||||
|
||||
static public function getBrowserSupportedLanguage(){
|
||||
$language = null;
|
||||
$input = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
||||
|
||||
$supported_languages = ["en", "no"];
|
||||
|
||||
$browserLanguages = explode(",", str_replace(' ', '', strtolower($input)));
|
||||
|
||||
foreach ($browserLanguages as $languageString){
|
||||
// Stop looking for languages if one is found
|
||||
if(!empty($language)){ break; }
|
||||
|
||||
// Check for language in format "en_US"
|
||||
$stringParts = explode('-', $languageString);
|
||||
|
||||
// If not in that format, check for "en;q=0.9" format
|
||||
if(count($stringParts) <= 1){
|
||||
$stringParts = explode(';', $languageString);
|
||||
}
|
||||
|
||||
// If whichever format, check if the first key is a supported language
|
||||
if(count($stringParts) > 1 && in_array($stringParts[0], $supported_languages)){
|
||||
$language = $stringParts[0];
|
||||
}
|
||||
// If in neither format, check the whole string
|
||||
elseif(in_array($languageString, $supported_languages)){
|
||||
$language = $languageString;
|
||||
}
|
||||
}
|
||||
|
||||
// If no language found, set the first one in the supported-list
|
||||
if(empty($language)){
|
||||
$language = $supported_languages[0];
|
||||
}
|
||||
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
use Gettext\Scanner\PhpScanner;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Generator\PoGenerator;
|
||||
use Gettext\Generator\MoGenerator;
|
||||
|
||||
|
||||
class TranslatorScanning {
|
||||
public $scanner;
|
||||
|
||||
public function __construct($domain = "default"){
|
||||
|
||||
$phpScanner = new PhpScanner(
|
||||
Translations::create($domain)
|
||||
);
|
||||
|
||||
//Set a default domain, so any translations with no domain specified, will be added to that domain
|
||||
$phpScanner->setDefaultDomain($domain);
|
||||
|
||||
//Extract all comments starting with 'i18n:' and 'Translators:'
|
||||
$phpScanner->extractCommentsStartingWith('i18n:', 'Translators:');
|
||||
|
||||
//Scan files
|
||||
foreach (glob(__DIR__.'/../www/{*,*/*,*/*/*}.php', GLOB_BRACE) as $file) {
|
||||
// echo $file."<br>\n";
|
||||
$phpScanner->scanFile($file);
|
||||
}
|
||||
|
||||
$this->scanner = $phpScanner;
|
||||
}
|
||||
|
||||
public function save(){
|
||||
//Save the translations in .po files
|
||||
$generator = new PoGenerator();
|
||||
|
||||
foreach ($this->phpScanner->getTranslations() as $domain => $translations) {
|
||||
echo "Generate file: ".__DIR__."/../www/{$domain}.po<br>\n";
|
||||
$generator->generateFile($translations, __DIR__."/../www/{$domain}.po");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function compiler(){
|
||||
$loader = new PoLoader();
|
||||
|
||||
//From a file
|
||||
$translations = $loader->loadFile(__DIR__."/../www/domain1.po");
|
||||
|
||||
$generator = new MoGenerator();
|
||||
$generator->generateFile($translations, __DIR__."/../www/domain1.mo");
|
||||
|
||||
|
||||
|
||||
// From a string
|
||||
// $string = file_get_contents('locales2/en.po');
|
||||
// $translations = $loader->loadString($string);
|
||||
}
|
||||
}
|
||||
|
||||
// Resources: https://packagist.org/packages/gettext/gettext
|
||||
// Resources: https://www.sitepoint.com/easy-multi-language-twig-apps-with-gettext/
|
||||
|
||||
|
||||
function __($string, ...$values): string {
|
||||
if(DEBUG){
|
||||
logTranslationKey($string);
|
||||
}
|
||||
return sprintf( _($string), $values);
|
||||
}
|
||||
|
||||
function logTranslationKey($string){
|
||||
file_put_contents("translationkeys.txt", file_get_contents("translationkeys.txt") . "\n" . $string);
|
||||
}
|
|
@ -18,5 +18,16 @@ class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static function debug(...$args){
|
||||
foreach($args as $arg){
|
||||
echo "<pre>";
|
||||
if(is_bool($arg)){
|
||||
var_dump($arg);
|
||||
}
|
||||
else {
|
||||
echo print_r($arg, true);
|
||||
}
|
||||
echo "</pre>\n";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@ class WebPage {
|
|||
|
||||
|
||||
public function __construct(){
|
||||
new Translator();
|
||||
|
||||
$this->pr = Config::get('system', 'projectroot', '');
|
||||
$this->loggedIn = Auth::checkLogin();
|
||||
|
||||
|
@ -28,6 +30,11 @@ class WebPage {
|
|||
}
|
||||
|
||||
try {
|
||||
if(isset($_POST) && !empty($_POST)){
|
||||
$this->sanitizePost();
|
||||
$this->doPost();
|
||||
}
|
||||
|
||||
$this->prepareNavbar();
|
||||
$this->load();
|
||||
|
||||
|
@ -35,7 +42,7 @@ class WebPage {
|
|||
$this->template = str_replace('.php', '.html', $_SERVER['SCRIPT_NAME']);
|
||||
}
|
||||
|
||||
if($this->httpStatus == 200){
|
||||
if($this->httpStatus == 200 || $this instanceof ErrorPage){
|
||||
global $twig;
|
||||
|
||||
if(DEBUG){
|
||||
|
@ -43,7 +50,7 @@ class WebPage {
|
|||
header("X-Script-name: ".$_SERVER['SCRIPT_NAME']);
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
http_response_code($this->httpStatus);
|
||||
|
||||
echo $twig->render($this->template, $this->vars());
|
||||
}
|
||||
|
@ -55,7 +62,9 @@ class WebPage {
|
|||
catch (\Twig\Error\LoaderError $e){
|
||||
http_response_code(500);
|
||||
if( $e->getCode() == 0){
|
||||
echo "<pre>";
|
||||
print_r($e->getMessage());
|
||||
echo "</pre>";
|
||||
}
|
||||
else {
|
||||
echo $e;
|
||||
|
@ -63,12 +72,21 @@ class WebPage {
|
|||
}
|
||||
catch(\Twig\Error\RuntimeError | \Twig\Error\SyntaxError | Exception $e) {
|
||||
http_response_code(500);
|
||||
echo $e;
|
||||
print_r( $this->vars() );
|
||||
|
||||
if(!$this instanceof ErrorPage){
|
||||
$e = new ErrorPage(500, $e, print_r( $this->vars(), true ));
|
||||
}
|
||||
else {
|
||||
echo "<h1>A critical error occurred!</h1>";
|
||||
echo "<pre>".$e."</pre>";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function load(){ throw new Exception("Incomplete implementation"); }
|
||||
function load(){ throw new Exception("Incomplete implementation"); }
|
||||
|
||||
function doPost(){ throw new Exception("Incomplete implementation"); }
|
||||
|
||||
function vars(): array {
|
||||
return (array) $this;
|
||||
|
@ -83,6 +101,20 @@ class WebPage {
|
|||
["href"=>"/review/", "name"=>"Review", "active"=>$this->activePage=="/review/"?'active':'']
|
||||
], $this->navbar ?? []);
|
||||
}
|
||||
|
||||
protected function sanitizePost(){
|
||||
$data = [];
|
||||
|
||||
foreach($_POST as $key => $value){
|
||||
if(($data[$key] = Utils::filter($value)) === false){
|
||||
$err[] = __("Failed to sanitize: `%s`: %s \t-\t type: %s\n", $key, $value, gettype($value));
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($err)){ return $err; }
|
||||
$this->data = $data;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class WebPageAuth extends WebPage {
|
||||
|
@ -91,4 +123,38 @@ class WebPageAuth extends WebPage {
|
|||
parent::__construct();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorPage extends WebPage {
|
||||
|
||||
public $errCode = 500;
|
||||
public $errormessage = "An unexpected error occurred";
|
||||
public $errorDescription = "";
|
||||
public $template = "errors/500.html";
|
||||
|
||||
public function load(){
|
||||
if($this->errCode != 500){
|
||||
$this->template = "errors/$this->errCode.html";
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct($errCode = 500, $e = "", $extra = ""){
|
||||
$this->errCode = $errCode;
|
||||
if($e instanceof Exception){
|
||||
$this->errormessage = $e->getMessage();
|
||||
}
|
||||
$this->errorDescription = $e;
|
||||
$this->errorDescription .= "\n".$extra;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
|
||||
function isExecutingPage($file) : bool {
|
||||
$file1 = explode("/", $file);
|
||||
$request = str_replace('/','', $_SERVER['REQUEST_URI']);
|
||||
if(strstr($request, '?')){
|
||||
$request = explode('?', $request)[0];
|
||||
}
|
||||
return $request == $file1[ array_key_last($file1) ];
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
"require": {
|
||||
"twbs/bootstrap": "5.1.3",
|
||||
"twig/twig": "^3.3",
|
||||
"ext-mysqli": "*"
|
||||
"ext-mysqli": "*",
|
||||
"gettext/gettext": "^5.6",
|
||||
"ext-gettext": "*",
|
||||
"gettext/php-scanner": "^1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,271 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0daa3afffc94d70bd593d310cef916f8",
|
||||
"content-hash": "6111dbb0fd97c255b22341c3bbfb4f67",
|
||||
"packages": [
|
||||
{
|
||||
"name": "gettext/gettext",
|
||||
"version": "v5.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-gettext/Gettext.git",
|
||||
"reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-gettext/Gettext/zipball/017e249601d32b9a88c2eb4c10eac89bf582a7d3",
|
||||
"reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"gettext/languages": "^2.3",
|
||||
"php": "^7.2|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"brick/varexporter": "^0.3.5",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"oscarotero/php-cs-fixer-config": "^2.0",
|
||||
"phpunit/phpunit": "^8.0|^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gettext\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oscar Otero",
|
||||
"email": "oom@oscarotero.com",
|
||||
"homepage": "http://oscarotero.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP gettext manager",
|
||||
"homepage": "https://github.com/php-gettext/Gettext",
|
||||
"keywords": [
|
||||
"JS",
|
||||
"gettext",
|
||||
"i18n",
|
||||
"mo",
|
||||
"po",
|
||||
"translation"
|
||||
],
|
||||
"support": {
|
||||
"email": "oom@oscarotero.com",
|
||||
"issues": "https://github.com/php-gettext/Gettext/issues",
|
||||
"source": "https://github.com/php-gettext/Gettext/tree/v5.6.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/oscarotero",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/oscarotero",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/misteroom",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2021-12-04T11:33:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gettext/languages",
|
||||
"version": "2.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-gettext/Languages.git",
|
||||
"reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-gettext/Languages/zipball/ed56dd2c7f4024cc953ed180d25f02f2640e3ffa",
|
||||
"reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4"
|
||||
},
|
||||
"bin": [
|
||||
"bin/export-plural-rules"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gettext\\Languages\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michele Locati",
|
||||
"email": "mlocati@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "gettext languages with plural rules",
|
||||
"homepage": "https://github.com/php-gettext/Languages",
|
||||
"keywords": [
|
||||
"cldr",
|
||||
"i18n",
|
||||
"internationalization",
|
||||
"l10n",
|
||||
"language",
|
||||
"languages",
|
||||
"localization",
|
||||
"php",
|
||||
"plural",
|
||||
"plural rules",
|
||||
"plurals",
|
||||
"translate",
|
||||
"translations",
|
||||
"unicode"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-gettext/Languages/issues",
|
||||
"source": "https://github.com/php-gettext/Languages/tree/2.9.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/mlocati",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/mlocati",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-11-11T17:30:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gettext/php-scanner",
|
||||
"version": "v1.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-gettext/PHP-Scanner.git",
|
||||
"reference": "989a2cffa1d0f43d13b14c83a50429119b5eb8e4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-gettext/PHP-Scanner/zipball/989a2cffa1d0f43d13b14c83a50429119b5eb8e4",
|
||||
"reference": "989a2cffa1d0f43d13b14c83a50429119b5eb8e4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"gettext/gettext": "^5.5.0",
|
||||
"nikic/php-parser": "^4.2",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.15",
|
||||
"oscarotero/php-cs-fixer-config": "^1.0",
|
||||
"phpunit/phpunit": "^8.0",
|
||||
"squizlabs/php_codesniffer": "^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gettext\\Scanner\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oscar Otero",
|
||||
"email": "oom@oscarotero.com",
|
||||
"homepage": "http://oscarotero.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP scanner for gettext",
|
||||
"homepage": "https://github.com/php-gettext/PHP-Scanner",
|
||||
"keywords": [
|
||||
"gettext",
|
||||
"i18n",
|
||||
"php",
|
||||
"scanner",
|
||||
"translation"
|
||||
],
|
||||
"support": {
|
||||
"email": "oom@oscarotero.com",
|
||||
"issues": "https://github.com/php-gettext/PHP-Scanner/issues",
|
||||
"source": "https://github.com/php-gettext/PHP-Scanner/tree/v1.3.1"
|
||||
},
|
||||
"time": "2022-03-18T11:47:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.13.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "210577fe3cf7badcc5814d99455df46564f3c077"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
|
||||
"reference": "210577fe3cf7badcc5814d99455df46564f3c077",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-tokenizer": "*",
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ircmaxell/php-yacc": "^0.0.7",
|
||||
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/php-parse"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpParser\\": "lib/PhpParser"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nikita Popov"
|
||||
}
|
||||
],
|
||||
"description": "A PHP parser written in PHP",
|
||||
"keywords": [
|
||||
"parser",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
|
||||
},
|
||||
"time": "2021-11-30T19:35:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.25.0",
|
||||
|
@ -304,7 +567,10 @@
|
|||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform": {
|
||||
"ext-mysqli": "*",
|
||||
"ext-gettext": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.2.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once __dir__ . '/../Router.php';
|
||||
|
||||
|
||||
// Deletes logins which are older than 24 hours.
|
||||
DB::query("DELETE FROM user_login WHERE expire = 0 AND ctime < ?", time() - 24 * 60 * 60);
|
|
@ -49,9 +49,9 @@
|
|||
</ul>
|
||||
<div class="navbar-nav" id="navLogin">
|
||||
{% if loggedIn %}
|
||||
<a class='nav-item nav-link' href='{{ pr }}/logout.php'>Log out</a>
|
||||
<a class='nav-item nav-link' href='{{ pr }}/logout.php'>{{ 'Log out'|t }}</a>
|
||||
{% else %}
|
||||
<a class="nav-item nav-link" id='login' href='{{ pr }}/login.php'>Login</a>
|
||||
<a class="nav-item nav-link" id='login' href='{{ pr }}/login.php'>{{ 'Login'|t }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -71,10 +71,11 @@
|
|||
{% endif %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Any prices you find on this website is added by its users and are only a guideline.<br>
|
||||
{{ 'Any prices you find on this website is added by its users and are only a guideline.'|t }}<br>
|
||||
<strong>PaperBag</strong> is a <a href="//svagard.no/projects/paperbag" target="_blank" rel="nofollow">Svagård</a> project<br>
|
||||
|
||||
{#
|
||||
|
@ -88,5 +89,7 @@
|
|||
</footer>
|
||||
</div>
|
||||
|
||||
{% block endContent %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "assets/base.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="headline text-center"><img src='paperbag_small.svg' style="height: 4rem;" alt='Logo' /> PaperBag</h1>
|
||||
<div class="container-sm" style="padding-top: 5px; text-align: center; background: #fffe; border-radius: 10px;">
|
||||
|
||||
<h1>500 - An unexpected error occured</h1>
|
||||
<h3>{{ errormessage }}</h3>
|
||||
<pre style="text-align: left;">{{ errorDescription }}</pre>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -9,7 +9,7 @@
|
|||
<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">
|
||||
<input type="email" class="form-control" name="loginEmail" id="loginEmail" value="{{ data.loginEmail }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-check" style="margin: 3px 0;">
|
||||
<input class="form-check-input" type="checkbox" id="gridCheck">
|
||||
<input class="form-check-input" type="checkbox" id="gridCheck" name='cookieAccept' value="1" {% if data.cookieAccept %}checked="checked"{% endif %}">
|
||||
<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>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
{% extends "assets/base.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class='container-sm' style="max-width: 540px;">
|
||||
|
||||
<h1 class="headline text-center">Register</h1>
|
||||
|
||||
<form action="register.php" method="POST" class="row">
|
||||
<input type="hidden" name="referrerPage" value="<?=$returnToPage;?>">
|
||||
<div class="col-md-6">
|
||||
<label for="newEmail" class="form-label">Email*</label>
|
||||
<input type="email" class="form-control" id="newEmail" name="newEmail" maxlength="220" required value="{{ data.newEmail }}">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="newPassword" class="form-label">Password*</label>
|
||||
<input type="password" class="form-control" id="newPassword" name="newPassword" minlength="6" maxlength="30" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="newName" class="form-label">Full name</label>
|
||||
<input type="text" class="form-control" id="newName" name="newName" maxlength="200" value="{{ data.newName }}">
|
||||
</div>
|
||||
<div class="col-12" style="margin: 3px 0;">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="gridCheck" required>
|
||||
<label class="form-check-label" for="gridCheck">
|
||||
I will read the Terms and Conditions when they are published.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mb-3">
|
||||
<button type="submit" class="btn btn-primary w-100">Sign up</button>
|
||||
</div>
|
||||
</form>
|
||||
<p>Already have an account? <a href="login.php">Login</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -4,7 +4,7 @@ require_once '../Router.php';
|
|||
|
||||
class LoginPage extends WebPage {
|
||||
public $pagekey = "login";
|
||||
public $title = "PaperBag - Plan & Execute Your Shopping";
|
||||
public $title = "Login - PaperBag - Plan & Execute Your Shopping";
|
||||
public $returnToPage = "/";
|
||||
|
||||
function load(){
|
||||
|
@ -17,13 +17,9 @@ class LoginPage extends WebPage {
|
|||
if(isset($_GET['thank'])){
|
||||
$this->msg[] = "Thank you for registering. Please log in to continue!";
|
||||
}
|
||||
|
||||
if(isset($_POST) && !empty($_POST)){
|
||||
$this->doPost();
|
||||
}
|
||||
}
|
||||
|
||||
private function findReturnPage(){
|
||||
protected function findReturnPage(){
|
||||
if(isset($_POST['referrerPage'])){
|
||||
$this->returnToPage = $_POST['referrerPage'];
|
||||
}
|
||||
|
@ -39,23 +35,22 @@ class LoginPage extends WebPage {
|
|||
}
|
||||
}
|
||||
|
||||
private function doPost(){
|
||||
$data = [];
|
||||
|
||||
foreach($_POST as $key => $value){
|
||||
if(($data[$key] = Utils::filter($value)) === false){
|
||||
echo "Failed to sanitize: `".$key."`: ".$value." \t-\t type: ".gettype($value)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
function doPost(){
|
||||
$stayLoggedIn = isset($_POST['stayLoggedIn']);
|
||||
$err = Auth::loginWithCredentials($data['loginEmail'], $data['loginPwd'], $stayLoggedIn);
|
||||
$err = Auth::loginWithCredentials($this->data['loginEmail'], $this->data['loginPwd'], $stayLoggedIn);
|
||||
if($err === true){
|
||||
header("Location: ".$this->returnToPage);
|
||||
die();
|
||||
}
|
||||
|
||||
// Do not send the password back to the client...
|
||||
$this->data['loginPwd'] = null;
|
||||
|
||||
$this->err = $err;
|
||||
}
|
||||
}
|
||||
|
||||
$a = new LoginPage();
|
||||
// For use when class is extended upon
|
||||
if(isExecutingPage(__file__)){
|
||||
$a = new LoginPage();
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
require 'webdata/init.php';
|
||||
require_once '../Router.php';
|
||||
|
||||
logoutUser();
|
||||
|
||||
header("Location: ".getConfig('projectRoot')."/");
|
||||
Auth::logout();
|
||||
|
||||
header("Location: ".Config::get('system', 'projectRoot', '')."/");
|
||||
|
|
126
www/register.php
126
www/register.php
|
@ -1,120 +1,32 @@
|
|||
<?php
|
||||
require 'webdata/init.php';
|
||||
|
||||
$returnToPage = "./";
|
||||
if(isset($_POST['referrerPage'])){
|
||||
$returnToPage = $_POST['referrerPage'];
|
||||
}
|
||||
else if(isset($_SERVER['HTTP_REFERER'])){
|
||||
$returnToPage = $_SERVER['HTTP_REFERER'];
|
||||
}
|
||||
if(stristr($returnToPage, "login.php") || stristr($returnToPage, "register.php")){
|
||||
$returnToPage = "./";
|
||||
}
|
||||
require_once '../Router.php';
|
||||
require_once 'login.php';
|
||||
|
||||
if(isset($_POST) && !empty($_POST)) {
|
||||
$db = database();
|
||||
$data = [];
|
||||
class RegisterPage extends LoginPage {
|
||||
public $pagekey = "register";
|
||||
public $title = "Register - PaperBag - Plan & Execute Your Shopping";
|
||||
public $returnToPage = "/";
|
||||
|
||||
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";
|
||||
function load(){
|
||||
$this->findReturnPage();
|
||||
|
||||
if(Auth::checkLogin(true)){
|
||||
header("Location: ".$this->returnToPage);
|
||||
}
|
||||
}
|
||||
|
||||
$userEmail = $data['newEmail'];
|
||||
$userPass = $data['newPassword'];
|
||||
$userName = $data['newName'] ?? '';
|
||||
|
||||
if(strlen($userPass) < 6){
|
||||
$err[] = "Password is too short. Password needs to be at least 6 characters!";
|
||||
}
|
||||
else if(strlen($userPass) > 30){
|
||||
$err[] = "Password is too long. Max length is set to 30 characters. If you believe it should be higher please contact the developers.";
|
||||
}
|
||||
|
||||
if(strlen($userEmail) > 220){
|
||||
$err[] = "Your email-address is too long. Can you please register with another email?";
|
||||
}
|
||||
|
||||
if(strlen($userName) > 200){
|
||||
$err[] = "Your name seems to be too large to fit this system. Maybe you can short it somehow?";
|
||||
}
|
||||
|
||||
if(empty($err)){
|
||||
// HASH PASSWORD
|
||||
$newPass = PwdGen($userPass, true);
|
||||
|
||||
$createUserSQL = "INSERT INTO user SET full_name = '$userName', user_email = '$userEmail', pwd = '$newPass';";
|
||||
if($db->query($createUserSQL)){
|
||||
$newID = $db->insert_id;
|
||||
$updateUserSQL = "UPDATE user SET md5_id = '".md5($newID)."' WHERE user_id = '$newID';";
|
||||
$db->query($updateUserSQL);
|
||||
|
||||
function doPost(){
|
||||
$err = Auth::register($this->data['newEmail'], $this->data['newPassword'], $this->data);
|
||||
if($err === true){
|
||||
header("Location: login.php?thank");
|
||||
die();
|
||||
}
|
||||
else {
|
||||
$err[] = "Something went wrong:<br>".$db->error;
|
||||
}
|
||||
$this->err = $err;
|
||||
|
||||
// Do not send the password back to the client...
|
||||
$this->data['newPassword'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<?=getHtmlHeaders();?>
|
||||
<title>Register - PaperBag</title>
|
||||
</head>
|
||||
<body id='plan'>
|
||||
<div id="page-container">
|
||||
<div id="page-wrapper">
|
||||
<?php include 'webdata/navbar.php'; ?>
|
||||
|
||||
<div class='container-md' style="max-width: 720px;">
|
||||
|
||||
<h1 class="headline text-center">Register</h1>
|
||||
<br>
|
||||
|
||||
<?php if(!empty($err)){
|
||||
foreach($err as $e){
|
||||
echo "<div class='alert alert-danger' role='alert'>$e</div>";
|
||||
}
|
||||
echo "<br>";
|
||||
}
|
||||
?>
|
||||
|
||||
<form action="register.php" method="POST" class="row">
|
||||
<input type="hidden" name="referrerPage" value="<?=$returnToPage;?>">
|
||||
<div class="col-md-6">
|
||||
<label for="newEmail" class="form-label">Email*</label>
|
||||
<input type="email" class="form-control" id="newEmail" name="newEmail" maxlength="220" required value="<?= $data['newEmail'] ?? '';?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="newPassword" class="form-label">Password*</label>
|
||||
<input type="password" class="form-control" id="newPassword" name="newPassword" minlength="6" maxlength="30" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="newName" class="form-label">Full name</label>
|
||||
<input type="text" class="form-control" id="newName" name="newName" maxlength="200" value="<?= $data['newName'] ?? '';?>">
|
||||
</div>
|
||||
<div class="col-12" style="margin: 3px 0;">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="gridCheck" required>
|
||||
<label class="form-check-label" for="gridCheck">
|
||||
I will read the Terms and Conditions when they are published.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mb-3">
|
||||
<button type="submit" class="btn btn-primary w-100">Sign up</button>
|
||||
</div>
|
||||
</form>
|
||||
<p>Already have an account? <a href="login.php">Login</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php include 'webdata/footer.html'; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
$a = new RegisterPage();
|
||||
|
|
Loading…
Reference in New Issue