initial commit

This commit is contained in:
2025-11-24 14:06:57 +01:00
commit 4fce91b055
81 changed files with 7718 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
<?php
/**
* Class Application
* The heart of the application
*/
class Application
{
/** @var mixed Instance of the controller */
private $controller;
/** @var array URL parameters, will be passed to used controller-method */
private $parameters = array();
/** @var string Just the name of the controller, useful for checks inside the view ("where am I ?") */
private $controller_name;
/** @var string Just the name of the controller's method, useful for checks inside the view ("where am I ?") */
private $action_name;
/**
* Start the application, analyze URL elements, call according controller/method or relocate to fallback location
*/
public function __construct()
{
// create array with URL parts in $url
$this->splitUrl();
// creates controller and action names (from URL input)
$this->createControllerAndActionNames();
// does such a controller exist ?
if (file_exists(Config::get('PATH_CONTROLLER') . $this->controller_name . '.php')) {
// load this file and create this controller
// example: if controller would be "car", then this line would translate into: $this->car = new car();
require Config::get('PATH_CONTROLLER') . $this->controller_name . '.php';
$this->controller = new $this->controller_name();
// check are controller and method existing and callable?
if (is_callable(array($this->controller, $this->action_name))) {
if (!empty($this->parameters)) {
// call the method and pass arguments to it
call_user_func_array(array($this->controller, $this->action_name), $this->parameters);
} else {
// if no parameters are given, just call the method without parameters, like $this->index->index();
$this->controller->{$this->action_name}();
}
} else {
// load 404 error page
require Config::get('PATH_CONTROLLER') . 'ErrorController.php';
$this->controller = new ErrorController;
$this->controller->error404();
}
} else {
// load 404 error page
require Config::get('PATH_CONTROLLER') . 'ErrorController.php';
$this->controller = new ErrorController;
$this->controller->error404();
}
}
/**
* Get and split the URL
*/
private function splitUrl()
{
if (Request::get('url')) {
// split URL
$url = trim(Request::get('url'), '/');
$url = filter_var($url, FILTER_SANITIZE_URL);
$url = explode('/', $url);
// put URL parts into according properties
$this->controller_name = isset($url[0]) ? $url[0] : null;
$this->action_name = isset($url[1]) ? $url[1] : null;
// remove controller name and action name from the split URL
unset($url[0], $url[1]);
// rebase array keys and store the URL parameters
$this->parameters = array_values($url);
}
}
/**
* Checks if controller and action names are given. If not, default values are put into the properties.
* Also renames controller to usable name.
*/
private function createControllerAndActionNames()
{
// check for controller: no controller given ? then make controller = default controller (from config)
if (!$this->controller_name) {
$this->controller_name = Config::get('DEFAULT_CONTROLLER');
}
// check for action: no action given ? then make action = default action (from config)
if (!$this->action_name OR (strlen($this->action_name) == 0)) {
$this->action_name = Config::get('DEFAULT_ACTION');
}
// rename controller name to real controller class/file name ("index" to "IndexController")
$this->controller_name = ucwords($this->controller_name) . 'Controller';
}
}

82
application/core/Auth.php Normal file
View File

@@ -0,0 +1,82 @@
<?php
/**
* Class Auth
* Checks if user is logged in, if not then sends the user to "yourdomain.com/login".
* Auth::checkAuthentication() can be used in the constructor of a controller (to make the
* entire controller only visible for logged-in users) or inside a controller-method to make only this part of the
* application available for logged-in users.
*/
class Auth
{
/**
* The normal authentication flow, just check if the user is logged in (by looking into the session).
* If user is not, then he will be redirected to login page and the application is hard-stopped via exit().
*/
public static function checkAuthentication()
{
// initialize the session (if not initialized yet)
Session::init();
// self::checkSessionConcurrency();
// if user is NOT logged in...
// (if user IS logged in the application will not run the code below and therefore just go on)
if (!Session::userIsLoggedIn()) {
// ... then treat user as "not logged in", destroy session, redirect to login page
Session::destroy();
// send the user to the login form page, but also add the current page's URI (the part after the base URL)
// as a parameter argument, making it possible to send the user back to where he/she came from after a
// successful login
header('location: ' . Config::get('URL') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI']));
// to prevent fetching views via cURL (which "ignores" the header-redirect above) we leave the application
// the hard way, via exit(). @see https://github.com/panique/php-login/issues/453
// this is not optimal and will be fixed in future releases
exit();
}
}
/**
* The admin authentication flow, just check if the user is logged in (by looking into the session) AND has
* user role type 7 (currently there's only type 1 (normal user), type 2 (premium user) and 7 (admin)).
* If user is not, then he will be redirected to login page and the application is hard-stopped via exit().
* Using this method makes only sense in controllers that should only be used by admins.
*/
public static function checkAdminAuthentication()
{
// initialize the session (if not initialized yet)
Session::init();
// self::checkSessionConcurrency();
// if user is not logged in or is not an admin (= not role type 7)
if (!Session::userIsLoggedIn() || Session::get("user_account_type") != 7) {
// ... then treat user as "not logged in", destroy session, redirect to login page
Session::destroy();
header('location: ' . Config::get('URL') . 'login');
// to prevent fetching views via cURL (which "ignores" the header-redirect above) we leave the application
// the hard way, via exit(). @see https://github.com/panique/php-login/issues/453
// this is not optimal and will be fixed in future releases
exit();
}
}
/**
* Detects if there is concurrent session (i.e. another user logged in with the same current user credentials),
* If so, then logout.
*/
public static function checkSessionConcurrency(){
if(Session::userIsLoggedIn()){
if(Session::isConcurrentSessionExists()){
LoginModel::logout();
Redirect::home();
exit();
}
}
}
}

View File

@@ -0,0 +1,23 @@
<?php
class Config
{
// this is public to allow better Unit Testing
public static $config;
public static function get($key)
{
if (!self::$config) {
$config_file = '../application/config/config.' . Environment::get() . '.php';
if (!file_exists($config_file)) {
return false;
}
self::$config = require $config_file;
}
return self::$config[$key];
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* This is the "base controller class". All other "real" controllers extend this class.
* Whenever a controller is created, we also
* 1. initialize a session
* 2. check if the user is not logged in anymore (session timeout) but has a cookie
*/
class Controller
{
/** @var View View The view object */
public $View;
/**
* Construct the (base) controller. This happens when a real controller is constructed, like in
* the constructor of IndexController when it says: parent::__construct();
*/
public function __construct()
{
// always initialize a session
Session::init();
// check session concurrency
Auth::checkSessionConcurrency();
// user is not logged in but has remember-me-cookie ? then try to login with cookie ("remember me" feature)
if (!Session::userIsLoggedIn() AND Request::cookie('remember_me')) {
header('location: ' . Config::get('URL') . 'login/loginWithCookie');
}
// create a view object to be able to use it inside a controller, like $this->View->render();
$this->View = new View();
}
}

60
application/core/Csrf.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
/**
* Cross Site Request Forgery Class
*
*/
/**
* Instructions:
*
* At your form, before the submit button put:
* <input type="hidden" name="csrf_token" value="<?= Csrf::makeToken(); ?>" />
*
* This validation needed in the controller action method to validate CSRF token submitted with the form:
*
* if (!Csrf::isTokenValid()) {
* LoginModel::logout();
* Redirect::home();
* exit();
* }
*
* To get simpler code it might be better to put the logout, redirect, exit into an own (static) method.
*/
class Csrf
{
/**
* get CSRF token and generate a new one if expired
*
* @access public
* @static static method
* @return string
*/
public static function makeToken()
{
// token is valid for 1 day
$max_time = 60 * 60 * 24;
$stored_time = Session::get('csrf_token_time');
$csrf_token = Session::get('csrf_token');
if ($max_time + $stored_time <= time() || empty($csrf_token)) {
Session::set('csrf_token', md5(uniqid(rand(), true)));
Session::set('csrf_token_time', time());
}
return Session::get('csrf_token');
}
/**
* checks if CSRF token in session is same as in the form submitted
*
* @access public
* @static static method
* @return bool
*/
public static function isTokenValid()
{
$token = Request::post('csrf_token');
return $token === Session::get('csrf_token') && !empty($token);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* Class DatabaseFactory
*
* Use it like this:
* $database = DatabaseFactory::getFactory()->getConnection();
*
* That's my personal favourite when creating a database connection.
* It's a slightly modified version of Jon Raphaelson's excellent answer on StackOverflow:
* http://stackoverflow.com/questions/130878/global-or-singleton-for-database-connection
*
* Full quote from the answer:
*
* "Then, in 6 months when your app is super famous and getting dugg and slashdotted and you decide you need more than
* a single connection, all you have to do is implement some pooling in the getConnection() method. Or if you decide
* that you want a wrapper that implements SQL logging, you can pass a PDO subclass. Or if you decide you want a new
* connection on every invocation, you can do do that. It's flexible, instead of rigid."
*
* Thanks! Big up, mate!
*/
class DatabaseFactory
{
private static $factory;
private $database;
public static function getFactory()
{
if (!self::$factory) {
self::$factory = new DatabaseFactory();
}
return self::$factory;
}
public function getConnection() {
if (!$this->database) {
/**
* Check DB connection in try/catch block. Also when PDO is not constructed properly,
* prevent to exposing database host, username and password in plain text as:
* PDO->__construct('mysql:host=127....', 'root', '12345678', Array)
* by throwing custom error message
*/
try {
$options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
$this->database = new PDO(
Config::get('DB_TYPE') . ':host=' . Config::get('DB_HOST') . ';dbname=' .
Config::get('DB_NAME') . ';port=' . Config::get('DB_PORT') . ';charset=' . Config::get('DB_CHARSET'),
Config::get('DB_USER'), Config::get('DB_PASS'), $options
);
} catch (PDOException $e) {
// Echo custom message. Echo error code gives you some info.
echo 'Database connection can not be estabilished. Please try again later.' . '<br>';
echo 'Error code: ' . $e->getCode();
// Stop application :(
// No connection, reached limit connections etc. so no point to keep it running
exit;
}
}
return $this->database;
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* Encryption and Decryption Class
*
*/
class Encryption
{
/**
* Cipher algorithm
*
* @var string
*/
const CIPHER = 'aes-256-cbc';
/**
* Hash function
*
* @var string
*/
const HASH_FUNCTION = 'sha256';
/**
* constructor for Encryption object.
*
* @access private
*/
private function __construct()
{
}
/**
* Encrypt a string.
*
* @access public
* @static static method
* @param string $plain
* @return string
* @throws Exception If functions don't exists
*/
public static function encrypt($plain)
{
if (!function_exists('openssl_cipher_iv_length') ||
!function_exists('openssl_random_pseudo_bytes') ||
!function_exists('openssl_encrypt')) {
throw new Exception('Encryption function doesn\'t exist');
}
// generate initialization vector,
// this will make $iv different every time,
// so, encrypted string will be also different.
$iv_size = openssl_cipher_iv_length(self::CIPHER);
$iv = openssl_random_pseudo_bytes($iv_size);
// generate key for authentication using ENCRYPTION_KEY & HMAC_SALT
$key = mb_substr(hash(self::HASH_FUNCTION, Config::get('ENCRYPTION_KEY') . Config::get('HMAC_SALT')), 0, 32, '8bit');
// append initialization vector
$encrypted_string = openssl_encrypt($plain, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
$ciphertext = $iv . $encrypted_string;
// apply the HMAC
$hmac = hash_hmac('sha256', $ciphertext, $key);
return $hmac . $ciphertext;
}
/**
* Decrypted a string.
*
* @access public
* @static static method
* @param string $ciphertext
* @return string
* @throws Exception If $ciphertext is empty, or If functions don't exists
*/
public static function decrypt($ciphertext)
{
if (empty($ciphertext)) {
throw new Exception('The String to decrypt can\'t be empty');
}
if (!function_exists('openssl_cipher_iv_length') ||
!function_exists('openssl_decrypt')) {
throw new Exception('Encryption function doesn\'t exist');
}
// generate key used for authentication using ENCRYPTION_KEY & HMAC_SALT
$key = mb_substr(hash(self::HASH_FUNCTION, Config::get('ENCRYPTION_KEY') . Config::get('HMAC_SALT')), 0, 32, '8bit');
// split cipher into: hmac, cipher & iv
$macSize = 64;
$hmac = mb_substr($ciphertext, 0, $macSize, '8bit');
$iv_cipher = mb_substr($ciphertext, $macSize, null, '8bit');
// generate original hmac & compare it with the one in $ciphertext
$originalHmac = hash_hmac('sha256', $iv_cipher, $key);
if (!self::hashEquals($hmac, $originalHmac)) {
return false;
}
// split out the initialization vector and cipher
$iv_size = openssl_cipher_iv_length(self::CIPHER);
$iv = mb_substr($iv_cipher, 0, $iv_size, '8bit');
$cipher = mb_substr($iv_cipher, $iv_size, null, '8bit');
return openssl_decrypt($cipher, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
}
/**
* A timing attack resistant comparison.
*
* @access private
* @static static method
* @param string $hmac The hmac from the ciphertext being decrypted.
* @param string $compare The comparison hmac.
* @return bool
* @see https://github.com/sarciszewski/php-future/blob/bd6c91fb924b2b35a3e4f4074a642868bd051baf/src/Security.php#L36
*/
private static function hashEquals($hmac, $compare)
{
if (function_exists('hash_equals')) {
return hash_equals($hmac, $compare);
}
// if hash_equals() is not available,
// then use the following snippet.
// It's equivalent to hash_equals() in PHP 5.6.
$hashLength = mb_strlen($hmac, '8bit');
$compareLength = mb_strlen($compare, '8bit');
if ($hashLength !== $compareLength) {
return false;
}
$result = 0;
for ($i = 0; $i < $hashLength; $i++) {
$result |= (ord($hmac[$i]) ^ ord($compare[$i]));
}
return $result === 0;
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Class Environment
*
* Extremely simple way to get the environment, everywhere inside your application.
* Extend this the way you want.
*/
class Environment
{
public static function get()
{
// if APPLICATION_ENV constant exists (set in Apache configs)
// then return content of APPLICATION_ENV
// else return "development"
return (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : "development");
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* Class Filter
*
* This is the place to put filters, usually methods that cleans, sorts and, well, filters stuff.
*/
class Filter
{
/**
* The XSS filter: This simply removes "code" from any data, used to prevent Cross-Site Scripting Attacks.
*
* A very simple introduction: Let's say an attackers changes its username from "John" to these lines:
* "<script>var http = new XMLHttpRequest(); http.open('POST', 'example.com/my_account/delete.php', true);</script>"
* This means, every user's browser would render "John" anymore, instead interpreting this JavaScript code, calling
* the delete.php, in this case inside the project, in worse scenarios something like performing a bank transaction
* or sending your cookie data (containing your remember-me-token) to somebody else.
*
* What is XSS ?
* @see http://phpsecurity.readthedocs.org/en/latest/Cross-Site-Scripting-%28XSS%29.html
*
* Deeper information:
* @see https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
*
* XSSFilter expects a value, checks if the value is a string, and if so, encodes typical script tag chars to
* harmless HTML (you'll see the code, it wil not be interpreted). Then the method checks if the value is an array,
* or an object and if so, makes sure all its string content is encoded (recursive call on its values).
* Note that this method uses reference to the assed variable, not a copy, meaning you can use this methods like this:
*
* CORRECT: Filter::XSSFilter($myVariable);
* WRONG: $myVariable = Filter::XSSFilter($myVariable);
*
* This works like some other popular PHP functions, for example sort().
* @see http://php.net/manual/en/function.sort.php
*
* @see http://stackoverflow.com/questions/1676897/what-does-it-mean-to-start-a-php-function-with-an-ampersand
* @see http://php.net/manual/en/language.references.pass.php
*
* FYI: htmlspecialchars() does this (from PHP docs):
*
* '&' (ampersand) becomes '&amp;'
* '"' (double quote) becomes '&quot;' when ENT_NOQUOTES is not set.
* "'" (single quote) becomes '&#039;' (or &apos;) only when ENT_QUOTES is set.
* '<' (less than) becomes '&lt;'
* '>' (greater than) becomes '&gt;'
*
* @see http://www.php.net/manual/en/function.htmlspecialchars.php
*
* @param $value The value to be filtered
* @return mixed
*/
public static function XSSFilter(&$value)
{
// if argument is a string, filters that string
if (is_string($value)) {
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
// if argument is an array or an object,
// recursivly filters its content
} else if (is_array($value) || is_object($value)) {
/**
* Make sure the element is passed by reference,
* In PHP 7, foreach does not use the internal array pointer.
* In order to be able to directly modify array elements within the loop
* precede $value with &. In that case the value will be assigned by reference.
* @see http://php.net/manual/en/control-structures.foreach.php
*/
foreach ($value as &$valueInValue) {
self::XSSFilter($valueInValue);
}
}
// other types are untouched
return $value;
}
}

154
application/core/Mail.php Normal file
View File

@@ -0,0 +1,154 @@
<?php
/* Using PHPMailer's namespace */
use PHPMailer\PHPMailer\PHPMailer;
/**
* Class Mail
*
* Handles everything regarding mail-sending.
*/
class Mail
{
/** @var mixed variable to collect errors */
private $error;
/**
* Try to send a mail by using PHP's native mail() function.
* Please note that not PHP itself will send a mail, it's just a wrapper for Linux's sendmail or other mail tools
*
* Good guideline on how to send mails natively with mail():
* @see http://stackoverflow.com/a/24644450/1114320
* @see http://www.php.net/manual/en/function.mail.php
*/
public function sendMailWithNativeMailFunction()
{
// no code yet, so we just return something to make IDEs and code analyzer tools happy
return false;
}
/**
* Try to send a mail by using SwiftMailer.
* Make sure you have loaded SwiftMailer via Composer.
*
* @return bool
*/
public function sendMailWithSwiftMailer()
{
// no code yet, so we just return something to make IDEs and code analyzer tools happy
return false;
}
/**
* Try to send a mail by using PHPMailer.
* Make sure you have loaded PHPMailer via Composer.
* Depending on your EMAIL_USE_SMTP setting this will work via SMTP credentials or via native mail()
*
* @param $user_email
* @param $from_email
* @param $from_name
* @param $subject
* @param $body
*
* @return bool
* @throws Exception
* @throws phpmailerException
*/
public function sendMailWithPHPMailer($user_email, $from_email, $from_name, $subject, $body)
{
$mail = new PHPMailer;
// you should use UTF-8 to avoid encoding issues
$mail->CharSet = 'UTF-8';
// if you want to send mail via PHPMailer using SMTP credentials
if (Config::get('EMAIL_USE_SMTP')) {
// set PHPMailer to use SMTP
$mail->IsSMTP();
// 0 = off, 1 = commands, 2 = commands and data, perfect to see SMTP errors
$mail->SMTPDebug = 0;
// enable SMTP authentication
$mail->SMTPAuth = Config::get('EMAIL_SMTP_AUTH');
// encryption
if (Config::get('EMAIL_SMTP_ENCRYPTION')) {
$mail->SMTPSecure = Config::get('EMAIL_SMTP_ENCRYPTION');
}
// set SMTP provider's credentials
$mail->Host = Config::get('EMAIL_SMTP_HOST');
$mail->Username = Config::get('EMAIL_SMTP_USERNAME');
$mail->Password = Config::get('EMAIL_SMTP_PASSWORD');
$mail->Port = Config::get('EMAIL_SMTP_PORT');
} else {
$mail->IsMail();
}
// fill mail with data
$mail->From = $from_email;
$mail->FromName = $from_name;
$mail->AddAddress($user_email);
$mail->Subject = $subject;
$mail->Body = $body;
// try to send mail, put result status (true/false into $wasSendingSuccessful)
// I'm unsure if mail->send really returns true or false every time, tis method in PHPMailer is quite complex
$wasSendingSuccessful = $mail->Send();
if ($wasSendingSuccessful) {
return true;
} else {
// if not successful, copy errors into Mail's error property
$this->error = $mail->ErrorInfo;
return false;
}
}
/**
* The main mail sending method, this simply calls a certain mail sending method depending on which mail provider
* you've selected in the application's config.
*
* @param $user_email string email
* @param $from_email string sender's email
* @param $from_name string sender's name
* @param $subject string subject
* @param $body string full mail body text
* @return bool the success status of the according mail sending method
*/
public function sendMail($user_email, $from_email, $from_name, $subject, $body)
{
if (Config::get('EMAIL_USED_MAILER') == "phpmailer") {
// returns true if successful, false if not
return $this->sendMailWithPHPMailer(
$user_email, $from_email, $from_name, $subject, $body
);
}
if (Config::get('EMAIL_USED_MAILER') == "swiftmailer") {
return $this->sendMailWithSwiftMailer();
}
if (Config::get('EMAIL_USED_MAILER') == "native") {
return $this->sendMailWithNativeMailFunction();
}
}
/**
* The different mail sending methods write errors to the error property $this->error,
* this method simply returns this error / error array.
*
* @return mixed
*/
public function getError()
{
return $this->error;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Class Redirect
*
* Simple abstraction for redirecting the user to a certain page
*/
class Redirect
{
/**
* To the last visited page before user logged in (useful when people are on a certain page inside your application
* and then want to log in (to edit or comment something for example) and don't to be redirected to the main page).
*
* This is just a bulletproof version of Redirect::to(), redirecting to an ABSOLUTE URL path like
* "http://www.mydomain.com/user/profile", useful as people had problems with the RELATIVE URL path generated
* by Redirect::to() when using HUGE inside sub-folders.
*
* @param $path string
*/
public static function toPreviousViewedPageAfterLogin($path)
{
header('location: http://' . $_SERVER['HTTP_HOST'] . '/' . $path);
}
/**
* To the homepage
*/
public static function home()
{
header("location: " . Config::get('URL'));
}
/**
* To the defined page, uses a relative path (like "user/profile")
*
* Redirects to a RELATIVE path, like "user/profile" (which works very fine unless you are using HUGE inside tricky
* sub-folder structures)
*
* @see https://github.com/panique/huge/issues/770
* @see https://github.com/panique/huge/issues/754
*
* @param $path string
*/
public static function to($path)
{
header("location: " . Config::get('URL') . $path);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* This is under development. Expect changes!
* Class Request
* Abstracts the access to $_GET, $_POST and $_COOKIE, preventing direct access to these super-globals.
* This makes PHP code quality analyzer tools very happy.
* @see http://php.net/manual/en/reserved.variables.request.php
*/
class Request
{
/**
* Gets/returns the value of a specific key of the POST super-global.
* When using just Request::post('x') it will return the raw and untouched $_POST['x'], when using it like
* Request::post('x', true) then it will return a trimmed and stripped $_POST['x'] !
*
* @param mixed $key key
* @param bool $clean marker for optional cleaning of the var
* @return mixed the key's value or nothing
*/
public static function post($key, $clean = false)
{
if (isset($_POST[$key])) {
// we use the Ternary Operator here which saves the if/else block
// @see http://davidwalsh.name/php-shorthand-if-else-ternary-operators
return ($clean) ? trim(strip_tags($_POST[$key])) : $_POST[$key];
}
}
/**
* Returns the state of a checkbox.
*
* @param mixed $key key
* @return mixed state of the checkbox
*/
public static function postCheckbox($key)
{
return isset($_POST[$key]) ? 1 : null;
}
/**
* gets/returns the value of a specific key of the GET super-global
* @param mixed $key key
* @return mixed the key's value or nothing
*/
public static function get($key)
{
if (isset($_GET[$key])) {
return $_GET[$key];
}
}
/**
* gets/returns the value of a specific key of the COOKIE super-global
* @param mixed $key key
* @return mixed the key's value or nothing
*/
public static function cookie($key)
{
if (isset($_COOKIE[$key])) {
return $_COOKIE[$key];
}
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* Session class
*
* handles the session stuff. creates session when no one exists, sets and gets values, and closes the session
* properly (=logout). Not to forget the check if the user is logged in or not.
*/
class Session
{
/**
* starts the session
*/
public static function init()
{
// if no session exist, start the session
if (session_id() == '') {
session_start();
}
}
/**
* sets a specific value to a specific key of the session
*
* @param mixed $key key
* @param mixed $value value
*/
public static function set($key, $value)
{
$_SESSION[$key] = $value;
}
/**
* gets/returns the value of a specific key of the session
*
* @param mixed $key Usually a string, right ?
* @return mixed the key's value or nothing
*/
public static function get($key)
{
if (isset($_SESSION[$key])) {
$value = $_SESSION[$key];
// filter the value for XSS vulnerabilities
return Filter::XSSFilter($value);
}
}
/**
* adds a value as a new array element to the key.
* useful for collecting error messages etc
*
* @param mixed $key
* @param mixed $value
*/
public static function add($key, $value)
{
$_SESSION[$key][] = $value;
}
/**
* deletes the session (= logs the user out)
*/
public static function destroy()
{
session_destroy();
}
/**
* update session id in database
*
* @access public
* @static static method
* @param string $userId
* @param string $sessionId
*/
public static function updateSessionId($userId, $sessionId = null)
{
$database = DatabaseFactory::getFactory()->getConnection();
$sql = "UPDATE users SET session_id = :session_id WHERE user_id = :user_id";
$query = $database->prepare($sql);
$query->execute(array(':session_id' => $sessionId, ":user_id" => $userId));
}
/**
* checks for session concurrency
*
* This is done as the following:
* UserA logs in with his session id('123') and it will be stored in the database.
* Then, UserB logs in also using the same email and password of UserA from another PC,
* and also store the session id('456') in the database
*
* Now, Whenever UserA performs any action,
* You then check the session_id() against the last one stored in the database('456'),
* If they don't match then log both of them out.
*
* @access public
* @static static method
* @return bool
* @see Session::updateSessionId()
* @see http://stackoverflow.com/questions/6126285/php-stop-concurrent-user-logins
*/
public static function isConcurrentSessionExists()
{
$session_id = session_id();
$userId = Session::get('user_id');
if (isset($userId) && isset($session_id)) {
$database = DatabaseFactory::getFactory()->getConnection();
$sql = "SELECT session_id FROM users WHERE user_id = :user_id LIMIT 1";
$query = $database->prepare($sql);
$query->execute(array(":user_id" => $userId));
$result = $query->fetch();
$userSessionId = !empty($result)? $result->session_id: null;
return $session_id !== $userSessionId;
}
return false;
}
/**
* Checks if the user is logged in or not
*
* @return bool user's login status
*/
public static function userIsLoggedIn()
{
return (self::get('user_logged_in') ? true : false);
}
}

32
application/core/Text.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
class Text
{
private static $texts;
public static function get($key, $data = null)
{
// if not $key
if (!$key) {
return null;
}
if ($data) {
foreach ($data as $var => $value) {
${$var} = $value;
}
}
// load config file (this is only done once per application lifecycle)
if (!self::$texts) {
self::$texts = require('../application/config/texts.php');
}
// check if array key exists
if (!array_key_exists($key, self::$texts)) {
return null;
}
return self::$texts[$key];
}
}

177
application/core/View.php Normal file
View File

@@ -0,0 +1,177 @@
<?php
/**
* Class View
* The part that handles all the output
*/
class View
{
/**
* simply includes (=shows) the view. this is done from the controller. In the controller, you usually say
* $this->view->render('help/index'); to show (in this example) the view index.php in the folder help.
* Usually the Class and the method are the same like the view, but sometimes you need to show different views.
* @param string $filename Path of the to-be-rendered view, usually folder/file(.php)
* @param array $data Data to be used in the view
*/
public function render($filename, $data = null)
{
if ($data) {
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
}
require Config::get('PATH_VIEW') . '_templates/header.php';
require Config::get('PATH_VIEW') . $filename . '.php';
require Config::get('PATH_VIEW') . '_templates/footer.php';
}
/**
* Similar to render, but accepts an array of separate views to render between the header and footer. Use like
* the following: $this->view->renderMulti(array('help/index', 'help/banner'));
* @param array $filenames Array of the paths of the to-be-rendered view, usually folder/file(.php) for each
* @param array $data Data to be used in the view
* @return bool
*/
public function renderMulti($filenames, $data = null)
{
if (!is_array($filenames)) {
self::render($filenames, $data);
return false;
}
if ($data) {
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
}
require Config::get('PATH_VIEW') . '_templates/header.php';
foreach($filenames as $filename) {
require Config::get('PATH_VIEW') . $filename . '.php';
}
require Config::get('PATH_VIEW') . '_templates/footer.php';
}
/**
* Same like render(), but does not include header and footer
* @param string $filename Path of the to-be-rendered view, usually folder/file(.php)
* @param mixed $data Data to be used in the view
*/
public function renderWithoutHeaderAndFooter($filename, $data = null)
{
if ($data) {
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
}
require Config::get('PATH_VIEW') . $filename . '.php';
}
/**
* Renders pure JSON to the browser, useful for API construction
* @param $data
*/
public function renderJSON($data)
{
header("Content-Type: application/json");
echo json_encode($data);
}
/**
* renders the feedback messages into the view
*/
public function renderFeedbackMessages()
{
// echo out the feedback messages (errors and success messages etc.),
// they are in $_SESSION["feedback_positive"] and $_SESSION["feedback_negative"]
require Config::get('PATH_VIEW') . '_templates/feedback.php';
// delete these messages (as they are not needed anymore and we want to avoid to show them twice
Session::set('feedback_positive', null);
Session::set('feedback_negative', null);
}
/**
* Checks if the passed string is the currently active controller.
* Useful for handling the navigation's active/non-active link.
*
* @param string $filename
* @param string $navigation_controller
*
* @return bool Shows if the controller is used or not
*/
public static function checkForActiveController($filename, $navigation_controller)
{
$split_filename = explode("/", $filename);
$active_controller = $split_filename[0];
if ($active_controller == $navigation_controller) {
return true;
}
return false;
}
/**
* Checks if the passed string is the currently active controller-action (=method).
* Useful for handling the navigation's active/non-active link.
*
* @param string $filename
* @param string $navigation_action
*
* @return bool Shows if the action/method is used or not
*/
public static function checkForActiveAction($filename, $navigation_action)
{
$split_filename = explode("/", $filename);
$active_action = $split_filename[1];
if ($active_action == $navigation_action) {
return true;
}
return false;
}
/**
* Checks if the passed string is the currently active controller and controller-action.
* Useful for handling the navigation's active/non-active link.
*
* @param string $filename
* @param string $navigation_controller_and_action
*
* @return bool
*/
public static function checkForActiveControllerAndAction($filename, $navigation_controller_and_action)
{
$split_filename = explode("/", $filename);
$active_controller = $split_filename[0];
$active_action = $split_filename[1];
$split_filename = explode("/", $navigation_controller_and_action);
$navigation_controller = $split_filename[0];
$navigation_action = $split_filename[1];
if ($active_controller == $navigation_controller AND $active_action == $navigation_action) {
return true;
}
return false;
}
/**
* Converts characters to HTML entities
* This is important to avoid XSS attacks, and attempts to inject malicious code in your page.
*
* @param string $str The string.
* @return string
*/
public function encodeHTML($str)
{
return htmlentities($str, ENT_QUOTES, 'UTF-8');
}
}