initial commit
This commit is contained in:
106
application/core/Application.php
Normal file
106
application/core/Application.php
Normal 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
82
application/core/Auth.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
application/core/Config.php
Normal file
23
application/core/Config.php
Normal 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];
|
||||
}
|
||||
}
|
||||
34
application/core/Controller.php
Normal file
34
application/core/Controller.php
Normal 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
60
application/core/Csrf.php
Normal 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);
|
||||
}
|
||||
}
|
||||
64
application/core/DatabaseFactory.php
Normal file
64
application/core/DatabaseFactory.php
Normal 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;
|
||||
}
|
||||
}
|
||||
146
application/core/Encryption.php
Normal file
146
application/core/Encryption.php
Normal 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;
|
||||
}
|
||||
}
|
||||
18
application/core/Environment.php
Normal file
18
application/core/Environment.php
Normal 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");
|
||||
}
|
||||
}
|
||||
77
application/core/Filter.php
Normal file
77
application/core/Filter.php
Normal 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 '&'
|
||||
* '"' (double quote) becomes '"' when ENT_NOQUOTES is not set.
|
||||
* "'" (single quote) becomes ''' (or ') only when ENT_QUOTES is set.
|
||||
* '<' (less than) becomes '<'
|
||||
* '>' (greater than) becomes '>'
|
||||
*
|
||||
* @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
154
application/core/Mail.php
Normal 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;
|
||||
}
|
||||
}
|
||||
48
application/core/Redirect.php
Normal file
48
application/core/Redirect.php
Normal 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);
|
||||
}
|
||||
}
|
||||
64
application/core/Request.php
Normal file
64
application/core/Request.php
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
135
application/core/Session.php
Normal file
135
application/core/Session.php
Normal 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
32
application/core/Text.php
Normal 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
177
application/core/View.php
Normal 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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user